值类别以及对它们的引用Value categories, and references to them

本主题介绍了 C++ 中存在的各种值类别(以及对值的引用)。This topic describes the various categories of values (and references to values) that exist in C++. 你肯定听说过左值 和右值 ,但可能未使用本主题提供的术语思考过它们。You will doubtless have heard of lvalues and rvalues, but you may not think of them in the terms that this topic presents. 此外还有其他类型的值。And there are other kinds of values, too.

C++ 中的每个表达式都会生成一个值,该值属于本主题讨论的类别之一。Every expression in C++ yields a value that belongs to one of the categories discussed in this topic. C++ 语言及其工具和规则的许多方面都需要正确理解这些值类别以及对它们的引用。There are aspects of the C++ language, its facilies, and rules, that demand a proper understanding of these value categories, and references to them. 例如,获取值的地址、复制值、移动值、将值转发给另一函数。For example, taking the address of a value, copying a value, moving a value, and forwarding a value on to another function. 本主题不会深入讨论所有这些方面,但会提供基本信息,帮助你深刻理解它们。This topic doesn't go into all of those aspects in depth, but it provides foundational information for a solid understanding of them.

本主题的信息结构遵循 Stroustrup 对值类别的分析,即按两种独立的属性(标识和移动性)对值分类 [Stroustrup, 2013]。The info in this topic is framed in terms of Stroustrup's analysis of value categories by the two independent properties of identity and movability [Stroustrup, 2013].

左值有标识An lvalue has identity

值有标识是什么意思? What does it mean for a value to have identity? 如果你有(或者可以获取)某个值的内存地址,并且可以安全地使用它,则说明该值有标识。If you have (or you can take) the memory address of a value and use it safely, then the value has identity. 这样,你除了比较值的内容,还可以执行其他操作:按标识比较或区分它们。That way, you can do more than compare the contents of values: you can compare or distinguish them by identity.

左值有标识。 An lvalue has identity. “lvalue”(左值)中的“l”是“left”(例如,在赋值的 left 侧,即左侧)的缩写,但目前这只具有历史意义。It's now a matter of only historical interest that the "l" in "lvalue" is an abbreviation of "left" (as in, the left-hand-side of an assignment). 在 C++ 中,左值可以出现在赋值的左侧,也可以出现在右侧。 In C++, an lvalue can appear on the left or on the right of an assignment. 因此,并不能根据“lvalue”(左值)中的“l”(左)来了解或确定值的实际位置。The "l" in "lvalues", then, doesn't actually help you to comprehend nor define what they are. 你只需明白,我们称某个值为左值,只是表明该值有标识。You need only to understand that what we call an lvalue is a value that has identity.

属于左值的表达式的示例包括:命名的变量或常量;返回引用的函数。Examples of expressions that are lvalues include: a named variable or constant; or a function that returns a reference. 不属于左值的表达式的示例包括: 临时值;返回值的函数。Examples of expressions that are not lvalues include: a temporary; or a function that returns by value.

int& get_by_ref() { ... }
int get_by_val() { ... }

int main()
{
    std::vector<byte> vec{ 99, 98, 97 };
    std::vector<byte>* addr1{ &vec }; // ok: vec is an lvalue.
    int* addr2{ &get_by_ref() }; // ok: get_by_ref() is an lvalue.

    int* addr3{ &(get_by_ref() + 1) }; // Error: get_by_ref() + 1 is not an lvalue.
    int* addr4{ &get_by_val() }; // Error: get_by_val() is not an lvalue.
}

