什么是右值、左值、x值、gl值和pr值?

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

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

  1. 右值
  2. 左值
  3. xvalue
  4. glvalue
  5. 纯右值

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

  • 这些新的表达方式有哪些?
  • 这些新类别如何与现有的右值和左值类别相关联?
  • C++0x中的右值和左值类别是否与C++03相同?
  • 为什么需要这些新类别?WG21神只是想混淆我们只是凡人吗?
230502 次浏览

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

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

从我基于草稿的猜测来看,r/l值的区别保持不变,只有在移动的情况下才会变得混乱。

是否需要它们?如果我们希望放弃新功能,可能不会。但是为了更好地优化,我们可能应该接受它们。

引用n3055

  • An左值(所谓的,历史上,因为左值可能出现在赋值的左边表达式)表示函数或一个对象。[示例:如果E是一个指针类型的表达式,然后*E是一个左值表达式E的对象或函数另一个例子是调用一个函数的结果返回类型是左值引用左值。]
  • Anxvalue(an“期望”值)也指对象,通常在其末尾附近生命周期(以便其资源可以被移动,例如)。xvalue是某些类型的结果涉及右值的表达式引用。[示例:The调用一个函数的结果返回类型是右值引用一个xvalue。]
  • glvalue(“广义”左值)是一个左值或者xvalue.
  • An右值(所谓的,从历史上看,因为右值可以出现在a的右侧赋值表达式)是一个xvalue,一个临时对象或子对象,或一个值不与对象关联。
  • A纯右值(“纯”右值)是右值这不是一个xvalue。[示例:The调用一个函数的结果返回类型不是引用是prvalue]

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

这些新的表达方式有哪些?

FCD(n3092)有一个很好的描述:

-左值(历史上称为左值,因为左值可能出现在赋值的左边表达式)表示函数或一个对象。[示例:如果E是一个指针类型的表达式,然后*E是一个左值表达式,引用对象或函数作为另一个例子,结果“期望”值)也指对象,通常在其末尾附近生命周期(以便其资源可能是例如,移动)。xvalue是某些类型表达式的结果涉及右值引用(8.3.2)。[示例:调用a的结果返回类型为右值引用是一个xvalue.-end示例]

-一个glvalue(“广义”)左值)是左值或xvalue。

-右值(历史上称为,因为右值可能出现在赋值的右边表达式)是一个xvalue,一个临时对象(12.2)或其子对象,或一个值不与一个对象。

-prvalue(纯右值)是不是xvalue的右值。[示例:调用a的结果返回类型不是a的函数引用是一个右值。a的值文字,如12、7.3e5或true是-结束示例]

