我试图理解 C++11 的概念。
我的标准草案说:
一个 xvalue(一个“eXpiring”值)也指一个对象,通常接近其生命周期的末尾(例如,它的资源可能会被移动)。xvalue 是某些涉及右值引用的表达式的结果 (8.3.2)。[ 示例:调用返回类型为右值引用的函数的结果是 xvalue。—结束示例]
好的,那么产生 xvalues 的“某些类型的表达式”到底是什么?规范的这一部分没有详细列出这些表达式。
我理解左值和纯右值(至少我认为,我理解)。
我试图理解 C++11 的概念。
我的标准草案说:
一个 xvalue(一个“eXpiring”值)也指一个对象,通常接近其生命周期的末尾(例如,它的资源可能会被移动)。xvalue 是某些涉及右值引用的表达式的结果 (8.3.2)。[ 示例:调用返回类型为右值引用的函数的结果是 xvalue。—结束示例]
好的,那么产生 xvalues 的“某些类型的表达式”到底是什么?规范的这一部分没有详细列出这些表达式。
我理解左值和纯右值(至少我认为,我理解)。
§5 (C++11 §5[expr]/6) 的介绍中有一个有用的非规范性注释:
[ 注意:表达式是一个 xvalue,如果它是:
调用函数的结果,无论是隐式还是显式,其返回类型是对对象类型的右值引用,
转换为对对象类型的右值引用,
一个类成员访问表达式,指定一个非引用类型的非静态数据成员,其中对象表达式是一个 xvalue,或
一个
.*
指向成员的表达式,其中第一个操作数是一个 xvalue,第二个操作数是一个指向数据成员的指针。一般来说,这条规则的效果是命名的右值引用被视为左值,而对对象的未命名的右值引用被视为xvalue;对函数的右值引用被视为左值,无论是否命名。——尾注]
搜索 §5 的其余部分,这个列表似乎很详尽。该列表后面是一个示例:
struct A { int m; }; A&& operator+(A, A); A&& f(); A a; A&& ar = static_cast<A&&>(a);
表达式
f()
、f().m
、static_cast<A&&>(a)
和a + a
是 xvalues。表达式ar
是一个左值。
获取 xvalue 表达式的常用方法有两种:
用于std::move
移动对象。 std::move
对右值引用类型执行 astatic_cast
并返回右值引用。
用于std::forward
转发右值。 std::forward
通常在函数模板中使用,以实现函数参数的完美转发。
如果提供给函数模板的参数是右值,则参数类型将是右值引用,即左值。在这种情况下,对右值引用类型std::forward
执行 astatic_cast
并返回右值引用。
(注意:如果提供给函数模板的参数是左值,则参数类型将是左值引用并std::forward
返回左值引用。)
第 5 章描述了有效表达式的语法,为每个表达式语法列出了表达式是左值、xvalue 或 prvalue 的条件。第 5 条中可能的 xvalue 的完整列表是:
5.2.2 第 10 段:如果结果类型是对对象类型的右值引用,则函数调用是……一个 xvalue。
(在标准的技术语言中,“对象类型”与“类类型”的含义不同。“对象类型”包括基本类型、指针和数组,仅不包括函数类型。对函数类型的右值引用始终被视为左值,而不是 xvalue。)
返回右值引用的最值得注意的函数当然是std::move
有时std::forward
。
5.2.5 第 4 段:如果
E2
是一个非静态数据成员……如果E1
是一个 xvalue,那么E1.E2
是一个 xvalue
(另一方面,数据成员查找E1->E2
始终是左值。)
类似地,如果E1
是一个 xvalue,则数据成员查找E1.*E2
是一个 xvalue:
5.5 第 6 段:
.*
第二个操作数是指向数据成员的指针的表达式的结果与其第一个操作数具有相同的值类别 (3.10)。
对于各种类型的演员表:
dynamic_cast<Type>(expr)
: 5.2.7 第 2 段static_cast<Type>(expr)
: 5.2.9 第 1 段reinterpret_cast<Type>(expr)
: 5.2.10 第 1 段const_cast<Type>(expr)
: 5.2.11 第 1 段(Type) expr
: 5.4 第 1 段当且仅当Type
是对对象类型的右值引用时,表达式才是 xvalue。对于 也是如此Type(expr)
,因为
5.2.3 第 1 段:如果表达式列表 [在类型名称后面的括号中] 是单个表达式,则类型转换表达式等效于(在定义上,如果在含义上定义)对应的强制转换表达式 (5.4)。
(另一方面,Type{expr}
始终是prvalue。)
关于条件运算符的第 5.16 节最后说A ? B : C
如果 B 和/或 C 是 xvalue,则有时可以是 xvalue。但完整的规则很难总结。
如果表达式最终调用用户定义的重载运算符函数,则第 5.2.2 节适用于该表达式,而不是描述内置运算符行为的表达式。(请参阅a + a
@James 发布的示例中的表达式。)
正如你提到的,
一个 xvalue(一个“eXpiring”值)也指一个对象,通常接近其生命周期的末尾(例如,它的资源可能会被移动)。
重点: object
, be moved
,end lifetime
这是一个例子:
void move_test(){
std::string s = "I'm here!";
std::string m = std::move(s); // move from <s> to <m>
// s is now in an undefined, but valid state;
std::cout << "s=" << s << "; &s=" << &s << std::endl;
}
通过移动,我们可以将命名值(左值,这里是s
)转换为右值(即std::move(s)
)。更具体地说,因为它不能是 a prvalue
(它有一个名字!换句话说,它有一个身份),它最终是一个xvalue
.
xvalue
始终服务于C++ 移动语义。
我从我所读到的内容中收集到的是,将某些东西称为 xvalue 是一种奇特的说法:
xvalue 只是一个右值,其存储可能已被释放,因此使用它意味着您必须自己验证它的存在。
通常,它是远离实际右值的一个或多个间接级别。
相比之下,只要右值在范围内,就可以保证其存储空间存在。
我可能错了,但这是我所理解的。