现在可以说左值有标识,而将亡值也是这样。Now, while it's a true statement that lvalues have identity, so do xvalues. 我们稍后会在本主题中详细探讨什么是将亡值。 We'll go more into what an xvalue is later in this topic. 现在,我们只需知道有一个名为“glvalue”(全称为“generalized lvalue”,即“泛左值”)的值类别。For now, just be aware that there is a value category called glvalue, for "generalized lvalue". 泛左值的超集包含左值(也称为“经典左值” )和将亡值。The superset of glvalues contains both lvalues (also known as classical lvalues) and xvalues. 因此,虽然“左值有标识”这种说法是正确的,但严格说来,应该是泛左值集有标识,如下图所示。So, while "an lvalue has identity" is true, the complete set of things that have identity is the set of glvalues, as shown in this illustration.

左值有标识

右值可移动,左值不能An rvalue is movable; an lvalue is not

但是,有的值不是泛左值。But there are values that are not glvalues. 因此,有的值你不能获取其内存地址(或者说,你不能依赖其有效性)。 Consequently, there are values that you can't obtain a memory address for (or you can't rely on it to be valid). 我们在上面的代码示例中看到过一些这样的值。We saw some such values in the code example above. 这似乎是一个缺点。This sounds like a disadvantage. 但实际上,此类值的优势是可以移动它(通常开销很低),而不是复制它(通常开销很高)。 But in fact the advantage of a value like that is that you can move from it (which is generally cheap), rather than copy from it (which is generally expensive). 移动一个值意味着该值不再在原来的位置。Moving from a value means that it's no longer in the place it used to be. 因此,应避免在原来的位置访问它。So, trying to access it in the place it used to be is something to be avoided. 本主题不讨论何时以及如何移动值。 A discussion of when and how to move a value is out of scope for this topic. 就本主题来说,我们只需知道,可移动的值称为“右值”(或“经典右值”)。 For this topic, we just need to know that a value that is movable is known as an rvalue (or classical rvalue).

“rvalue”(右值)中的“r”是“right”(例如,在赋值的 right 侧,即右侧)的缩写。The "r" in "rvalue" is an abbreviation of "right" (as in, the right-hand-side of an assignment). 但是,你可以在赋值外部使用右值以及右值的引用。But you can use rvalues, and references to rvalues, outside of assignments. 那么,就不需关注“rvalue”(右值)中的“r”(右)了。The "r" in "rvalues", then, is not the thing to focus on. 你只需明白,我们称某个值为右值,只是表明该值可移动。You need only to understand that what we call an rvalue is a value that is movable.

与之相反,左值不可移动,如下图所示。An lvalue, conversely, isn't movable, as shown in this illustration. 如果左值可以移动,则违反左值的定义, 会对代码造成意外的问题,因为代码通常会认为左值是可以持续访问的。An lvalue that moved would defy the definition of lvalue, and it would be an unexpected problem for code that very reasonably expected to be able to continue to access the lvalue.

右值可移动,左值不能

不能移动左值。You can't move an lvalue. 但是,有一种 泛左值(带标识的值集)是可以移动的—前提是你知道如何操作(例如,注意不要在移动后访问它)—这就是将亡值。But there is a kind of glvalue (the set of things with identity) that you can move—if you know what you're doing (including being careful not to access it after the move)—and that's the xvalue. 我们会在下面再次遇到此概念,并会对值类别进行汇总。We'll revisit this idea one more time below, when we look at the complete picture of value categories.

右值引用以及引用绑定规则Rvalue references, and reference-binding rules

此部分介绍右值引用的语法。This section introduces the syntax for a reference to an rvalue. 我们需要等到另一个主题来讨论如何对移动和转发进行实质性处理,不过,在这里只需知道右值引用是那些问题的解决方案的必要部分。We'll have to wait for another topic to go into a substantial treatment of moving and forwarding, but suffice to say that rvalue references are a necessary piece of the solution of those problems. 但是,在讨论右值引用之前,我们首先需要更清楚地了解 T&—我们以前直接称之为“引用”。Before we look at rvalue references, though, we first need to be clearer about T&—the thing we've formerly been calling just "a reference". 它实际上是“左值(非常量)引用”,引用的是允许引用用户写入的值。It's really "an lvalue (non-const) reference", which refers to an value to which the user of the reference can write.

