什么是右值,左值,x值,glvalues和prvalues?

Jam*_*lis 1291 c++ expression c++-faq c++11

在C++ 03中,表达式是rvaluelvalue.

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

  1. 右值
  2. 左值
  3. x值
  4. glvalue
  5. prvalue

两类已成为五大类.

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

Kor*_*icz 602

我想这篇文章可能不是那么简短的介绍:n3055

整个大屠杀始于移动语义.一旦我们有可以移动而不是复制的表达式,突然很容易掌握规则要求区分可以移动的表达式,以及在哪个方向上.

根据我的猜测,基于草案,r/l值的区别保持不变,只有在移动的东西变得混乱的情况下.

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

引用n3055:

  • 一个左值(所谓的,历史上,因为左值可以出现在赋值表达式的左侧)表示一函数或一个对象. [示例:如果E是指针类型的表达式,则*E 是一个左值表达式,引用要指向的对象或函数E .另一个例子,调用返回类型为左值引用的函数的结果是左值.
  • 一个x值(一个"到期"值)也指一个对象,通常接近其寿命的末尾(使得其资源可以被移动,例如).xvalue是涉及rvalue引用的某些表达式的结果. [示例:调用返回类型为右值引用的函数的结果是xvalue.]
  • glvalue ("广义"左值)是左值x值.
  • 右值(所谓的,历史上,因为右值可以出现在赋值表达式的右手侧)是x值,临时对象或子对象,或它们的不与一个对象相关联的值.
  • prvalue("纯"右值)是一个rvalue这不是一个x值. [示例:调用返回类型不是引用的函数的结果是prvalue]

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

  • 我使用这里的宏来测试各种表达式:http://stackoverflow.com/a/6114546/96963可能是他们误解了事情. (4认同)
  • 我的朋友们,我们已经正式发疯了。 (3认同)
  • 添加 xvalue 并不是为了移动语义。只有同时使用左值和右值,移动语义、完美转发和右值引用仍然可以正常工作。我认为 xvalue 仅适用于 decltype 运算符:如果操作数表达式是 xvalue,则 decltype 给出右值引用的类型。 (2认同)

dir*_*tly 327

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

FCD(n3092)具有优异的描述:

- 左值(所谓的历史,因为左值可能出现在赋值表达式的左侧)指定一个函数或一个对象.[示例:如果E是指针类型的表达式,则*E是指向E指向的对象或函数的左值表达式.另一个例子,调用返回类型为左值引用的函数的结果是左值. - 末端的例子]

- xvalue("eXpiring"值)也指对象,通常接近其生命周期的末尾(例如,可以移动其资源).xvalue是涉及rvalue引用的某些表达式的结果(8.3.2).[示例:调用返回类型为右值引用的函数的结果是xvalue. - 末端的例子]

- glvalue("广义"左值)是左值或x值.

- rvalue(历史上所谓的,因为rvalues可能出现在赋值表达式的右侧)是xvalue,临时对象(12.2)或其子对象,或者是与对象无关的值.

- prvalue("纯"rvalue)是一个不是xvalue的rvalue.[示例:调用返回类型不是引用的函数的结果是prvalue.诸如12,7.3e5或true之类的文字的值也是prvalue. - 末端的例子]

每个表达式都属于此分类法中的基本分类之一:lvalue,xvalue或prvalue.表达式的此属性称为其值类别.[注意:第5章中对每个内置运算符的讨论表明了它产生的值的类别以及它所期望的操作数的值类别.例如,内置赋值运算符期望左操作数是左值,右操作数是prvalue并产生左值作为结果.用户定义的运算符是函数,它们期望和产生的值的类别由它们的参数和返回类型决定. - 注意

我建议你阅读3.10 Lvalues和rvalues的整个部分.

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

再次:

分类

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

rvalues的语义特别随着移动语义的引入而发展.

为什么需要这些新类别?

因此可以定义和支持移动构造/分配.

  • 我喜欢这里的图表.我认为用*开始答案可能是有用的."每个表达式都属于这个分类中的基本分类之一:lvalue,xvalue或prvalue."*然后很容易使用图表来显示这三个基本类是结合使glvalue和rvalue. (49认同)
  • “is glvalue”相当于“is not prvalue”,“is rvalue”相当于“is not lvalue”。 (4认同)
  • @AaronMcDaid嗨,如果你/有人可以回答的话,快速提问...为什么不将`glvalue`命名为`lvalue`并将`lvalue`命名为`plvalue`,以保持一致? (3认同)
  • @VijayChavda 这种命名的好处之一是,每个表达式仍然是左值或右值,但不能同时是两者。如果将泛左值命名为左值,则会中断。在之前的一个回答中已经解释过。 (3认同)
  • 这对我的帮助最大:https://bajamircea.github.io/assets/2016-04-07-move-forward/value-categories.png(价值类别的芬恩图) (2认同)

sel*_*tze 177

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

为什么需要这些新类别?

C++标准包含许多处理表达式的值类别的规则.一些规则区分左值和右值.例如,当涉及到重载决策时.其他规则区分glvalue和prvalue.例如,您可以使用不完整或抽象类型的glvalue,但没有不完整或抽象类型的prvalue.在我们使用这个术语之前,实际需要区分glvalue/prvalue的规则是指lvalue/rvalue和它们无意中是错误的还是包含了很多解释和例外的规则......"除非rvalue是由于未命名右值参考......".因此,将glvalues和prvalues的概念作为自己的名称似乎是一个好主意.

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

我们仍然有与C++ 98兼容的术语lvalue和rvalue.我们只是将rvalues分成两个子组,xvalues和prvalues,我们将lvalues和xvalues称为glvalues.Xvalues是未命名的右值引用的一种新值类别.每个表达式都是以下三个中的一个:左值,右值,右值.维恩图看起来像这样:

    ______ ______
   /      X      \
  /      / \      \
 |   l  | x |  pr  |
  \      \ /      /
   \______X______/
       gl    r
Run Code Online (Sandbox Code Playgroud)

功能示例:

int   prvalue();
int&  lvalue();
int&& xvalue();
Run Code Online (Sandbox Code Playgroud)

但也不要忘记命名的右值引用是左值:

void foo(int&& t) {
  // t is initialized with an rvalue expression
  // but is actually an lvalue expression itself
}
Run Code Online (Sandbox Code Playgroud)


Nic*_*las 155

为什么需要这些新类别?WG21众神只是试图让我们迷惑凡人吗?

我觉得其他答案(尽管其中很多都很好)并没有真正抓住这个特定问题的答案.是的,这些类别等存在允许移动语义,但复杂性存在的原因之一.这是在C++ 11中移动东西的一个不可侵犯的规则:

只有在毫无疑问安全的情况下才会移动.

这就是为什么存在这些类别的原因:能够谈论可以安全地离开它们的价值观,并谈论不存在的价值观.

在最早版本的r值参考中,运动很容易发生.容易了.当用户并不真正想要的时候,很容易隐藏移动的东西.

以下是移动物品安全的情况:

  1. 当它是临时或子对象时.(prvalue)
  2. 当用户明确表示要移动它时.

如果你这样做:

SomeType &&Func() { ... }

SomeType &&val = Func();
SomeType otherVal{val};
Run Code Online (Sandbox Code Playgroud)

这是做什么的?在规范的旧版本中,在5个值进入之前,这将引发一个移动.当然可以.您将rvalue引用传递给构造函数,因此它绑定到采用右值引用的构造函数.这很明显.

这只有一个问题; 你没有要求移动它.哦,你可能会说&&应该是一个线索,但这并没有改变它违反规则的事实.val不是暂时的,因为临时工没有名字.你可能延长了临时工作的寿命,但这意味着它不是暂时的 ; 它就像任何其他堆栈变量一样.

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

显而易见的解决方案是制作val一个左值.这意味着你无法从中移动.好的; 它被命名,所以它是一个左值.

一旦你这样做,你再也不能说这SomeType&&意味着同样的事情.您现在已经区分了命名的右值引用和未命名的右值引用.那么,命名的右值引用是左值; 这是我们上面的解决方案.那么我们称之为未命名的右值引用(Func上面的返回值)?

它不是左值,因为你无法从左值移动.我们需要能够通过返回来移动&&; 你怎么能明确地说要移动一些东西?std::move毕竟,这就是回归.它不是rvalue(旧式),因为它可以在等式的左侧(事情实际上有点复杂,请参阅下面的问题和评论).它既不是左值也不是左值; 这是一种新事物.

我们拥有的是一个值,您可以将其视为左值,它可以隐式移动.我们称之为xvalue.

请注意,xvalues使我们获得另外两类值:

  • prvalue实际上只是前一种rvalue的新名称,即它们是不是 xvalues的rvalues.

  • Glvalues是一组中xvalues和lvalues的并集,因为它们共享许多属性.

实际上,这一切都归结为xvalues以及将运动限制在精确且仅限于某些地方的需要.这些地方由右值类别定义; prvalues是隐式移动,xvalues是显式移动(std::move返回xvalue).

  • @Thomas:这是一个例子; 它如何创建返回值并不重要.重要的是它返回一个`&&`. (10认同)

Iva*_*ush 128

恕我直言,关于其含义的最佳解释给了我们Stroustrup +考虑到DánielSándorMohan的例子:

斯特劳斯:

现在我非常担心.显然,我们正陷入僵局或混乱或两者兼而有之.我花了午餐时间进行分析,看看哪些属性(值)是独立的.只有两个独立的属性:

  • has identity - 即地址,指针,用户可以确定两个副本是否相同,等等.
  • can be moved from - 即我们被允许在一些不确定但有效的状态下留下"副本"的来源

这导致我得出的结论是,正好有三种值(使用正则表达式使用大写字母来表示否定 - 我匆忙):

  • iM:有身份,不能移动
  • im:具有身份并可以移动(例如,将左值转换为右值参考的结果)
  • Im:没有身份,可以从中移除.

    第四种可能性,IM(没有身份,不能移动C++)在任何其他语言中(或者,我认为)都没用.

除了这三个基本的值分类之外,我们还有两个明显的概括,它们对应于两个独立的属性:

  • i:有身份
  • m:可以搬走

这导致我把这个图放在板上: 在此输入图像描述

命名

我观察到我们只有有限的自由命名:左边的两个点(标记为iMi)是那些或多或少有形式的人所说的lvalues,而右边的两个点(标记为 mIm)是具有或多或少形式的人打来电话rvalues.这必须反映在我们的命名中.也就是说,左边的"leg" W应该有与之相关的名称,lvalue而右边的"leg" W应该有与rvalue.我相关的名称,注意这个整个讨论/问题来自引入rvalue引用和移动语义.这些概念根本就不在斯特雷奇的世界组成的只是存在rvalueslvalues.有人观察到这个想法

  • 每一个value都是一个lvalue或一个rvalue
  • 一个lvalue不是一个rvaluervalue不是一个lvalue

深深植根于我们的意识,非常有用的属性,这种二分法的痕迹可以在标准草案中找到.我们都同意我们应该保留这些属性(并使它们精确).这进一步限制了我们的命名选择.我观察到标准库的措辞用于rvalue表示m(概括),以便保留标准库的期望和文本,W应该命名的 右手底点rvalue.

这引发了对命名的集中讨论.首先,我们需要决定lvalue.应该lvalue是什么意思iM还是概括i?在Doug Gregor的带领下,我们在核心语言的措辞中列出了这个词的位置,这个词lvalue在哪个词中被认为是一个或另一个.列表已经制定,在大多数情况下,lvalue目前的意思是最棘手/最脆弱的文本 iM.这是左值的经典含义,因为"在过去"没有任何东西被移动; move是一个新的概念C++0x.另外,命名的topleft点W lvalue给我们的属性,每个值都是一个lvalue或一个rvalue,但不是两个.

因此,Wis lvaluervalue.左下角的左上角是什么使得左下角和右上角分?左下角是经典左值的推广,允许移动.所以它是generalized lvalue.我们命名它 glvalue.你可以狡辩关于缩写,但(我认为)不是逻辑.我们认为严重使用generalized lvalue 会以某种方式缩写,所以我们最好立即做(或冒险混淆).W的右上角不如右下角(现在,一如既往地称为rvalue).这一点代表了你可以移动的对象的原始纯概念,因为它不能再被引用(除了析构函数).我喜欢这个短语specialized rvaluegeneralized lvalue不是pure rvalue缩写为prvalue胜利(并且可能是正确的).因此,W的左腿lvalueglvalue和右腿prvaluervalue.顺便说一句,每个值可以是一个glvalue或prvalue,但不能同时使用.

这留下的顶部中间W:im; 也就是说,具有身份且可以移动的值.我们真的没有任何东西可以引导我们为那些神秘的野兽命名.它们对使用(草案)标准文本的人很重要,但不太可能成为家喻户晓的名字.我们没有找到任何真正的命名约束来指导我们,所以我们选择'x'代表中心,未知,奇怪,仅xpert,甚至是x-rated.

史蒂夫炫耀最终产品

  • 是的,如果你想了解它们的含义,最好阅读C++ comitee的原始提案和讨论,而不是标准:D (13认同)
  • 文字没有身份,也无法移动; 它们仍然有用. (7认同)
  • @DrPizza根据标准:字符串文字是`lvalue`s,所有其他文字都是'prvalue`s.严格地说,你可以提出一个论据,说非字符串文字应该是不可移动的,但这不是标准的编写方式. (5认同)

Dán*_*dor 47

介绍

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

  • 右值参考
  • xvalue,glvalue,prvalue表达式值类别
  • 移动语义

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

int& r_i=7; // compile error
int&& rr_i=7; // OK
Run Code Online (Sandbox Code Playgroud)

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

3.10左值和右值[basic.lval]

1表达式根据图1中的分类法进行分类.

  • 左值(历史上所谓的左值,因为左值可能出现在赋值表达式的左侧)指定一个函数或一个对象.[示例:如果E是指针类型的表达式,则*E是指向E指向的对象或函数的左值表达式.另一个例子,调用返回类型为左值引用的函数的结果是左值. - 末端的例子]
  • xvalue("eXpiring"值)也指对象,通常接近其生命周期的末尾(例如,可以移动其资源).xvalue是涉及rvalue引用的某些表达式的结果(8.3.2).[示例:调用返回类型为右值引用的函数的结果是xvalue. - 末端的例子]
  • glvalue("广义"左值)是左值或x值.
  • rvalue(历史上所谓的,因为rvalues可能出现在赋值表达式的右侧)是xvalue,
    临时对象(12.2)或其子对象,或者是
    与对象无关的值.
  • prvalue("纯"rvalue)是一个不是xvalue的rvalue.[示例:调用返回类型不是
    引用的函数的结果是prvalue.诸如12,7.3e5或
    true 之类的文字的值也是prvalue. - 末端的例子]

每个表达式都属于此分类法中的基本分类之一:lvalue,xvalue或prvalue.表达式的此属性称为其值类别.

但我不太清楚这一小节足以清楚地理解这些概念,因为"通常"并不是一般的,"接近其生命的终点"并不是真正具体的,"涉及右值引用"并不是很清楚,和"示例:调用返回类型为右值引用的函数的结果是xvalue." 听起来像一条蛇咬着它的尾巴.

主要价值类别

每个表达式都属于一个主要值类别.这些值类别是左值,右值和右值类别.

左值

当且仅当E指的是ALREADY具有使其可在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;
}
Run Code Online (Sandbox Code Playgroud)

xvalues

表达式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;
}
Run Code Online (Sandbox Code Playgroud)

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

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;
}
Run Code Online (Sandbox Code Playgroud)

