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

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

右值左值x值glvalue值prvalue值

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

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


当前回答

上面优秀答案的一个补充,即使在我读过Stroustrup并认为我理解了右值/左值的区别之后,这一点也让我感到困惑。当你看到

int&&a=3,

将int&&作为一种类型阅读,并得出a是一个右值的结论是非常诱人的。它不是:

int&& a = 3;
int&& c = a; //error: cannot bind 'int' lvalue to 'int&&'
int& b = a; //compiles

a有一个名字,实际上是一个左值。不要将&&视为a;类型的一部分;它只是告诉你什么是允许绑定的。

这对构造函数中的T&&类型参数尤为重要。如果你写

Foo::Foo(T&&_T):T{_T}{}

你将把t复制到t中。你需要

Foo::Foo(T&&_T):T{std::move(_T)}{}如果您想移动。如果我忽略了移动,编译器会警告我吗!

其他回答

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

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

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

基本上:

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

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

我想这份文件可以作为一个不那么简短的介绍:n3055

整个屠杀从移动语义开始。一旦我们有了可以移动而不可复制的表达式,突然间,容易掌握的规则要求区分可以移动的表达式和方向。

根据我根据草案的猜测,r/l值的区别保持不变,只有在移动的情况下才会变得混乱。

他们需要吗?如果我们想放弃新功能,可能不会。但为了实现更好的优化,我们可能应该接受它们。

引用n3055:

左值(历史上,因为lvalues可能出现在作业的左侧表达式)指定函数或一个物体。[示例:如果E是指针类型的表达式,则为*E是一个左值表达式,引用E所针对的对象或功能点。作为另一示例调用函数的结果返回类型是左值引用左值。]x值(An“eXpiring”值)也指对象,通常在其末端附近生命周期(以便其资源可以例如被移动)。x值为某些类型的结果涉及右值的表达式参考文献。[示例:调用函数的结果返回类型为右值引用为x值。]glvalue(“广义”左值)是左值或x值。右值(所谓,历史上,因为rvalues可以显示在赋值表达式)是xvalue,临时对象或子对象,或其值不与对象关联。A.prvalue(“纯”右值)是右值这不是xvalue。[示例:调用函数的结果返回类型不是引用是prvalue(压力值)]

所讨论的文件是这个问题的一个很好的参考,因为它显示了由于引入新的命名法,标准发生的确切变化。

引言

ISOC++11(正式的ISO/IEC 14882:2011)是C++编程语言标准的最新版本。它包含一些新功能和概念,例如:

右值引用xvalue、glvalue、prvalue表达式值类别移动语义

如果我们想理解新表达式值类别的概念,我们必须知道有右值和左值引用。最好知道右值可以传递给非常值右值引用。

int& r_i=7; // compile error
int&& rr_i=7; // OK

如果我们引用N3337工作草案(与已发布的ISOC++11标准最相似的草案)中标题为Lvalues和rvalues的小节,我们可以获得价值类别概念的一些直觉。

3.10 L值和R值[基本值]1表达式根据图1中的分类法进行分类。左值(历史上称为左值,因为左值可能出现在赋值表达式的左侧)表示函数或物体。[示例:如果E是指针类型的表达式,则*E是指E指向的对象或函数的左值表达式。作为另一个示例,调用函数的结果其返回类型为左值引用的值为左值-结束示例]xvalue(“eXpiring”值)也指的是一个对象,通常在其生命周期结束时(这样它的资源就可以移动,例如示例)。xvalue是某些类型表达式的结果涉及右值引用(8.3.2)。[示例:调用返回类型为右值引用的函数为xvalue-终止示例]glvalue(“广义”左值)是左值或x值。右值(历史上称为,因为右值可能出现在赋值表达式的右侧)是一个xvalue,一个临时对象(12.2)或其子对象,或不是与对象关联。prvalue(“纯”右值)是一个不是xvalue的右值。[示例:调用返回类型不是引用是prvalue。文字的值,例如12、7.3e5或true也是prvalue-结束示例]每个表达式都属于此分类法中的分类:lvalue、xvalue或prvalue。这表达式的属性称为其值类别。