template<typename T> T& get_by_lvalue_ref() { ... } // Get by lvalue (non-const) reference.
template<typename T> void set_by_lvalue_ref(T&) { ... } // Set by lvalue (non-const) reference.

左值引用可以绑定到左值,但不能绑定到右值。An lvalue reference can bind to an lvalue, but not to an rvalue.

此外还有左值常量引用 (T const&),引用的是不可供引用用户写入的对象(例如常量)。 Then there are lvalue const references (T const&), which refer to objects to which the user of the reference can't write (for example, a constant).

template<typename T> T const& get_by_lvalue_cref() { ... } // Get by lvalue const reference.
template<typename T> void set_by_lvalue_cref(T const&) { ... } // Set by lvalue const reference.

左值常量引用可以绑定到左值或右值。An lvalue const reference can bind to an lvalue or to an rvalue.

类型为 T 的右值引用的语法可以表示为 T&&The syntax for a reference to an rvalue of type T is written as T&&. 右值引用引用可移动值—其内容在使用后不需保留的值(例如临时值)。An rvalue reference refers to a movable value—an value whose contents we don't need to preserve after we've used it (for example, a temporary). 由于关键是移动(并修改)绑定到右值引用的值,因此不会将 constvolatile 限定符(也称 cv 限定符)应用到右值引用。Since the whole point is to move from (thereby modifying) the value bound to an rvalue reference, const and volatile qualifiers (also known as cv-qualifiers) don't apply to rvalue references.

template<typename T> T&& get_by_rvalue_ref() { ... } // Get by rvalue reference.
struct A { A(A&& other) { ... } }; // A move constructor takes an rvalue reference.

右值引用绑定到右值。An rvalue reference binds to an rvalue. 事实上,在进行重载决策时,右值 首选绑定到右值引用而不是左值常量引用。In fact, in terms of overload resolution, an rvalue prefers to be bound to an rvalue reference than to an lvalue const reference. 但是,右值引用不能绑定到左值,因为如前所述,右值引用引用的是假定其内容不需保留的值(例如,移动构造函数的参数)。But an rvalue reference can't bind to an lvalue because, as we've said, an rvalue reference refers to a value whose contents it's assumed we don't need to preserve (say, the parameter for a move constructor).

也可在需要传值参数的情况下,通过复制构造传递右值(或者在右值是将亡值的情况下,通过移动构造来传递)。You can also pass an rvalue where a by-value argument is expected, via copy construction (or via move construction, if the rvalue is an xvalue).

泛左值有标识,纯右值没有A glvalue has identity; a prvalue does not

目前,我们介绍了什么值有标识。At this stage, we know what has identity. 我们还介绍了什么值可以移动,什么值不能移动。And we know what's movable and what isn't. 但是,我们还没有介绍没有标识的值集。 But we haven't yet named the set of values that don't have identity. 该集称为“prvalue”(全称“pure rvalue”,即“纯右值”)。 That set is known as the prvalue, or pure rvalue.

int& get_by_ref() { ... }
int get_by_val() { ... }

int main()
{
    int* addr3{ &(get_by_ref() + 1) }; // Error: get_by_ref() + 1 is a prvalue.
    int* addr4{ &get_by_val() }; // Error: get_by_val() is a prvalue.
}

左值有标识,纯右值没有

值类别汇总The complete picture of value categories

现在,我们将上面的信息和图片汇总一下。It only remains to combine the info and illustrations above into a single, big picture.

值类别汇总

泛左值 (i)glvalue (i)

泛左值(即 glvalue,全称为 generalized lvalue)有标识。A glvalue (generalized lvalue) has identity.

左值 (i&!m)lvalue (i&!m)

左值(一种泛左值)有标识,但不可移动。An lvalue (a kind of glvalue) has identity, but isn't movable. 这些通常都是可以传递的读-写值,传递方法包括引用传递、常量引用传递、值传递(如果复制的开销很低)。These are typically read-write values that you pass around by reference or by const reference, or by value if copying is cheap. 左值不能绑定到右值引用。An lvalue can't be bound to an rvalue reference.