- 一个类成员访问表达式,指定非引用类型的非静态数据成员,其中对象表达式是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;
}
Run Code Online (Sandbox Code Playgroud)

- 指向成员的指针表达式,其中第一个操作数是xvalue,第二个操作数是指向数据成员的指针.

请注意,上述规则的效果是对对象的命名rvalue引用被视为lvalues,对对象的未命名rvalue引用被视为xvalues; 对函数的右值引用被视为左值,无论是否命名.

#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;
}
Run Code Online (Sandbox Code Playgroud)

prvalues

当且仅当E既不属于左值也不属于xvalue类别时,表达式E属于prvalue类别.

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;
}
Run Code Online (Sandbox Code Playgroud)

混合价值类别

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

右值

当且仅当E属于xvalue类别或属于prvalue类别时,表达式E属于rvalue类别.

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

glvalues

当且仅当E属于左值类别或xvalue类别时,表达式E属于glvalue类别.

一个实用的规则

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

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

  • lvalues的所有示例和xvalues的所有示例都是glvalues的示例.谢谢你的编辑! (3认同)
  • 很难理解 `struct As{void f(){this;}}`,`this` 变量是一个纯右值。我认为`this`应该是一个左值。直到标准 9.3.2 说:在非静态(9.3)成员函数的主体中,关键字 this 是一个纯右值表达式。 (2认同)
  • @ r0ng`his`是一个prvalue,但`*this`是一个左值 (2认同)

