在C++03中,表达式要么是右值,要么是左值。

在C++11中,表达式可以是:

右值左值x值glvalue值prvalue值

两个类别变成了五个类别。

这些新的表达类别是什么?这些新类别与现有的右值和左值类别有何关联?C++0x中的右值和左值类别是否与C++03中的相同?为什么需要这些新类别?WG21众神只是想迷惑我们这些凡人吗?


当前回答

这些新类别与现有的右值和左值类别有何关联?

C++03左值仍然是C++11左值,而C++03右值在C++11中称为prvalue。

其他回答

我将从你的最后一个问题开始:

为什么需要这些新类别?

C++标准包含许多处理表达式的值类别的规则。有些规则区分左值和右值。例如,当涉及到过载解决方案时。其他规则区分glvalue和prvalue。例如,可以有一个glvalue具有不完整或抽象类型,但没有prvalue具有不完全或抽象类型。在我们使用这个术语之前,实际上需要区分glvalue/prvalue的规则称为lvalue/rvalue,它们要么是无意中错误的,要么包含大量对规则的解释和例外“……除非rvalue是由于未命名的rvalue引用……”。因此,只给glvalues和prvalues的概念起自己的名字似乎是个好主意。

这些新的表达类别是什么?这些新类别与现有的右值和左值类别有何关联?

我们仍然有与C++98兼容的术语lvalue和rvalue。我们将右值分为两个子组,xvalue和prvalue,我们将左值和xvalue称为glvalue。Xvalues是未命名右值引用的一种新的值类别。每个表达式都是这三个值之一:lvalue、xvalue、prvalue。Venn图如下所示:

    ______ ______
   /      X      \
  /      / \      \
 |   l  | x |  pr  |
  \      \ /      /
   \______X______/
       gl    r

函数示例:

int   prvalue();
int&  lvalue();
int&& xvalue();

但也不要忘记,命名的rvalue引用是lvalues:

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不是临时的,因为临时人员没有名字。你可能延长了暂时的生命周期,但这意味着它不是暂时的;它就像任何其他堆栈变量一样。

如果这不是暂时的,并且你没有要求移动它,那么移动是错误的。

显而易见的解决方案是使val成为左值。这意味着你不能离开它。好的,好的;它被命名,所以它是一个左值。

一旦你做到了这一点,你就不能再说SomeType&&在任何地方都意味着相同的东西。现在,您已经区分了命名的右值引用和未命名的右值引用。嗯,命名的右值引用是左值;这就是我们上面的解决方案。那么我们怎么称呼未命名的右值引用(上面Func的返回值)呢?

它不是左值,因为你不能从左值移动。我们需要能够通过返回&&;你怎么能明确地说要搬东西?毕竟,这就是std::move返回的结果。这不是一个右值(老式),因为它可以位于方程的左侧(事实上情况有点复杂,请参阅下面的问题和注释)。它既不是左值,也不是右值;这是一种新事物。

我们所拥有的是一个可以作为左值处理的值,除了它可以从中隐式移动。我们称之为xvalue。

注意,xvalues是我们获得其他两类值的原因:

prvalue实际上只是先前类型的rvalue的新名称,即它们是不是xvalue的rvalues。Glvalues是xvalues和lvalue在一个组中的联合,因为它们确实共享许多共同的财产。

所以,实际上,这一切都归结为xvalue和将移动限制在特定位置的需要。这些地方由右值类别定义;prvalues是隐式移动,xvalues是显式移动(std::move返回一个xvalue)。

我一直在纠结这个问题,直到我看到cpreference.com对价值类别的解释。

这其实很简单,但我发现它的解释方式很难记住。这里非常示意性地解释了这一点。我将引用页面的某些部分:

主要类别主要值类别对应于表达式的两个财产:具有同一性:可以通过比较对象的地址或它们标识的函数(直接或间接获得)来确定表达式是否与另一个表达式引用相同的实体;可以从以下位置移动:移动构造函数、移动赋值运算符或实现移动语义的另一个函数重载可以绑定到表达式。表达式:具有标识且不能从中移动的称为左值表达式;具有标识并且可以从中移动的称为xvalue表达式;没有标识并且可以从中移动的称为prvalue表达式;没有标识并且无法从中移动。左值左值(“左值”)表达式是一个具有标识且不能从中移动的表达式。rvalue(直到C++11),prvalue(从C++11开始)prvalue(“pure rvalue”)表达式是一个没有标识的表达式,可以从中移动。x值xvalue(“过期值”)表达式是一个具有标识的表达式,可以从中移动。glvalue值glvalue(“广义左值”)表达式是左值或xvalue的表达式。它有身份。它可以移动,也可以不移动。右值(自C++11以来)右值(“右值”)表达式是一个prvalue或xvalue表达式。它可以从移动。它可能有身份,也可能没有身份。