将亡值 (i&m)xvalue (i&m)

将亡值(一种泛左值,但也是一种右值)有标识,但也不可移动。An xvalue (a kind of glvalue, but also a kind of rvalue) has identity, and is also movable. 将亡值可以是以前的某个左值。由于复制开销很高,你已经决定移动此左值,但需注意不要在以后再访问它。This might be an erstwhile lvalue that you've decided to move because copying is expensive, and you'll be careful not to access it afterward. 下面演示如何将左值转换为将亡值。Here's how you can turn an lvalue into an xvalue.

struct A { ... };
A a; // a is an lvalue...
static_cast<A&&>(a); // ...but this expression is an xvalue.

在上述代码示例中,我们还没有移动任何值。In the code example above, we haven't moved anything yet. 我们只是将左值强制转换为未命名的右值引用,通过这种方式创建了一个将亡值。We've just created an xvalue by casting an lvalue to an unnamed rvalue reference. 该值仍可通过其左值名称进行标识,但作为将亡值,它现在可以移动了。 It can still be identified by its lvalue name; but, as an xvalue, it is now capable of being moved. 若要了解这样做的原因,以及实际上要进行什么样的移动,则需参阅另一主题。The reasons for doing so, and what moving actually looks like, will have to wait for another topic. 不过,你不妨将“xvalue”(将亡值)中的“x”视为“expert-only”(专家级),暂时跳过该主题。But you can think of the "x" in "xvalue" as meaning "expert-only" if that helps. 将左值强制转换为将亡值(一种右值)以后,该值就可以绑定到右值引用了。By casting an lvalue into an xvalue (a kind of rvalue), the value then becomes capable of being bound to an rvalue reference.

下面是两个其他的将亡值示例—一个示例调用可返回未命名右值引用的函数,另一个示例访问将亡值的成员。Here are two other examples of xvalues—calling a function that returns an unnamed rvalue reference, and accessing a member of an xvalue.

struct A { int m; };
A&& f();
f(); // This expression is an xvalue...
f().m; // ...and so is this.

纯右值 (!i&m)prvalue (!i&m)

纯右值(即 prvalue,全称为 pure rvalue,一种右值)没有标识,但可移动。A prvalue (pure rvalue; a kind of rvalue) doesn't have identity, but is movable. 此类值通常为临时值,是调用可返回值的函数后生成的,或者是对任何其他不属于泛左值的表达式求值后生成的。These are typically temporaries, the result of calling a function that returns by value, or the result of evaluating any other expression that's not a glvalue,

右值 (m)rvalue (m)

右值可移动。An rvalue is movable. 右值引用 始终引用右值(假定其内容不需保留的值)。An rvalue reference always refers to an rvalue (a value whose contents it's assumed we don't need to preserve).

但是,右值引用本身是否为右值?But, is an rvalue reference itself an rvalue? 未命名 右值引用(例如在上面的将亡值代码示例中显示的引用)为将亡值,因此属于右值。An unnamed rvalue reference (like the ones shown in the xvalue code examples above) is an xvalue so, yes, it's an rvalue. 它首选绑定到右值引用函数参数,例如移动构造函数的参数。It prefers to be bound to an rvalue reference function parameter, such as that of a move constructor. 相反(也许违背直觉),如果右值引用有名称,则包含该名称的表达式为左值。Conversely (and perhaps counter-intuitively), if an rvalue reference has a name, then the expression consisting of that name is an lvalue. 因此,它不能绑定到右值引用参数。 So it can't be bound to an rvalue reference parameter. 但是,让它那样做很容易—将它再次强制转换为未命名右值引用(将亡值)即可。But it's easy to make it do so—just cast it to an unnamed rvalue reference (an xvalue) again.