Joh*_*itb 34

C++ 03的类别太受限制,无法正确地将rvalue引用引入表达式属性.

随着它们的引入,据说一个未命名的右值引用求值为一个rvalue,这样重载解析更喜欢rvalue引用绑定,这将使它选择移动构造函数而不是复制构造函数.但是发现这会导致问题,例如动态类型和资格.

为了表明这一点,请考虑

int const&& f();

int main() {
  int &&i = f(); // disgusting!
}
Run Code Online (Sandbox Code Playgroud)

在pre-xvalue草案中,允许这样做,因为在C++ 03中,非类类型的rvalues永远不会被cv限定.但其意图是const适用于右值引用的情况下,因为在这里我们参考对象(=内存!),并从非类右值下降常量主要是针对周围没有对象的原因.

动态类型的问题具有类似的性质.在C++ 03中,类类型的rvalues具有已知的动态类型 - 它是该表达式的静态类型.因为要以另一种方式,你需要引用或解引用,它们评估为左值.对于未命名的右值引用,情况并非如此,但它们可以显示多态行为.所以要解决它,

  • 未命名的右值引用变为xvalues.它们可以是合格的,并且可能具有不同的动态类型.它们像预期的那样在重载期间更喜欢rvalue引用,并且不会绑定到非const左值引用.

  • 先前的rvalue(文字,由非强制转换为非引用类型创建的对象)现在变为prvalue.它们在重载期间具有与xvalues相同的优先级.

  • 先前的左值是一个左值.

