什么是右值,左值,左值,右值和左值?
在C ++ 03中,expression式是右值或左值 。
在C ++ 11中,expression式可以是:
- 右值
- 左值
- x值
- glvalue
- prvalue
两类分为五类。
- 什么是这些新的expression类别?
- 这些新类别如何与现有的右值和左值类别相关联?
- C ++ 0x中的右值和左值类别与C ++ 03中的相同吗?
- 为什么需要这些新类别? WG21神只是想把我们这些凡人混为一谈?
我想这个文件可能不是那么简短的介绍: n3055
整个屠杀始于移动语义。 一旦我们有了可以移动而不被复制的expression式,那么突然容易掌握的规则就要求区分可以移动的expression式和在哪个方向上。
根据我对草案的看法,r / l值的区别保持不变,只是在移动的东西变得混乱的情况下。
他们需要吗? 如果我们想放弃新function,可能不会。 但是为了让更好的优化,我们应该拥抱他们。
引用n3055 :
- 一个左值 (所谓的,历史上,因为左值可以出现在赋值expression式的左边)指定一个函数或一个对象。 [例如:如果
E
是指针types的expression式,那么*E
是指向*E
指向的对象或函数的左值expression式。 再举一个例子,调用返回types为左值引用的函数的结果是一个左值。] - 一个xvalue (一个“eXpiring”值)也指一个对象,通常接近其生命周期结束(例如,可以移动它的资源)。 xvalue是涉及右值引用的某些expression式的结果。 [例子:调用返回types是右值引用的函数的结果是一个xvalue。]
- 一个glvalue (“广义”左值)是一个左值或一个xvalue 。
- 一个右值 (所谓的,在历史上,因为右值可能出现在赋值expression式的右侧)是一个xvalue,一个临时对象或其子对象,或一个与对象无关的值。
- 一个prvalue (“纯”的右值)是一个不是xvalue的右值。 [例子:调用返回types不是引用的函数的结果是一个prvalue]
所讨论的文件对于这个问题来说是一个很好的参考,因为它显示了由于引入新术语而发生的标准的确切变化。
什么是这些新的expression类别?
FCD(n3092)有一个很好的描述:
– 一个左值(所谓的,在历史上,因为左值可能出现在赋值expression式的左边)指定一个函数或一个对象。 [例如:如果E是指针types的expression式,那么* E是指向E指向的对象或函数的左值expression式。 作为另一个例子,调用返回types是左值引用的函数的结果是左值。 – 例子]
– 一个xvalue(一个“eXpiring”值)也指一个对象,通常接近它的生命周期结束(例如,它的资源可能会被移动)。 xvalue是涉及右值引用(8.3.2)的某些expression式的结果。 [例子:调用返回types是右值引用的函数的结果是一个xvalue。 – 例子]
– 一个glvalue(“广义”左值)是一个左值或一个xvalue。
– 一个右值(所谓的历史上,因为右值可能出现在赋值expression式的右边)是一个xvalue,一个临时对象(12.2)或其子对象,或一个与对象无关的值。
– 一个prvalue(“纯”的右值)是一个不是xvalue的右值。 [例子:调用返回types不是引用的函数的结果是一个prvalue。 诸如12,7.3e5或true的字面值也是一个值。 – 例子]
每个expression式都属于这个分类中的一个基本分类:左值,左值或右值。 expression式的这个属性称为它的值类别。 [注:在第5章讨论每个内置操作符指出了它产生的值的类别和它期望的操作数的值类别。 例如,内置赋值运算符期望左操作数是左值,右操作数是前值,并产生左值作为结果。 用户定义的运算符是函数,它们期望的值的类别和产出取决于它们的参数和返回types。 – 结束注释
我build议你阅读整节3.10左值和右值 。
这些新的类别如何与现有的右值和左值类别相关联?
再次:
C ++ 0x中的右值和左值类别与C ++ 03中的相同吗?
rvalues的语义特别是随着移动语义的引入而发展起来的。
为什么需要这些新类别?
所以这一举措的build设/任务可以定义和支持。
我将从最后一个问题开始:
为什么需要这些新类别?
C ++标准包含许多处理expression式的值类别的规则。 一些规则区分了左值和右值。 例如,当涉及到重载parsing。 其他的规则也是区分了价值和价值。 例如,您可以使用不完整或抽象types的glvalue,但不具有不完整或抽象types的prvalue。 在我们使用这个术语之前,实际上需要区分左值/右值的规则,它们或者是无意的错误,或者是包含了大量的解释和例外规则,“除非右值是由于未命名右值引用…“。 所以,把自己的名字和价值的概念给予自己的名字似乎是个好主意。
什么是这些新的expression类别? 这些新的类别如何与现有的右值和左值类别相关联?
我们仍然有与C ++ 98兼容的术语左值和右值。 我们刚刚将rvalues分成两个子组,xvalues和prvalues,我们把lvalues和xvalues称为glvalues。 X值是未命名的右值引用的一种新的值类别。 每个expression式都是以下三种之一:左值,左值,右值。 维恩图如下所示:
______ ______ / X \ / / \ \ | l | x | pr | \ \ / / \______X______/ gl r
具有function的示例:
int prvalue(); int& lvalue(); int&& xvalue();
但也不要忘记,名称右值引用是左值:
void foo(int&& t) { // t is initialized with an rvalue expression // but is actually an lvalue expression itself }
为什么需要这些新类别? WG21神只是想把我们这些凡人混为一谈?
我不觉得其他的答案(虽然其中很多都是好的)真的能够解答这个问题。 是的,这些类别和类似的存在允许移动语义,但复杂性存在的原因之一。 这是在C ++ 11中移动东西的一个不可侵犯的规则:
只有在毫无疑问安全的情况下才能移动。
这就是为什么这些类别存在的原因:能够谈论可以安全地从这些价值中移走的价值观,以及在哪些价值观不存在的地方谈论价值。
在最早版本的r值引用中,移动很容易发生。 太容易了。 很容易,当用户没有真正的意思时,隐含地移动事物的可能性很大。
以下是在什么情况下可以安全移动的东西:
- 当它是一个临时或子对象。 (prvalue)
- 当用户明确表示要移动它 。
如果你这样做:
SomeType &&Func() { ... } SomeType &&val = Func(); SomeType otherVal{val};
这是做什么的? 在规范的旧版本中,在5个值出现之前,这会引起一个移动。 当然是的。 你传递了一个右值引用给构造函数,因此它绑定到了一个右值引用的构造函数。 这很明显。
这只是一个问题。 你没有要求移动它。 哦,你可能会说&&
应该是一个线索,但是这并不能改变它打破了规则的事实。 val
并不是临时性的,因为临时性人物没有名字。 你可能已经延长了临时的生命,但这意味着它不是暂时的 ; 就像任何其他的堆栈variables一样。
如果不是暂时的,而且你没有要求移动它,那么移动是错误的。
显而易见的解决办法是做一个左值。 这意味着你不能离开它。 好的; 它的名字,所以它是一个左值。
一旦你这样做,你不能再说SomeType&&
意味着同样的事情。 现在你已经对命名的右值引用和未命名的右值引用进行了区分。 那么,名为右值引用是左值; 这是我们上面的解决scheme。 那么我们称之为未命名的右值引用(上面Func
的返回值)呢?
这不是一个左值,因为你不能从左值移动。 我们需要能够通过返回一个&&
; 你还可以怎样明确地说要搬家? 毕竟这就是std::move
返回。 这不是一个右值(旧式),因为它可能在方程的左边(事实上有点复杂,请看下面的问题和注释)。 它既不是左值也不是右值; 这是一种新的东西。
我们所拥有的是一种价值,你可以把它看作是一个左值, 除了它是隐含的可移动的 。 我们把它称为一个xvalue 。
请注意,xvalues是什么使我们获得另外两类值:
-
一个prvalue实际上只是前面types右值的新名称,即它们是不是 xvalues的右值。
-
glvalues是一个组中的xvalue和lvalues的联合 ,因为它们共享很多属性。
所以真的,这一切都归结为xvalues,并需要限制到确切的,只有某些地方的运动。 这些地方是由右值类别定义的; prvalues是隐式的动作,而xvalues是明确的动作( std::move
返回一个xvalue)。
恕我直言,关于它的含义最好的解释给了我们Stroustrup +考虑DánielSándor和Mohan的例子:
斯特劳斯:
现在我很担心。 显然,我们正在陷入僵局或混乱,或两者兼而有之。 我花了午餐时间做一个分析,看看哪些属性(值)是独立的。 只有两个独立的属性:
has identity
– 即地址,指针,用户可以确定两个副本是否相同,等等。can be moved from
– 也就是说,我们被允许以一些不确定的,但有效的状态离开来源的“副本”这使我得出这样的结论:正好有三种价值观(使用正则expression式使用大写字母表示否定的正则expression式 – 我很着急):
iM
:有身份,不能移动im
:具有身份,并且可以从(例如,将左值转换为右值引用的结果)Im
:没有身份,可以从第四种可能性(IM
:没有身份,不能被移动)在C++
(或者我认为)在任何其他语言中没有用处。除了这三个基本的价值分类之外,我们还有两个明显的概括,即对应于两个独立的属性:
i
:有身份m
:可以从中移出这导致我把这个图表放在板子上:
命名
我观察到,我们的名字只有有限的自由:左边的两个点(标记为
iM
和i
)是lvalues
或多或less有人称为左值,右边的两点(标为m
和Im
)是人们或多或less的forms被称为rvalues
。 这必须反映在我们的命名中。 也就是说,W
的左“腿”应该有与lvalue
有关的名字,W
的右“腿”应该有与rvalue.
相关的名字rvalue.
我注意到,整个讨论/问题来自于引用右值引用和移动语义。 这些概念根本不存在于由rvalues
和rvalues
组成的rvalues
世界中。 有人观察到那个想法
- 每个
value
都是lvalue
或rvalue
lvalue
不是rvalue
,rvalue
不是lvalue
深深植根于我们的意识,非常有用的特性,这种二分法的痕迹可以在草案标准中find。 我们都同意,我们应该保留这些属性(并使其精确)。 这进一步限制了我们的命名select。 我观察到标准库的措辞使用
rvalue
来表示m
(泛化),为了保留标准库的期望和文本,W
右下angular应该被命名为rvalue.
这导致了对命名的重点讨论。 首先,我们需要决定
lvalue.
如果lvalue
意味着iM
还是泛化? 由格雷戈尔(Doug Gregor)领导,我们用核心语言的词语列出了lvalue
这个词是否合格的地方。 一个清单是在大多数情况下,在最棘手/脆弱的文本lvalue
意味着iM
。 这是左值的古典意义,因为“旧时”没有什么动作;move
是一个新的概念在C++0x
。 另外,命名W
lvalue
的顶点给了我们每一个值都是lvalue
或rvalue
但不是两者的属性。所以
W
左上angular是lvalue
,右下angular是rvalue.
这是什么使左下angular和右上angular的点? 左下angular是经典左值的推广,允许移动。 所以这是一个generalized lvalue.
我们把它命名为“glvalue.
你可以质疑这个缩写,但是(我认为)不符合逻辑。 我们假定在严重使用中,总之generalized lvalue
不pipe怎么样都可以简化,所以我们最好马上做(或者冒险)。 W的右上angular比右下angular(现在,一直称为rvalue
)不那么一般。 这一点代表了一个可以移动的对象的原始纯粹概念,因为它不能再被引用(除了析构函数)。 我喜欢使用specialized rvalue
与generalized lvalue
specialized rvalue
对比,但是将pure rvalue
缩写为generalized lvalue
pure rvalue
(可能是正确的)。 所以W的左腿是lvalue
和右lvalue
,glvalue
是lvalue
和prvalue
rvalue.
顺便说一下,每一个价值都是一个价值或一个价值,但不是两个。这离开
W
:im
的顶部中间; 也就是说,具有身份和可以移动的值。 我们真的没有什么能够引导我们为这些神秘的野兽命名。 对于(草案)标准文本工作人员来说,它们非常重要,但不可能成为家喻户晓的名字。 我们没有发现任何真正的约束来指导我们,所以我们select了“x”作为中心,未知,奇怪,xpert,甚至是x-rating。
C ++ 03的类别太受限制,无法正确地将rvalue引用引入到expression式属性中。
有了它们的介绍,有人说,一个未命名的右值引用计算右值,这样重载parsing会更喜欢右值引用绑定,这将使它select移动构造函数超过拷贝构造函数。 但是,发现这会导致所有问题,例如dynamictypes和资格。
为了表明这一点,考虑
int const&& f(); int main() { int &&i = f(); // disgusting! }
在预先xvalue草稿,这是允许的,因为在C ++ 03中,非类types的rvalues从不cv限定。 但是const
意图是在右值引用的情况下,因为在这里我们引用了对象(= memory!),并且从非类的右值中删除const主要是因为没有对象。
dynamictypes的问题具有类似的性质。 在C ++ 03中,类types的右值具有已知的dynamictypes – 它是该expression式的静态types。 因为有另一种方式,你需要引用或解引用,评估为左值。 对于未命名的右值引用,这是不正确的,但它们可以显示多态行为。 所以要解决它,
-
未命名的右值引用变成xvalues 。 他们可以是合格的,并且可能具有不同的dynamictypes。 它们像意图一样,在重载期间更喜欢右值引用,并且不会绑定到非常量左值引用。
-
以前是一个右值(文字,通过转换为非引用types创build的对象)现在变成了一个前值 。 它们在重载时与xvalues有相同的偏好。
-
以前是一个左值留在左翼。
两个分组是为了捕获那些可以被限定的,可以有不同的dynamictypes( glvalues )和那些重载比较喜欢rvalue引用绑定( rvalues )的。
介绍
ISOC ++ 11(官方ISO / IEC 14882:2011)是C ++编程语言标准的最新版本。 它包含一些新的function和概念,例如:
- 右值引用
- xvalue,glvalue,prvalueexpression式值类别
- 移动语义
如果我们想了解新的expression式值类别的概念,我们必须知道有右值和左值引用。 知道rvalues可以传递给非常量右值引用。
int& r_i=7; // compile error int&& rr_i=7; // OK
如果我们引用工作草案N3337(与已公布的ISOC ++ 11标准最相似的草案)中的Lvalues和rvalues这一小节,我们可以对价值类别的概念有一些直觉。
3.10左值和右值[basic.lval]
1expression式根据图1中的分类进行分类。
- 一个左值(所谓的,历史上,因为左值可能出现在赋值expression式的左边)指定一个函数或一个对象。 [例如:如果E是指针types的expression式,那么* E是指向E指向的对象或函数的左值expression式。 作为另一个例子,调用返回types是左值引用的函数的结果是左值。 – 例子]
- 一个xvalue(一个“eXpiring”值)也指一个对象,通常接近其生命周期结束(例如,可以移动它的资源)。 xvalue是涉及右值引用(8.3.2)的某些expression式的结果。 [例子:调用返回types是右值引用的函数的结果是一个xvalue。 – 例子]
- 一个glvalue(“广义”左值)是一个左值或一个xvalue。
- 一个右值(所谓的,历史上,因为右值可能出现在赋值expression式的右侧)是一个xvalue,一个
临时对象(12.2)或其子对象,或者不是的值
与一个对象相关联。- 一个prvalue(“纯”的右值)是一个不是xvalue的右值。 [例子:调用返回types不是的函数的结果
参考是一个prvalue。 一个文字的值,如12,7.3e5或
真正的也是一个值得。 – 例子]每个expression式都属于这个分类中的一个基本分类:左值,左值或右值。 expression式的这个属性称为它的值类别。
但是我不太清楚这个小节是否足够清楚地理解这些概念,因为“通常”并不是一般的,“接近其一生的终点”并不是真正具体的,“涉及参考价值”并不是很清楚,和“示例:调用返回types是右值引用的函数的结果是一个xvalue”。 听起来像是一条蛇咬着它的尾巴。
主要价值类别
每个expression式都属于一个主要的值类别。 这些值类别是左值,左值和右值类别。
左值
expression式E属于左值类,当且仅当E指的是实体已经有一个身份(地址,名称或别名),使得它可以在E之外访问。
#include <iostream> int i=7; const int& f(){ return i; } int main() { std::cout<<&"www"<<std::endl; // This address ... std::cout<<&"www"<<std::endl; // ... and this address are the same. "www"; // The expression "www" in this row is an lvalue expression, because it refers to the same entity ... "www"; // ... as the entity the expression "www" in this row refers to. i; // The expression i in this row is an lvalue expression, because it refers to the same entity ... i; // ... as the entity the expression i in this row refers to. int* p_i=new int(7); *p_i; // The expression *p_i in this row is an lvalue expression, because it refers to the same entity ... *p_i; // ... as the entity the expression *p_i in this row refers to. const int& r_I=7; r_I; // The expression r_I in this row is an lvalue expression, because it refers to the same entity ... r_I; // ... as the entity the expression r_I in this row refers to. f(); // The expression f() in this row is an lvalue expression, because it refers to the same entity ... i; // ... as the entity the expression f() in this row refers to. return 0; }
xvalues
expression式E属于xvalue类,当且仅当它是
– 调用一个函数的结果,无论是隐式的还是显式的,其返回types是一个右值的引用,返回的对象的types,或者
int&& f(){ return 3; } int main() { f(); // The expression f() belongs to the xvalue category, because f() return type is an rvalue reference to object type. return 0; }
– 强制转换为对象types的右值引用,或者
int main() { static_cast<int&&>(7); // The expression static_cast<int&&>(7) belongs to the xvalue category, because it is a cast to an rvalue reference to object type. std::move(7); // std::move(7) is equivalent to static_cast<int&&>(7). return 0; }
– 一个类成员访问expression式,指定一个非引用types的非静态数据成员,其中的对象expression式是一个xvalue,或者
struct As { int i; }; As&& f(){ return As(); } int main() { f().i; // The expression f().i belongs to the xvalue category, because As::i is a non-static data member of non-reference type, and the subexpression f() belongs to the xvlaue category. return 0; }
– 指向成员的expression式,其中第一个操作数是一个xvalue,第二个操作数是指向数据成员的指针。
请注意,上述规则的效果是,对象的名称右值引用被视为左值,而对于对象的未命名右值引用被视为xvalue; 对函数的右值引用被视为左值,无论是否被命名。
#include <functional> struct As { int i; }; As&& f(){ return As(); } int main() { f(); // The expression f() belongs to the xvalue category, because it refers to an unnamed rvalue reference to object. As&& rr_a=As(); rr_a; // The expression rr_a belongs to the lvalue category, because it refers to a named rvalue reference to object. std::ref(f); // The expression std::ref(f) belongs to the lvalue category, because it refers to an rvalue reference to function. return 0; }
prvalues
expression式E属于prvalue类,当且仅当E既不属于左值也不属于xvalue类。
struct As { void f(){ this; // The expression this is a prvalue expression. Note, that the expression this is not a variable. } }; As f(){ return As(); } int main() { f(); // The expression f() belongs to the prvalue category, because it belongs neither to the lvalue nor to the xvalue category. return 0; }
混合价值类别
还有两个重要的混合价值类别。 这些值类别是右值和glvalue类别。
右值
expression式E属于右值类,当且仅当E属于xvalue类或prvalue类。
请注意,这个定义意味着当且仅当E指的是一个没有任何身份使其在EYET外可访问的实体时,expression式E属于右值类别。
glvalues
expression式E属于glvalue类,当且仅当E属于左值类别或xvalue类别。
实用的规则
Scott Meyer 发表了一个非常有用的经验法则来区分左值和左值。
- 如果可以采用expression式的地址,则expression式是一个左值。
- 如果expression式的types是左值引用(例如,T&或const T&等),则该expression式是左值。
- 否则,expression式是一个右值。 从概念上(通常也是实际上),右值对应于临时对象,例如从函数返回的或通过隐式types转换创build的临时对象。 大多数文字值(例如10和5.3)也是rvalues。
我一直在努力,直到我遇到了价值类别的cppreference.com解释。
其实很简单,但是我发现它经常被解释为很难记住。 这里是非常示意性的解释。 我会引用页面的某些部分:
主要类别
主要的值类别对应于expression式的两个属性:
具有同一性 :可以确定expression式是否指向与另一个expression式相同的实体,例如通过比较对象的地址或它们识别的function(直接或间接获得的);
可以从以下位置移动:移动构造函数,移动赋值运算符或实现移动语义的另一个函数重载可以绑定到expression式。
expression方式:
- 有身份而且不能被移动的称为左值expression式 ;
- 有身份,可以被称为xvalueexpression式 ;
- 没有身份,可以被称为prvalueexpression式 ;
- 没有身份,不能被移动不被使用。
左值
左值(“左值”)expression式是具有标识且不能从中移出的expression式。
Rvalue(直到C ++ 11),prvalue(自C ++ 11以来)
一个prvalue(“纯右值”)expression式是一个没有标识并且可以从中移出的expression式。
x值
An xvalue ("expiring value") expression is an expression that has identity and can be moved from .
glvalue
A glvalue ("generalized lvalue") expression is an expression that is either an lvalue or an xvalue. It has identity . It may or may not be moved from.
rvalue (since C++11)
An rvalue ("right value") expression is an expression that is either a prvalue or an xvalue. It can be moved from . It may or may not have identity.
How do these new categories relate to the existing rvalue and lvalue categories?
A C++03 lvalue is still a C++11 lvalue, whereas a C++03 rvalue is called a prvalue in C++11.
One addendum to the excellent answers above, on a point that confused me even after I had read Stroustrup and thought I understood the rvalue/lvalue distinction. 当你看到
int&& a = 3
,
it's very tempting to read the int&&
as a type and conclude that a
is an rvalue. It's not:
int&& a = 3; int&& c = a; //error: cannot bind 'int' lvalue to 'int&&' int& b = a; //compiles
a
has a name and is ipso facto an lvalue. Don't think of the &&
as part of the type of a
; it's just something telling you what a
is allowed to bind to.
This matters particularly for T&&
type arguments in constructors. 如果你写
Foo::Foo(T&& _t) : t{_t} {}
you will copy _t
into t
. You need
Foo::Foo(T&& _t) : t{std::move(_t)} {}
if you want to move. Would that my compiler warned me when I left out the move
!