void foo(A&) { ... }
void foo(A&&) { ... }
void bar(A&& a) // a is a named rvalue reference; it's an lvalue.
{
    foo(a); // Calls foo(A&).
    foo(static_cast<A&&>(a)); // Calls foo(A&&).
}
A&& get_by_rvalue_ref() { ... } // This unnamed rvalue reference is an xvalue.

!i&!m!i&!m

没有标识且不可移动的这类值是我们尚未讨论过的一种组合。The kind of value that doesn't have identity and isn't movable is the one combination that we haven't yet discussed. 不过,我们可以将它忽略,因为该类别在 C++ 语言中属于无用的概念。But we can disregard it, because that category isn't a useful idea in the C++ language.

引用折叠规则Reference-collapsing rules

一个表达式中有多个相同的引用(对左值引用进行左值引用,或者对右值引用进行右值引用)时,会互相取消。Multiple like references in an expression (an lvalue reference to an lvalue reference, or an rvalue reference to an rvalue reference) cancel one another out.

  • A& & 折叠成 A&A& & collapses into A&.
  • A&& && 折叠成 A&&A&& && collapses into A&&.

一个表达式中有多个不同的引用时,会折叠成一个左值引用。Multiple unlike references in an expression collapse to an lvalue reference.

  • A& && 折叠成 A&A& && collapses into A&.
  • A&& & 折叠成 A&A&& & collapses into A&.

转发引用Forwarding references

这最后一部分将我们讨论过的右值引用与转发引用的不同概念进行比较。 This final section contrasts rvalue references, which we've already discussed, with the different concept of a forwarding reference.

void foo(A&& a) { ... }
  • A&& 为右值引用,如前所述。A&& is an rvalue reference, as we've seen. 常量和易失性不适用于右值引用。Const and volatile don't apply to rvalue references.
  • foo 只接受 A 类型的右值。foo accepts only rvalues of type A.
  • 之所以存在右值引用(例如 A&&),是因为这样可以创作一个重载,该重载经过优化,适用于需要传递临时值(或其他右值)的情况。The reason rvalue references (such as A&&) exist is so that you can author an overload that's optimized for the case of a temporary (or other rvalue) being passed.
template <typename _Ty> void bar(_Ty&& ty) { ... }
  • _Ty&& 是转发引用。 _Ty&& is a forwarding reference. 类型 _Ty 可以是常量/非常量(独立于易失性/非易失性),具体取决于传递给 bar 的内容。Depending what you pass to bar, type _Ty could be const/non-const independently of volatile/non-volatile.
  • bar 接受任何类型为 _Ty 的左值或右值。bar accepts any lvalue or rvalue of type _Ty.
  • 传递左值会导致转发引用变成 _Ty& &&,后者折叠成左值引用 _Ty&Passing an lvalue causes the forwarding reference to become _Ty& &&, which collapses to the lvalue reference _Ty&.
  • 传递右值会导致转发引用变成 _Ty&& &&,后者折叠成右值引用 _Ty&&Passing an rvalue causes the forwarding reference to become _Ty&& &&, which collapses to the rvalue reference _Ty&&.
  • 使用转发引用(例如 _Ty&&)不是为了优化, 而是为了获取传递的值并以透明且高效的方式对其进行转发。The reason forwarding references (such as _Ty&&) exist is not for optimization, but to take what you pass to them and to forward it on transparently and efficiently. 只有在编写(或深入研究)库代码时,才可能遇到转发引用—例如,一个在构造函数参数基础上进行转发的工厂函数。You're likely to encounter a forwarding reference only if you write (or closely study) library code—for example, a factory function that forwards on constructor arguments.

Sources

  • [Stroustrup, 2013] B. Stroustrup:《C++ 程序设计语言》,第四版。[Stroustrup, 2013] B. Stroustrup: The C++ Programming Language, Fourth Edition. Addison-Wesley。Addison-Wesley. 2013。2013.