并且完成了两个分组来捕获那些可以被限定并且可以具有不同动态类型(glvalues)的分组以及那些重载优先于rvalue引用绑定(rvalues)的分组.


Fel*_*bek 25

我很长时间都在努力,直到我遇到cppreference.com对价值类别的解释.

它实际上相当简单,但我发现它通常以难以记忆的方式解释.这里非常示意性地解释.我将引用页面的某些部分:

主要类别

主要值类别对应于表达式的两个属性:

  • 具有同一性:可以确定表达式是否与另一个表达式引用相同的实体,例如通过比较对象的地址或它们识别的函数(直接或间接获得);

  • 可以移动:移动构造函数,移动赋值运算符,或实现移动语义的另一个函数重载可以绑定到表达式.

表达:

  • 有身份,不能被移动称为左值表达式 ;
  • 有身份,可以从被称为xvalue表达式移动 ;
  • 没有身份并且可以被移动称为prvalue表达式 ;
  • 没有身份,不能被移动不被使用.

左值

左值("左值")表达式是具有标识无法移动的表达式.

rvalue(直到C++ 11),prvalue(自C++ 11起)

prvalue("纯rvalue")表达式是一个没有标识可以从中移出的表达式.

x值

xvalue("expiring value")表达式是一个具有标识可以从中移出的表达式.

