c - 什么是rvalues, lvalues, xvalues, glvalues, and prvalues?

  显示原文与译文双语对照的内容

在C++03中,一个表达式要么是一个右值或者一个左值

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

  1. 英镑
  2. xvalue
  3. glvalue
  4. prvalue

两个类别已经成为五种类别。

  • 这些新的表达式类别是什么?
  • 这些新类别如何与现有的右值和左值类别相关?
  • C++0x中的右值和左值类别与C++03中的相同?
  • 为什么需要这些新类别? WG21 神只是想让我们迷惑?
时间:

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

为什么需要这些新类别?

C++ 标准包含许多处理表达式值类别的规则。 一些规则在左值和右值之间做出了区别。 例如当过载解决时。 其他规则区分glvalue和 prvalue 。 例如你可以有一个不完整或者抽象类型的glvalue,但没有具有不完整或者抽象类型的prvalue 。 在我们有这个术语之前,需要区分 glvalue/prvalue的规则引用了左值/右值,它们要么无意中出错,要么包含大量解释和例外规则的规则。 因此,只需给出glvalues和prvalues的概念,它们自己的名字就好了。

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

我们仍然有与C++98兼容的术语左值和右值。 我们只将左值分成两个子组xvalues和 prvalues,我们将左值和xvalues称为 glvalues 。 Xvalues是一个新的值类别,用于未命名的右值引用。 每个表达式都是以下三个表达式之一: 左值,xvalue,prvalue 。一个维恩图如下所示:


 ______ ______
/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-value引用的最早版本中,移动很容易发生。 太容易 。轻松地足以让为隐式地移动在它们出现时确实有许多潜在的用户并没有真正的意思来。

下面是移动某些内容的安全条件:

  1. 当它是临时或子对象时。 ( prvalue )
  2. 当用户有明确地说移动它 。

如果你这样做:


SomeType &&Func() {.. . }

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

这是什么在规范的早期版本中,5值到来之前,这将引起一个移动。 当然可以。你传递了对构造函数的右值引用,因此它绑定到构造函数,该构造函数采用右值引用。 那很明显。

这里有一个问题,你没有 move来移动它。 哦,你可以说 && 应该是一个线索,但这并没有改变它打破了规则的事实。 val 不是临时的,因为它没有名称。 你可能已经延长了临时时间,但这意味着它不是临时,它就像任何其他堆栈变量一样。

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

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

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

这不是左值,因为你不能从左值移动。 并且我们需要移动通过返回一个 && ;否则你能怎样,以便能够明确地说要移动什么的? 这就是 std::move的回报,毕竟。 它不是一个右值( old-style ),因为它可以在等式的左边。 它既不是左值也不是右值;它是一种新事物。

在,没什么,就是一个值,你可以将它的看作左值,它将隐式 moveable. 除外, 我们称它为 xvalue

注意,xvalues使我们获得了其他两个类别的值:

  • 一个 prvalue 实际上只是的新名称前面的类型的,右值,它们是 换句话说,左值并不是 xvalues

  • glvalues xvalues和一个组中左值,因为它们的并集是做共享大量的共同属性。

因此,这一切都归结到 xvalues,并且需要限制移动到确切的位置。 这些位置由右值类别定义;prvalues是隐式移动,xvalues是显式移动( std::move 返回一个 xvalue ) 。

c++03的类别太多,无法将右值引用的引入正确地捕获到表达式属性中。

有了它们的介绍,就会说未命名的右值引用计算结果为右值,这样重载解析会更倾向于右值引用绑定,这将使它在复制构造函数中选择移动构造函数。 但发现这导致了问题,比如动态类型和具有资格的。

要显示这里问题,请考虑


int const&& f();

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

在pre-xvalue草稿上,这是允许的,因为在C++03中,non-class类型的左值永远不会是 cv-qualified 。 于没有任何对象之所以会相关around,但设计旨在 const rvalue-reference中的情况下,因为在这里我们适用于做( = 内存) 引用对象,并放置了媒介从 non-class rvalues是 mainly. !

动态类型的问题类似于。 在C++03中,类类型的左值有一个已知的动态类型- 它是该表达式的静态类型。 因为有另一种方法,你需要引用或者取消引用,这将计算为一个左值。 这不是未命名的右值引用,但它们可以显示多态行为。 为了解决这个问题

  • 未命名的右值引用变成了英镑 xvalues 。 它们可以被限定并且有可能有不同的动态类型。 它们像预期的那样,在重载时喜欢右值引用,并且不会绑定到non-const左值引用。

  • 以前是一个右值( 文本,通过转换到non-reference类型创建的对象) 现在变成了一个的prvalue 。 在重载过程中,它们与xvalues有相同的偏好。

  • 以前是一个左值保留一个左值。

和两个分组来捕获那些可以被限定的,可以有不同动态类型的( glvalues ),以及重载首选右值引用绑定( rvalues )的。

...