让我们把它放到一张桌子上:

Can be moved from (= rvalue) Cannot be moved from
Has identity (= glvalue) xvalue lvalue
No identity prvalue not used

这些是C++委员会用来在C++11中定义移动语义的术语。这就是故事。

我发现很难理解这些术语,因为它们有精确的定义、长长的规则列表或这个流行的图表:

在带有典型示例的Venn图上更容易:

基本上:

每个表达式都是左值或右值必须复制左值,因为它具有标识,所以可以稍后使用可以移动rvalue,因为它是临时的(prvalue)或显式移动的(xvalue)

现在,好问题是,如果我们有两个正交的财产(“有恒等式”和“可以移动”),那么完成左值、xvalue和prvalue的第四个类别是什么?这将是一个没有标识的表达式(因此以后无法访问),并且无法移动(需要复制其值)。这根本没用,所以没有命名。

IMHO,关于其含义的最佳解释给了我们Stroustrup+考虑到Dániel Sándor和Mohan的例子:

斯特鲁斯特鲁普:

现在我非常担心。显然,我们正在走向僵局,或者一团糟或两者兼而有之。我用午餐时间做了一个分析,看看(值的)财产是独立的。只有两个独立财产:具有标识–即地址、指针,用户可以确定两个副本是否相同等。可以从中移动–即,我们可以以某种不确定但有效的状态离开到“副本”的源这使我得出结论:值(使用正则表达式符号技巧,使用大写字母表示否定——我很着急):iM:具有标识,无法从中移动im:具有身份并且可以从中移动(例如,将左值转换为右值引用的结果)Im:没有身份,可以被转移。第四种可能性是IM(没有身份,无法移动)在C++(或者,我认为)任何其他语言中都很有用。除了这三种基本的价值分类,我们有两个明显的概括,与这两个相对应独立财产:i: 具有身份m: 可以从移动这让我把这张图放在黑板上:命名我观察到,我们只有有限的命名自由:左边(标记为iM和i)是人们或多或少拥有的形式称为lvalues和右边的两点(标记为m和Im)是多少有些拘谨的人称为右值。这必须反映在我们的命名中。即,W的左“腿”应具有与左值和W的右“腿”应该有和右值相关的名称。我注意到了整个讨论/问题源于右值引用和移动语义。这些概念根本不存在在Strachey的世界中,只有右值和左值。某人观察到每个值都是左值或右值左值不是右值,右值不是左值深深嵌入我们的意识,非常有用的财产,以及在整个标准草案中都可以找到这种二分法的痕迹。我们所有人都同意我们应该保留那些财产(并使其精确)。这进一步限制了我们的命名选择。我观察到标准库措辞使用rvalue表示m(概括),以便保留标准库W的右下点应命名为右值。这导致了对命名的集中讨论。首先,我们需要决定左值。左值是指iM还是广义i?带路道格·格雷戈,我们列出了核心语言措辞中的位置其中lvalue一词被限定为表示一个或另一个。A.在大多数情况下,在最棘手/最脆弱的文本中列出了列表lvalue当前表示iM。这是左值的经典含义因为“在过去的日子里”,什么都没有动过;这是一个新颖的概念在C++0x中。此外,命名左上角的W值给了我们每个值都是左值或右值,但不是两者都是。所以,W的左上角是左值,右下角是为右值。左下角和右上角是什么?左下点是经典左值的推广,允许移动。所以它是一个广义的左值。我们给它取名glvalue值。你可以对缩写词吹毛求疵,但(我认为)不行逻辑。我们假设在认真使用广义左值无论如何都会被缩写,所以我们最好这样做立即(或风险混淆)。W的右上角较小一般比右下角(现在,一如既往,称为右值)。那个点表示可以移动的对象的原始纯概念因为它不能再次引用(析构函数除外)。我喜欢专门的右值这个短语,而不是广义的右值lvalue,但缩写为prvalue的纯rvalue胜出(和可能是正确的)。因此,W的左腿是左值glvalue和右腿是prvalue和rvalue。顺便提一句每个值都是glvalue或prvalue,但不是两者都是。这将留下W:im;也就是说并且可以移动。我们真的没有任何指导我们为那些神秘的野兽取了个好名字。它们对使用(草案)标准文本的人员,但不太可能成为家喻户晓的名字。我们没有在命名来指导我们,所以我们选择了“x”作为中心,未知奇怪,只有专家,甚至是x级。