Mag*_*ero 4 c++ lvalue language-lawyer move-semantics xvalue
当一个即将超出范围的变量被返回或抛出时,它的资源可以被重用,即它可以从 中移出,如下面的 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;
}
Run Code Online (Sandbox Code Playgroud)
编译(我们使用标志关闭复制/移动省略-fno-elide-constructors)、链接和执行:
clang++ -std=c++17 -O2 -Wall -pedantic -pthread -fno-elide-constructors\
main.cpp && ./a.out
Run Code Online (Sandbox Code Playgroud)
输出:
X()
返回
X(X&&)
~X()
抛出
X(X&&)
~X()
catch
~X()
x在上面的语句中return x;, 和throw x;表示一个资源可以重用的对象。
在工作草案,编程语言 C++ 标准,[basic.lval-1]中,我们对值类别有以下定义:
- 左值是一个表达式,其计算确定对象或函数的身份。
- xvalue是一个泛左值,表示其资源可以重用的对象(通常是因为它已接近其生命周期的终点)。
- 左值是不是 xvalue 的左值。
那么是x左值还是x值呢?
x本身就是一个左值。
\n\n以下表达式是左值表达式:
\n\n
\n- 变量、函数或数据成员的名称
\n, a template parameter object (since C++20),无论类型如何,例如 std::cin 或 std::endl。即使变量的类型是右值引用,由其名称组成的表达式也是左值表达式;
对于局部变量 as x,在return 语句和throw 表达式中,初始化的重载决策分两阶段进行;首先,好像x是一个右值表达式(然后可以选择移动构造函数)。
回报声明:
\n\n\n\n
or, for co_return, to select the overload of promise.return_value() (since C++20)(C++11 起) 然后执行两次重载解析来选择用于初始化返回值的构造函数:\n
\n- 首先,就好像表达式是右值表达式(因此它可以选择移动构造函数),并且
\n
在抛出表达式中:
\n\n\n\n
\n- 如果左值表达式命名局部变量或函数或 catch 子句参数,其范围不超过最内层封闭的 try 块(如果有),则也可以通过与 return 语句中相同的两步重载决策来调用左值表达式的移动构造函数(自 C++17 起)
\n
作为效果,在这两种情况下都选择了移动构造函数。这只是returnand的特殊之处throw,并不意味着x是rvalue或xvalue完全。如果您像X x2(x);in那样编写f(),则将选择复制构造函数(并导致错误,因为复制构造函数被隐式删除)。
根据标准,[class.copy.elision]/3:
\n\n\n\n隐式可移动实体是具有自动存储持续时间的变量,它可以是非易失性对象,也可以是对非易失性对象类型的右值引用。在以下复制初始化上下文中,在尝试复制操作之前首先考虑移动操作:
\n(3.1) -\n如果 return ([stmt.return]) 或 co_\xc2\xadreturn ([stmt.return.coroutine]) 语句中的表达式是一个(可能带括号的)id 表达式,它命名了声明的隐式可移动实体在最内层封闭函数或 lambda 表达式的主体或参数声明子句中,或者
\n(3.2) -\n如果 throw 表达式 ([expr.throw]) 的操作数是一个(可能带括号的)id 表达式,它命名了一个隐式可移动实体,该实体属于不包含该复合语句的作用域最里面的 try 块或函数 try 块(如果有),其复合语句或构造函数初始化器包含 throw 表达式,
\n首先执行重载解析,以选择要复制的构造函数或要调用的 return_\xc2\xadvalue 重载,就好像表达式或操作数是右值一样。如果第一次重载决策失败或未执行,则会再次执行重载决策,并将表达式或操作数视为左值。
\n
\n\n\n如果实体是函数、变量、结构化绑定、数据成员或模板参数对象,则表达式是左值,否则是纯右值 ([basic.lval]);
\n
\n\n(1.1) -\n泛左值是一个表达式,其计算确定对象或函数的身份。
\n(1.2) -\n纯右值是一个表达式,其求值初始化一个对象或计算一个运算符的操作数的值(由它出现的上下文指定),或者是一个类型为 cv void 的表达式。
\n(1.3) -\nxvalue 是一个泛左值,表示其资源可以重用的对象(通常是因为它已接近其生命周期的末尾)。
\n(1.4) -\n左值是不是 x 值的左值。
\n(1.5) -\n右值是右值或x值。
\n[注 3:表达式是 x 值,如果它是:
\n(4.1) -\n调用函数的结果,无论是隐式还是显式,其返回类型是对对象类型的右值引用 ([expr.call]),
\n(4.2) -\na 转换为对象类型的右值引用 ([expr.type.conv], [expr.dynamic.cast], [expr.static.cast] [expr.reinterpret.cast], [expr.const .cast], [expr.cast]),
\n(4.3) -\na 使用 xvalue 数组操作数 ([expr.sub]) 进行下标操作,
\n(4.4) -\na 指定非引用类型的非静态数据成员的类成员访问表达式,其中对象表达式是 xvalue ([expr.ref]),或者
\n(4.5) -\na .* 指向成员的指针表达式,其中第一个操作数是 xvalue,第二个操作数是指向数据成员 ([expr.mptr.oper]) 的指针。
\n
| 归档时间: |
|
| 查看次数: |
323 次 |
| 最近记录: |