值類別,以及其參考Value categories, and references to them

本主題說明 C++ 中存在的各種值類別 (以及值參考)。This topic describes the various categories of values (and references to values) that exist in C++. 您一定曾聽過 lvaluesrvalues,但您可能不認為它們如本主題所呈現。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].

lvalue 具有身分識別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.

lvalue 具有身分識別。An lvalue has identity. 現在只關乎歷史價值:"lvalue" 中的 "l" 是 "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++,lvalue 可以出現在指派的左邊「或」 右邊。In C++, an lvalue can appear on the left or on the right of an assignment. 然後,"lvalues" 中的 "L" 實際上不會幫助您理解或定義其意義。The "l" in "lvalues", then, doesn't actually help you to comprehend nor define what they are. 您只需要了解我們所謂的 lvalue 是具有身分識別的值。You need only to understand that what we call an lvalue is a value that has identity.

lvalue 的運算式範例包括:具名的變數或常數;或可傳回參考的函式。Examples of expressions that are lvalues include: a named variable or constant; or a function that returns a reference. 「不是」 lvalue 的運算式範例包括:暫存項目;或可依照值傳回資料的函式。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.
}

現在,lvalue 確實有身分識別,然而 xvalue 也有身分識別。Now, while it's a true statement that lvalues have identity, so do xvalues. 我們稍後會在本主題中近一步討論什麼是 xvalue 。We'll go more into what an xvalue is later in this topic. 現在,只要知道有一個名為 glvalue 的值類別即可,其代表「廣義 lvalue」。For now, just be aware that there is a value category called glvalue, for "generalized lvalue". glvalue 的超集同時包含 lvalue (也稱為「傳統 lvalue」 ) 和 xvalue。The superset of glvalues contains both lvalues (also known as classical lvalues) and xvalues. 因此,「lvalue 具有身分識別」是真的,而具有身分識別的完整項目集合就是 glvalue 集合,如下圖所示。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.

lvalue 具有身分識別

rvalue 可移動;lvalue 不可移動An rvalue is movable; an lvalue is not

但有不是 glvalue 的值。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. 在本主題中,我們只需要知道可移動的值即為 rvalue (或「傳統 rvalue」 )。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" 的縮寫 (就像,在指派的右手邊)。The "r" in "rvalue" is an abbreviation of "right" (as in, the right-hand-side of an assignment). 但您可以在指派之外使用 rvalue,以及 rvalue 的參考。But you can use rvalues, and references to rvalues, outside of assignments. 然而,"rvalues" 中的 "r" 不是要聚焦的項目。The "r" in "rvalues", then, is not the thing to focus on. 您只需要了解我們所謂的 rvalue 是可移動的值。You need only to understand that what we call an rvalue is a value that is movable.

相反地,lvalue 不可移動,如此圖所示。An lvalue, conversely, isn't movable, as shown in this illustration. 已移動的 lvalue 會蔑視 lvalue 的定義,而對於非常合理預期能夠繼續存取 lvalue 的程式碼而言會是非預期的問題。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.

rvalue 可移動;lvalue 不可移動

您無法移動 lvalue。You can't move an lvalue. 但有 一種您可移動的 glvalue (具有身分識別的項目集合)—如果您知道您正在做什麼 (包括小心不要在移動後存取它)—也就是 xvalue。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 參考和參考繫結規則Rvalue references, and reference-binding rules

本節將介紹 rvalue 參考的語法。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. 不過,在我們研究 rvalue 參考之前,我們必須先更清楚了解 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". 它其實是「lvalue (非 const) 參考」,會參照參考的使用者可以寫入其中的值。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.

lvalue 參考可以繫結至 lvalue,但不可繫結至 rvalue。An lvalue reference can bind to an lvalue, but not to an rvalue.

而後有 lvalue const 參考 (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.

lvalue const 參考可以繫結至 lvalue 或 rvalue。An lvalue const reference can bind to an lvalue or to an rvalue.

類型 T 的 rvalue 參考語法會撰寫為 T&&The syntax for a reference to an rvalue of type T is written as T&&. rvalue 參考會參照可移動的值—我們在使用後不需要保留其內容的值 (例如,暫存項目)。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). 由於重點在於從繫結至 rvalue 參考的值移動 (從而修改),因此 constvolatile 限定詞 (也稱為 cv 限定詞) 不會套用至 rvalue 參考。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.

rvalue 參考會繫結至 rvalue。An rvalue reference binds to an rvalue. 事實上,就多載解析而論,rvalue「偏好」 繫結至 rvlue 參考 (相較於 lvalue const 參考)。In fact, in terms of overload resolution, an rvalue prefers to be bound to an rvalue reference than to an lvalue const reference. 但是 rvlue 參考無法繫結至 lvalue,因為我們已經說過,rvlue 參考會參照假設我們不需要保留其內容的值 (例如,移動建構函式的參數)。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).

您也可以透過複製建構 (如果 rvalue 是 xvalue,則透過移動建構),傳遞預期有 by-value 的 rvalue。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).

glvalue 具有身分識別;prvalue 則不具備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 或「單純 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.
}

glvalue 具有身分識別;prvalue 則不具備

值類別的全貌The complete picture of value categories

仍然只是將上面的資訊與圖解結合成一個大圖片。It only remains to combine the info and illustrations above into a single, big picture.

值類別的全貌

glvalue (i)glvalue (i)