glvalue

glvalue("generalized lvalue")表达式是一个左值或x值的表达式.它有身份.它可能会或可能不会被移动.

rvalue(自C++ 11起)

rvalue("right value")表达式是prvalue或xvalue的表达式.它可以从.它可能有也可能没有身份.

  • 在某些书籍中,xvalues 显示其 x 来自“专家”或“例外” (2认同)

fre*_*low 17

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

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


Moh*_*han 14

上面的优秀答案的一个附录,即使在我阅读了Stroustrup并且认为我理解rvalue/lvalue区别之后,我也很困惑.当你看到

int&& a = 3,

读取int&&作为一个类型并得出结论a是非常诱人的rvalue.不是:

int&& a = 3;
int&& c = a; //error: cannot bind 'int' lvalue to 'int&&'
int& b = a; //compiles
Run Code Online (Sandbox Code Playgroud)

a有一个名字,并且事实上是一个左值.不要把它&&视为那种类型的一部分a; 它只是告诉你什么a是允许绑定到的东西.

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

Foo::Foo(T&& _t) : t{_t} {}

你会复制_tt.你需要

Foo::Foo(T&& _t) : t{std::move(_t)} {}如果你想搬家 当我遗漏时,我的编译器会警告我move!


0xF*_*0xF 12

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

