即将离开作用域的C++变量属于lvalue还是xvalue?
当一个即将离开作用域的变量被返回或抛出时,其资源可以被复用,也就是说可以对其执行moved from(移动操作),如下方C++程序所示:
#include <iostream> struct X { X() { std::cout << "X()\n"; } X(X&&) { std::cout << "X(X&&)\n"; } ~X() { std::cout << "~X()\n"; } }; X f() { X x; std::cout << "return\n"; return x; } int main() { try { X x = f(); std::cout << "throw\n"; throw x; } catch (...) { std::cout << "catch\n"; } return 0; }
编译(我们使用-fno-elide-constructors编译选项关闭复制/移动消除)、链接与执行命令如下:
clang++ -std=c++17 -O2 -Wall -pedantic -pthread -fno-elide-constructors main.cpp && ./a.out
运行输出如下:
X()
return
X(X&&)
~X()
throw
X(X&&)
~X()
catch
~X()
上述代码中return x;与throw x;语句里的x指代的就是资源可被复用的对象。
在《C++编程语言标准工作草案》的[basic.lval-1]章节中,对值类别给出了如下定义:
- glvalue是一类表达式,对其求值可以确定一个对象或函数的身份。
- xvalue是指代资源可被复用的对象的glvalue(通常是因为该对象的生命周期即将结束)。
- lvalue是不属于xvalue的glvalue。
问题解答
此处的x属于lvalue,调用移动构造是标准特殊规则的结果,和它的值类别无关。
- 首先明确变量名作为表达式的默认值类别:普通的自动变量名本身就是lvalue,你可以用编译期断言验证:在
f()函数内加入static_assert(std::is_lvalue_reference_v<decltype((x))>);,编译会直接通过,说明x作为表达式对应的值类别就是lvalue。 - 移动构造被调用的原因:C++标准在拷贝消除相关章节做了特殊规定:当return语句的操作数是符合要求的自动存储期局部变量时,重载决议选择构造函数会优先把它当做右值处理,优先匹配移动构造,匹配失败才回退到拷贝构造。throw语句抛出局部变量时也有完全相同的规则。
- 它和xvalue定义的差异:xvalue的核心属性是「表达式本身就代表资源可复用」,比如
std::move(x)的返回结果就是典型的xvalue。而这里的x本身没有xvalue属性,只是标准在return/throw的特定场景下许可了资源复用,属于规则层面的特殊例外,和xvalue的定义并不冲突。
内容的提问来源于stack exchange,提问作者Géry Ogam
相关产品推荐
相关产品推荐