glvalue (廣義 lvalue) 具有身分識別。A glvalue (generalized lvalue) has identity.

lvalue (i&!m)lvalue (i&!m)

lvalue (一種 glvalue) 具有身分識別,但不是可移動。An lvalue (a kind of glvalue) has identity, but isn't movable. 這些通常是您依照參考或依照 const 參考,或依照值 (如果複製成本低廉) 傳遞的讀寫值。These are typically read-write values that you pass around by reference or by const reference, or by value if copying is cheap. lvalue 無法繫結至 rvalue 參考。An lvalue can't be bound to an rvalue reference.

xvalue (i&m)xvalue (i&m)

xvalue (一種 glvalue,但也是一種 rvalue) 具有身分識別,也可移動。An xvalue (a kind of glvalue, but also a kind of rvalue) has identity, and is also movable. 這可能是您先前因為複製成本昂貴而決定要移動的 lvalue,您得小心之後不要存取它。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. 以下顯示如何將 lvalue 轉換成 xvalue。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. 我們剛才將 lvalue 轉換為未具名的 rvalue 參考,而建立了 xvalue。We've just created an xvalue by casting an lvalue to an unnamed rvalue reference. 它仍可依照其 lvalue 名稱識別,但作為 xvalue,現在就「能夠」 移動。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" 認為是「只有專家才懂」(如果這有幫助的話)。But you can think of the "x" in "xvalue" as meaning "expert-only" if that helps. 將 lvalue 轉換為 xvalue (一種 rvalue),而後此值就能夠繫結至 rvalue 參考。By casting an lvalue into an xvalue (a kind of rvalue), the value then becomes capable of being bound to an rvalue reference.

以下是 xvalue 的兩個其他範例—呼叫可傳回未具名 rvalue 參考的函式,並存取 xvalue 的成員。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.

prvalue (!i&m)prvalue (!i&m)

Prvalue (單純;一種 rvalue) 沒有身分識別,但可移動。A prvalue (pure rvalue; a kind of rvalue) doesn't have identity, but is movable. 這些通常都是暫存項目、呼叫函式 (其可依照值傳回資料) 的結果,或是評估任何其他非 glvalue 運算式的結果。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,

rvalue (m)rvalue (m)

rvalue 是可移動。An rvalue is movable. rvalue「參考」 一律會參照 rvalue (假設我們不需要保留其內容的值)。An rvalue reference always refers to an rvalue (a value whose contents it's assumed we don't need to preserve).

但是,rvalue 參考本身是 rvalue 嗎?But, is an rvalue reference itself an rvalue? 「未具名」 的 rvalue 參考 (如同上述 xvalue 程式碼範例所示的項目) 是 xvalue,所以它就是 rvalue。An unnamed rvalue reference (like the ones shown in the xvalue code examples above) is an xvalue so, yes, it's an rvalue. 它偏好繫結至 rvalue 參考函式參數,例如移動建構函式的參數。It prefers to be bound to an rvalue reference function parameter, such as that of a move constructor. 相反地 (或許反直覺),如果 rvalue 參考具有名稱,則該名稱所組成的運算式為 lvalue。Conversely (and perhaps counter-intuitively), if an rvalue reference has a name, then the expression consisting of that name is an lvalue. 因此它「無法」 繫結至 rvalue 參考參數。So it can't be bound to an rvalue reference parameter. 但這麼做很輕鬆—只要將它再次轉換為未具名的 rvalue 參考 (xvalue)。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

運算式中的多個相似參考 (lvalue 參考對 lvalue 參考,或 rvalue 參考對 rvalue 參考) 會相互抵消。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&&.

運算式中的多個相異參考會摺疊成 lvalue 參考。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

最後這一節會對比 rvalue 參考 (我們已經討論過) 與「轉送參考」 的不同概念。This final section contrasts rvalue references, which we've already discussed, with the different concept of a forwarding reference.

void foo(A&& a) { ... }
  • 如我們所見,A&& 是 rvalue 參考。A&& is an rvalue reference, as we've seen. Const 和 volatile 不適用於 rvalue 參考。Const and volatile don't apply to rvalue references.
  • foo 只接受類型 A 的 rvalue。foo accepts only rvalues of type A.
  • rvalue 參考 (例如 A&&) 存在的原因,是方便您撰寫針對傳遞暫存項目 (或其他 rvalue) 的情況最佳化的多載。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. 根據您傳遞至 bar 的項目,類型 _Ty 可能是與 volatile/非 volatile 無關的 const/非 const。Depending what you pass to bar, type _Ty could be const/non-const independently of volatile/non-volatile.
  • bar 接受任何類型 _Ty 的 lvalue 或 rvalue。bar accepts any lvalue or rvalue of type _Ty.
  • 傳遞 lvalue 會導致轉送參考變成 _Ty& &&,這會摺疊成 lvalue 參考_Ty&Passing an lvalue causes the forwarding reference to become _Ty& &&, which collapses to the lvalue reference _Ty&.
  • 傳遞 rvalue 會導致轉送參考變成 _Ty&& &&,這會摺疊成 rvalue 參考_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. 只有撰寫 (或仔細研究) 程式庫程式碼,才可能遇到轉送參考—例如,在建構函式引數上轉送的 factory 函式。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:The C++ Programming Language, Fourth Edition.[Stroustrup, 2013] B. Stroustrup: The C++ Programming Language, Fourth Edition. Addison-Wesley.Addison-Wesley. 2013.2013.