我发现很难理解这些术语的精确定义、长长的规则列表或这张流行的图表:

值类别传统图

使用典型示例在维恩图上更容易:

值类别维恩图

基本上:

  • 每个表达式要么是左值要么是右值
  • lvalue 必须复制,因为它有标识,所以以后可以使用
  • 右值可以移动,因为它是临时的(纯右值)或显式移动的(xvalue)

现在,好问题是,如果我们有两个正交属性(“具有身份”和“可以移动”),那么完成左值、x 值和右值的第四个类别是什么?那将是一个没有身份(因此以后无法访问)且无法移动(需要复制其值)的表达式。这根本没有用,所以没有被命名。

  • 这是非常清楚的,也是我见过的唯一明确的答案。是的,我的经历与你所描述的完全相符;特别是,流行的图对于了解正在发生的事情没有帮助,因为除非人们已经理解了这些关系并且本质上已经在头脑中掌握了维恩图,否则它是没有意义的。 (4认同)

Dan*_*ell 12

这是我为我正在编写的一本高度可视化的 C++ 书籍制作的维恩图,我将很快在开发过程中在leanpub 上发布该书。

显示 C++ 中值类别之间关系的维恩图

其他答案用文字进行了更详细的描述,并显示了类似的图表。但希望这些信息的介绍相当完整,并且可供参考。

我在这个主题上的主要收获是表达式具有这两个属性:恒等性可移动性。第一个涉及事物存在的“坚固性”。这很重要,因为 C++ 抽象机被允许并鼓励通过优化积极地改变和缩小代码,这意味着没有身份的东西可能只存在于编译器的头脑中或在寄存器中存在一小会儿,然后就被践踏了在。但是,如果您回收它的内部结构,也保证不会导致问题,因为没有办法尝试使用它。因此,移动语义的发明是为了让我们能够捕获对临时变量的引用,将它们升级为左值并延长它们的生命周期。

移动语义最初不仅仅是浪费地丢弃临时数据,而是将它们放弃,以便它们可以被另一个人使用。

当您想吃掉朋友剩下的玉米面包时,以免浪费。

当你把玉米面包送给别人时,你送给它的人现在就拥有了它。他们消费它。一旦你把它送给别人,你就不应该试图吃或消化所说的玉米面包。也许玉米面包无论如何都会被扔进垃圾桶,但现在它会被送进他们的肚子。它不再是你的了。