每表达式正好属于中的基本分类此分类:左值、xvalue或prvalue。一个的这个属性表达式称为其值[注:讨论第5条中的每个内置运算符表示它的值的类别的收益和价值类别它期望的操作数。例如,内置赋值操作符左操作数是一个左值正确的操作数是一个右值并产生一个左值作为结果。用户定义的运算符是函数,以及它们的价值类别期望值和收益率由它们的参数和返回类型。-结束注释

我建议你阅读整个部分3.10左值和右值

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

再次:

分类法

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

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

为什么需要这些新类别?

因此,可以定义和支持移动构造/赋值。

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

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

C++03的类别限制太大,无法将右值引用正确引入表达式属性。

随着它们的引入,据说未命名的右值引用计算为右值,因此重载解析更喜欢右值引用绑定,这将使它选择移动构造函数而不是复制构造函数。但发现这会导致周围的问题,例如动态类型和限定。

为了证明这一点,考虑

int const&& f();
int main() {int &&i = f(); // disgusting!}

在前xvalue草稿上,这是允许的,因为在C++03中,非类类型的右值永远不会被cv限定。但它的目的是const适用于右值引用情况,因为这里我们引用对象(=内存!),从非类右值中删除const主要是因为周围没有对象。

动态类型的问题本质相似。在C++03中,类类型的右值有一个已知的动态类型——它是该表达式的静态类型。因为要拥有它的另一种方式,你需要引用或取消引用,它们评估为左值。未命名的右值引用不是这样,但它们可以显示多态行为。所以要解决它,

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

  • 以前是右值(字面量,由强制转换为非引用类型创建的对象)现在变成了prvalue。它们在重载期间与xvalue具有相同的首选项。

  • 以前的左值保持为左值。

并且进行了两个分组来捕获那些可以限定并且可以具有不同动态类型的分组(价值观)和重载更喜欢右值引用绑定的分组(右值)。

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

为什么需要这些新类别?

C++标准包含许多处理表达式的值类别的规则。一些规则区分左值和右值。例如,当涉及到重载分辨率时。其他规则区分glvalue和prvalue。例如,你可以有一个不完整或抽象类型的glvalue,但没有一个不完整或抽象类型的prvalue。在我们有这个术语之前,实际上需要区分glvalue/prvalue的规则指的是左值/右值,它们要么是无意中错误的,要么包含很多解释和规则的例外a la"…除非右值是由于未命名的右值引用…"。因此,这似乎是一个好主意,只是给他们自己的名字的概念,普罗值和普罗值。

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

我们仍然有与C++98兼容的术语左值和右值。我们只是将右值分为两个子组,x值和右值,并且我们将左值和x值称为泛值。X值是一种新的未命名右值引用的值类别。每个表达式都是这三个之一:左值,xvalue,右值。维恩图如下所示:

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

函数示例:

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值引用的最早版本中,移动很容易发生。也是很容易。很容易,当用户并不真正有意时,隐式移动事物的潜力很大。

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

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

如果你这样做:

SomeType &&Func() { ... }
SomeType &&val = Func();SomeType otherVal{val};

这是做什么的?在旧版本的规范中,在5个值进入之前,这会引发移动。当然可以。您将右值引用传递给构造函数,因此它绑定到接受右值引用的构造函数。这是显而易见的。

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

如果它不是临时的,并且您没有要求移动它,那么移动是错。

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

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

它不是左值,因为你不能从左值移动。第二,我们能够通过返回&&来移动;否则你怎么能明确地说移动某物?毕竟,这是std::move返回的。它不是右值(老式),因为它可以在等式的左侧(事情实际上有点复杂,见这个问题和下面的评论)。它既不是左值也不是右值;这是一种新的东西。

我们有一个可以作为左值的值,除了,它可以隐式移动。我们称之为xvalue。

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

  • 右值实际上只是先前类型的右值的新名称,即它们是不是x值的右值。

  • 极值是一组中x值和左值的联合,因为它们确实共享许多共同的属性。

所以实际上,这一切都归结为x值以及将移动限制在精确且仅限于特定位置的需要。这些位置由右值类别定义;右值是隐式移动,x值是显式移动(std::move返回一个x值)。

导言

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

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

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

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

如果我们引用工作草案N3337(与已发布的ISOC++11标准最相似的草案)中标题为L值和右值的小节,我们可以直观地了解价值类别的概念。

3.10左值和右值[basic.lval]

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

  • 左值(历史上称为左值,因为左值可能出现在赋值表达式的左侧)指定一个函数[示例:如果E是指针类型的表达式,则*E是指E指向的对象或函数的左值表达式。另一个例子,调用函数的结果返回类型为左值引用的是左值。
  • xvalue(一个“eXping”值)也指一个对象,通常接近其生命周期的结束(以便可以移动其资源,例如xvalue是某些类型的表达式的结果涉及右值引用(8.3.2)。[示例:调用的结果返回类型为右值引用的函数是xvalue示例]
  • glvalue(广义左值)是左值或xvalue。
  • 右值(历史上称为右值,因为右值可能出现在赋值表达式的右侧)是一个xvalue,一个
    临时对象(12.2)或其子对象,或不是
    的值关联到一个对象。
  • prvalue(“纯”右值)是不是xvalue的右值。[示例:调用返回类型不是
    的函数的结果引用是右值。文字的值,例如12、7.3e5或
    true也是一个右值。-结束示例]

每个表达式都属于一个基本的此分类中的分类:左值、xvalue或prvalue表达式的属性称为其值类别。

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

基本价值类别

每个表达式都属于一个主值类别。这些值类别是左值、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,第二个操作数是指向数据成员的指针。

请注意,上面规则的效果是,对象的命名右值引用被视为左值,对象的未命名右值引用被视为x值;函数的右值引用被视为左值,无论是否命名。

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

右值

表达式E属于右值类别当且仅当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;}

混合价值类别

还有另外两个重要的混合价值类别。这些价值类别是右值和高值类别。

右值

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

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

价值观

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

一个实用的规则

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

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

我为此挣扎了很长时间,直到我遇到了价值类别的cppreference.com解释。

它实际上相当简单,但我发现它经常以一种难以记忆的方式解释。这里解释得非常示意。我将引用页面的一些部分:

主要类别

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

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

  • 可以从:移动构造函数、移动赋值操作符或其他实现移动语义学的函数重载可以绑定到表达式。

表示:

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

左值

左值(“左值”)表达式是有身份不能从的表达式。

右值(直到C++11),右值(C++11)

prvalue(“纯右值”)表达式是没有身份可以从的表达式。

xvalue

xvalue(“过期值”)表达式是有身份可以从的表达式。

glvalue

glvalue("广义左值")表达式是一个左值或xvalue的表达式。它有身份。它可能被移动也可能不被移动。

右值(自C++11)

右值("right value")表达式是右值或xvalue的表达式。它可以从。它可能有也可能没有标识。

让我们把它放到一个表中:

可以从(=右值)移动不能从
具有身份(=glvalue)xvalue左值
没有身份纯右值未使用

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

Stroustrup:

现在我真的很担心。显然我们正走向僵局或一团糟或者两者兼而有之我用午餐时间做了个分析的属性是独立的。只有两个独立属性:

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

这使我得出结论,有三种类型值(使用使用大写字母的正则表达式符号技巧来表示否定-我很匆忙):

  • iM:具有身份并且不能从
  • im:具有标识并且可以从(例如,将左值转换为右值引用的结果)移动
  • Im:没有标识,可以从中移动。

    第四种可能,IM(没有标识,不能移动)不是C++(或者,我认为)在任何其他语言中都很有用。

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

  • i:有身份
  • m:可以从

这让我把这张图放在黑板上:输入图片描述

命名

我注意到我们只有有限的命名自由:左边(标记为iMi)是人们或多或少形式调用lvalues和右边的两个点(标记为mIm)是那些或多或少拘谨的人已经调用了rvalues。这必须反映在我们的命名中。也就是说,W的左“腿”应该有与lvalue相关的名称W的右“腿”应该有与rvalue.相关的名称,我注意到整个讨论/问题产生于引入右值引用和移动语义学这些概念根本不存在在斯特雷奇的世界里只有0号和1号注意到的想法

  • 每个value要么是lvalue要么是rvalue
  • lvalue不是rvaluervalue也不是lvalue

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

这导致了对命名的集中讨论。首先,我们需要决定lvalue应该意味着iM还是泛化i?领导由Doug Gregor撰写,我们列出了核心语言措辞中的位置其中lvalue这个词被限定为一个或另一个。A在大多数情况下,在最棘手/脆弱的文本中lvalue目前表示iM。这是左值的经典含义因为“在过去”没有移动任何东西;move是一个新颖的概念此外,命名Wlvalue的顶部点给了我们每个值都是lvaluervalue的属性,但不能两者兼而有之。

因此,W的左上角点是lvalue,右下角点是rvalue.左下角和右上角是什么?左下角是经典左值的推广,允许移动。所以它是一个generalized lvalue.我们命名它glvalue.你可以对缩写吹毛求疵,但(我认为)不是与逻辑。我们假设在认真使用generalized lvalue无论如何都会被缩写,所以我们最好这样做立即(或有混淆的危险)。一般比右下角(现在,一如既往,称为rvalue)。那点表示您可以移动的对象的原始纯概念因为它不能被再次引用(除了析构函数)。我喜欢specialized rvalue这个短语,而不是泛化左值pure rvalue缩写为prvalue胜出(和可能是正确的)。所以,W的左腿是lvalueglvalue,右腿是prvaluervalue.顺便说一句,每个值要么是glvalue,要么是prvalue,但不能两者兼而有之。

这留下了Wim的顶部中间;即具有身份并且可以移动。我们真的没有任何东西可以引导我们为那些深奥的野兽取个好名字。他们对使用(草案)标准文本的人,但不太可能成为家喻户晓的名字。我们没有发现任何真正的限制命名来指导我们,所以我们选择了'x'作为中心,未知,奇怪,只有xpert,甚至是x级。

史蒂夫展示最终产品

对上述优秀答案的补充,即使在我读过Stroustrup并认为我理解右值/左值的区别之后,我仍然感到困惑

int&& a = 3

很容易将int&&作为一个类型来读,并得出结论a是一个右值。

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

#0有一个名字,事实上是一个左值。不要认为&&a类型的一部分;它只是告诉你a可以绑定到什么。

这对于构造函数中的T&&类型参数尤其重要

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

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

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

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

对于值类别的一些实践实验,您可以使用dectype说明符。它的行为显式区分了三个主要值类别(xvalue、左值和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

混合类别:

#define IS_GLVALUE(X) (IS_LVALUE(X) || IS_XVALUE(X))#define IS_RVALUE(X) (IS_PRVALUE(X) || IS_XVALUE(X))

现在我们可以(几乎)重现价值类别偏好中的所有示例。

以下是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 9.1. Clang 7.0.0 and msvc 19.16 are doing fine.static_assert(IS_XVALUE(S().x));

一旦你弄清楚了主要类别,混合类别就有点无聊了。

有关更多示例(和实验),请查看以下编译器资源管理器上的链接。不过,不要费心阅读程序集。我添加了很多编译器只是为了确保它适用于所有常见的编译器。

这些是C++委员会在C++11.0中用来定义动作语义学的术语。

我发现很难理解这些术语的精确定义,一长串规则或这个流行的图表:

值类别传统图

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

值类别维恩图

基本上:

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

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

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

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

其他的答案用文字更详细,并显示类似的图表。但希望这种信息的呈现是相当完整的,对参考有用,此外。

我在这个话题上的主要收获是表达式有这两个属性:身份可移动性。第一个涉及某个事物存在的“稳固性”。这很重要,因为C++抽象机器被允许并鼓励通过优化激进地改变和缩小你的代码,这意味着没有身份的东西可能只在编译器的脑海中或寄存器中存在一会儿,然后被践踏。但是像这样的数据也保证了如果你回收它的内部不会引起问题,因为没有办法尝试使用它。因此,移动语义学的发明使我们能够捕捉到对临时的引用,将它们升级为左值并延长它们的寿命。

移动语义学最初不仅仅是浪费地扔掉临时物品,而是把它们送走,这样它们就可以被另一个人消费。

当你想吃掉朋友剩下的玉米面包时,它就不会浪费了。

当你把你的玉米面包送人时,你给的人现在拥有它。他们消费它。一旦你把它送人,你就不应该试图吃或消化所说的玉米面包。也许那个玉米面包本来是要扔到垃圾里的,但现在它是要扔到他们的肚子里的。它不再是你的了。

在C++土地上,“消耗”资源的想法意味着资源现在归我们所有,因此我们应该做任何必要的清理,并确保该对象不会被其他地方访问。通常,这意味着借用勇气来创建新对象。我称之为“捐赠器官”。通常,我们谈论的是对象包含的指针或引用,或类似的东西,我们希望保留这些指针或引用,因为它们引用了我们程序中其他地方没有死亡的数据。

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

在某个时候,我们意识到我们经常有左值的非临时数据,我们在一个范围内已经完成了,但想在另一个范围内拆用。但它们不是右值,所以不会绑定到右值引用。所以我们做了std::move,这只是从左值到右值引用的花哨转换。这样的数据是一个xvalue:以前的左值现在看起来像是临时的,所以它也可以从…