但我不太确定这一小节是否足以清楚地理解这些概念,因为“通常”不是很一般,“接近其生命周期结束时”不是很具体,“涉及右值引用”也不是很清楚,“示例:调用返回类型为右值引用的函数的结果是一个xvalue”听起来像蛇在咬尾巴。

主要价值类别

每个表达式都属于一个主值类别。这些值类别是lvalue、xvalue和prvalue类别。

左值

表达式E属于左值类别,当且仅当E指的是一个实体,该实体的身份(地址、名称或别名)使其可以在E之外访问。

#include <iostream>

int i=7;

const int& f(){
    return i;
}

int main()
{
    std::cout<<&"www"<<std::endl; // The expression "www" in this row is an lvalue expression, because string literals are arrays and every array has an address.  

    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;
}

x值

表达式E属于xvalue类别,当且仅当它是

-调用函数(无论是隐式还是显式)的结果,该函数的返回类型是对所返回对象类型的右值引用,或

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;
}

-对对象类型的右值引用的强制转换,或

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;
}

-指定非引用类型的非静态数据成员的类成员访问表达式,其中对象表达式是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;
}

-指向成员的指针表达式,其中第一个操作数是一个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;
}

pr值

表达式E属于prvalue类别,当且仅当E既不属于lvalue类别,也不属于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;
}

混合价值类别

还有两个重要的混合价值类别。这些值类别是rvalue和glvalue类别。

右值

表达式E属于右值类别,当且仅当E属于xvalue类别或prvalue类别。

请注意,此定义意味着表达式E属于右值类别,当且仅当E指的是一个实体,该实体没有任何使其在E YET之外可访问的标识。

gl值

表达式E属于glvalue类别,当且仅当E属于lvalue类别或xvalue类别。

实用规则

Scott Meyer发表了一个非常有用的经验法则来区分右值和左值。

如果可以获取表达式的地址,则该表达式是左值。如果表达式的类型是左值引用(例如,T&或常量T&等),则该表达式是左值。否则,表达式为右值。从概念上(通常也是事实上),右值对应于临时对象,例如作为从函数返回的或通过隐式类型创建的转换。大多数文字值(例如10和5.3)也是右值。

这些新的表达类别是什么?

FCD(n3092)具有出色的描述:

-左值(历史上称为左值,因为左值可能出现在作业的左侧表达式)指定函数或一个物体。[示例:如果E是指针类型的表达式,则*E是一个左值表达式,表示E所指向的对象或函数点。作为另一个例子,结果调用其返回的函数类型是左值引用是左值-结束示例]-x值(An“eXpiring”值)也指对象,通常在其末端附近生命周期(以便其资源例如移动)。xvalue是某些类型表达式的结果涉及右值参考(8.3.2)[示例:调用返回类型为rvalue引用是一个xvalue-终止示例]-glvalue(“广义”左值)是左值或x值。—右值(历史上称为,因为右值可能出现在作业的右侧表达式)是一个xvalue,一个临时对象(12.2)或其子对象,或与对象-prvalue(“纯”rvalue)为不是xvalue的右值。[示例:调用返回类型不是引用是prvalue。a的值文字,如12、7.3e5或true也是prvalue-结束示例]每一个表达式正好属于中的基本分类此分类法:lvalue、xvalue或prvalue。一个表达式称为其值类别[注:讨论第5条中的每个内置运算符指示其值的类别收益率和价值类别它需要的操作数。例如内置赋值运算符左操作数是左值,并且右操作数是prvalue并作为结果产生左值。用户定义的运算符是函数,以及他们的价值类别预期和收益由以下因素决定它们的参数和返回类型-终止笔记

我建议您阅读整个第3.10节L值和右值。

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

再一次:

C++0x中的右值和左值类别是否与C++03中的相同?

右值的语义随着移动语义的引入而不断发展。

为什么需要这些新类别?

以便定义和支持移动构造/分配。

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

为什么需要这些新类别?

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
}