在 C++ 领域,“消耗”资源的想法意味着资源现在由我们拥有,因此我们应该进行任何必要的清理,并确保该对象不会在其他地方访问。很多时候,这意味着借用勇气来创造新的物体。我称之为“器官捐献”。通常,我们谈论的是对象包含的指针或引用,或者类似的东西,并且我们希望保留这些指针或引用,因为它们引用了程序中其他地方不会消失的数据。

因此,您可以编写一个采用右值引用的函数重载,如果传入临时值(右值),则这就是将被调用的重载。绑定到函数所采用的右值引用时将创建一个新的左值,从而延长临时变量的生命周期,以便您可以在函数中使用它。

在某些时候,我们意识到我们经常有左值非临时数据,我们在一个作用域中完成了这些数据,但想要在另一个作用域中进行蚕食。但它们不是右值,因此不会绑定到右值引用。所以我们制作了std::move,这只是从左值到右值引用的奇特转换。这样的数据是一个 xvalue:以前的左值现在充当临时值,因此也可以从中移动。

  • 不幸的是,这会破坏这个图形的要点,即我在(有点)知道它的*年*里努力记住和处理这些信息,因为我的大脑需要一个图表而不是一堵文字墙来挂住。但你是对的,如果这是主要答案,它应该更加以文本为导向。我希望这个答案没问题。只是希望能帮助别人。 (2认同)

小智 5

由于前面的答案详尽地涵盖了价值类别背后的理论,因此我想补充一件事:您可以实际使用它并进行测试。

对于一些使用值类别的动手实验,您可以使用decltype说明符。它的行为明确区分了三个主要值类别(xvalue,lvalue和prvalue)。

使用预处理程序可以节省一些键入时间...

主要类别:

#define IS_XVALUE(X) std::is_rvalue_reference<decltype((X))>::value
#define IS_LVALUE(X) std::is_lvalue_reference<decltype((X))>::value
#define IS_PRVALUE(X) !std::is_reference<decltype((X))>::value
Run Code Online (Sandbox Code Playgroud)

混合类别:

#define IS_GLVALUE(X) IS_LVALUE(X) || IS_XVALUE(X)
#define IS_RVALUE(X) IS_PRVALUE(X) || IS_XVALUE(X)
Run Code Online (Sandbox Code Playgroud)

现在,我们可以(几乎)从cppreference的value category上复制所有示例。

以下是C ++ 17的一些示例(用于简短的static_assert):

void doesNothing(){}
struct S
{
    int x{0};
};
int x = 1;
int y = 2;
S s;

static_assert(IS_LVALUE(x));
static_assert(IS_LVALUE(x+=y));
static_assert(IS_LVALUE("Hello world!"));
static_assert(IS_LVALUE(++x));

static_assert(IS_PRVALUE(1));
static_assert(IS_PRVALUE(x++));
static_assert(IS_PRVALUE(static_cast<double>(x)));
static_assert(IS_PRVALUE(std::string{}));
static_assert(IS_PRVALUE(throw std::exception()));
static_assert(IS_PRVALUE(doesNothing()));

static_assert(IS_XVALUE(std::move(s)));
// The next one doesn't work in gcc 8.2 but in gcc(trunk). Clang 7.0.0 and msvc 19.16 are doing fine.
static_assert(IS_XVALUE(S().x)); 
Run Code Online (Sandbox Code Playgroud)

一旦弄清了主要类别,混合类别就很无聊了。

有关更多示例(和实验),请查看以下有关编译器资源管理器的链接。不过,不要打扰阅读程序集。我添加了很多编译器,只是为了确保它可以在所有常见的编译器中正常工作。

  • 我认为`#define IS_GLVALUE(X) IS_LVALUE(X) || IS_XVALUE(X)` 实际上应该是 `#define IS_GLVALUE(X) (IS_LVALUE(X) || IS_XVALUE(X))` 否则看看如果 `&amp;&amp;` 两个 `IS_GLVALUE` 会发生什么。 (2认同)

归档时间:

查看次数:

166198 次

最近记录:

6 年,6 月 前