c++ - guidelines - 什麼是右值,左值,xvalues,glvalues和prvalues?




iso c++ (8)

How do these new categories relate to the existing rvalue and lvalue categories?

A C++03 lvalue is still a C++11 lvalue, whereas a C++03 rvalue is called a prvalue in C++11.

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

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

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

兩類分為五類。

  • 這些新的表達類別是什麼?
  • 這些新類別如何與現有的右值和左值類別相關聯?
  • C ++ 0x中的右值和左值類別與C ++ 03中的相同嗎?
  • 為什麼需要這些新類別? WG21神只是試圖將我們這些凡人混為一談?

為什麼需要這些新類別? WG21神只是試圖把我們這些凡人混為一談?

我不覺得其他答案(雖然其中很多答案都很好)確實捕捉了這個特定問題的答案。 是的,這些類別和類別存在允許移動語義,但出於某種原因存在復雜性。 這是在C ++ 11中移動東西的一個不可侵犯的規則:

只有在毫無疑問安全的情況下才能移動。

這就是為什麼存在這些類別的原因:能夠談論可以安全地從它們轉移的價值,並且在不存在價值的地方談論價值。

在最早版本的r值引用中,移動很容易發生。 容易了。 很容易,當用戶沒有真正的意圖時,隱含地移動事物有很大的潛力。

以下是可以安全移動某些東西的環境:

  1. 當它是一個臨時或子對象時。 (prvalue)
  2. 當用戶明確表示要移動它時

如果你這樣做:

SomeType &&Func() { ... }

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

這是做什麼的? 在規範的舊版本中,在5個值出現之前,這會引發一個移動。 當然是的。 您將一個右值引用傳遞給了構造函數,因此它綁定到了一個右值引用的構造函數。 這很明顯。

這只是一個問題。 你沒有要求移動它。 哦,你可能會說&&應該是一個線索,但這並不能改變它違反規則的事實。 val並不是臨時性的,因為臨時工沒有名字。 你可能已經延長了臨時的生命週期,但這意味著它不是暫時的 ; 它就像任何其他堆棧變量一樣。

如果它不是暫時的,而且你沒有要求移動它,那麼移動是錯誤的。

顯而易見的解決方案是使val值左值。 這意味著你不能離開它。 好的; 它的名字,所以它是一個左值。

一旦你這樣做了,你不能再說SomeType&&意味著任何地方都是同樣的東西。 現在你已經對命名的右值引用和未命名的右值引用進行了區分。 那麼,名為右值引用是左值; 這是我們上面的解決方案。 那麼我們稱之為未命名的右值引用(上面Func的返回值)?

這不是一個左值,因為你不能從左值移動。 我們需要能夠通過返回&&來移動; 你還可以明確地說要搬家嗎? 畢竟,這就是std::move結果。 這不是一個右值(舊式),因為它可能在方程的左邊(事實上有點複雜,請參閱下面的問題和評論)。 它既不是左值也不是右值; 這是一種新的東西。

我們擁有的是一種價值,你可以把它看作是一個左值, 只是它可以隱式地從中移除 。 我們稱之為一個xvalue

請注意,xvalues是我們獲得另外兩類值的原因:

  • 一個prvalue實際上只是前一類右值的新名稱,即它們是不是 xvalues的右值。

  • glvalues是一個組中的xvalue和lvalues的結合 ,因為它們共享很多共同的屬性。

所以真的,這一切都歸結為xvalues,並且需要將運動限制在恰好和特定的地方。 這些地方由右值類別定義; prvalues是隱式的動作,而xvalues是明確的動作( std::move返回一個xvalue)。


介紹

ISOC ++ 11(官方ISO / IEC 14882:2011)是C ++編程語言標準的最新版本。 它包含一些新功能和概念,例如:

  • 右值引用
  • xvalue,glvalue,prvalue表達式值類別
  • 移動語義

如果我們想了解新的表達式值類別的概念,我們必須知道有右值和左值引用。 最好知道rvalues可以傳遞給非常量右值引用。

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

如果我們引用工作草案N3337(與已發布的ISOC ++ 11標準最相似的草案)中的Lvalues和rvalues小節,我們可以對價值類別的概念有一些直覺。

3.10左值和右值[basic.lval]

1表達式根據圖1中的分類法進行分類。

  • 一個左值(所謂的,歷史上,因為左值可以出現在賦值表達式的左側)指定一個函數或一個對象。 [例如:如果E是指針類型的表達式,則* E是指向E指向的對像或函數的左值表達式。 作為另一個例子,調用返回類型是左值引用的函數的結果是左值。 - 例子]
  • xvalue(“eXpiring”值)也指對象,通常接近其生命週期結束時(例如,可以移動其資源)。 xvalue是涉及右值引用(8.3.2)的某些表達式的結果。 [例子:調用返回類型是右值引用的函數的結果是一個xvalue。 - 例子]
  • 一個glvalue(“廣義”左值)是一個左值或一個xvalue。
  • 一個右值(所謂的,歷史上,因為右值可能出現在賦值表達式的右側)是一個xvalue,一個
    臨時對象(12.2)或其子對象,或者不是的值
    與一個對象相關聯。
  • 一個prvalue(“純”右值)是一個不是xvalue的右值。 [例子:調用返回類型不是的函數的結果
    參考是一個價值。 一個文字的值,如12,7.3e5或
    真實也是一種價值。 - 例子]

每個表達式都屬於該分類中的基本分類之一:左值,左值或右值。 表達式的這個屬性被稱為它的值類別。

但我不太清楚這一小節是否足以清楚地理解這些概念,因為“通常”並不是一般的,“接近其一生的終點”並不是真正具體的,“涉及右值引用”並不十分清楚,和“示例:調用返回類型為右值引用的函數的結果是一個xvalue。” 聽起來像是一條蛇咬著它的尾巴。

主要價值類別

每個表達式都只屬於一個主要值類別。 這些值類別是左值,左值和右值類別。

左值

表達式E屬於左值範疇,當且僅當E引用實體ALREADY有一個身份(地址,名稱或別名),使它可以在E之外訪問。

#include <iostream>

int i=7;

const int& f(){
    return i;
}

int main()
{
    std::cout<<&"www"<<std::endl; // This address ...
    std::cout<<&"www"<<std::endl; // ... and this address are the same.
    "www"; // The expression "www" in this row is an lvalue expression, because it refers to the same entity ...
    "www"; // ... as the entity the expression "www" in this row refers to.

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

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

- 強制轉換為對像類型的右值引用,或

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,第二個操作數是指向數據成員的指針。

請注意,上述規則的效果是,對象的名稱rvalue引用被視為左值,而對對象的未命名右值引用則被視為xvalue; 對函數的右值引用被視為左值,無論是否被命名。

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

prvalues

表達式E屬於prvalue類,當且僅當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;
}

混合價值類別

還有兩個重要的混合價值類別。 這些值類別是右值和glvalue類別。

右值

當且僅當E屬於xvalue類別或屬於prvalue類別時,表達式E屬於右值類別。

請注意,這個定義意味著當且僅當E指的是一個沒有任何身份的實體使得它可以在E YET之外訪問的時候,表達式E屬於右值類別。

glvalues

表達式E屬於glvalue類,當且僅當E屬於左值類別或xvalue類別。

實用規則

Scott Meyer published了一個非常有用的經驗法則來區分右值和左值。

  • 如果您可以獲取表達式的地址,則表達式是一個左值。
  • 如果表達式的類型是左值引用(例如,T&或const T&等),則該表達式是左值。
  • 否則,表達式是一個右值。 從概念上(通常也是實際上),右值對應於臨時對象,例如從函數返回或通過隱式類型轉換創建的臨時對象。 大多數文字值(例如10和5.3)也是右值。

C ++ 03的類別太局限了,無法正確地將rvalue引用引入到表達式屬性中。

通過引入它們,據說一個未命名的右值引用的值為右值,這樣重載解析會更喜歡右值引用綁定,這將使其選擇移動構造函數而不是複制構造函數。 但發現這會導致所有問題,例如動態類型和資格。

為了表明這一點,請考慮

int const&& f();

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

在pre-xvalue草稿上,這是允許的,因為在C ++ 03中,非類類型的rvalues永遠不會被cv限定。 但是它的目的是const在右值引用的情況下應用,因為在這裡我們引用了對象(= memory!),並且從非類右值中刪除const主要是因為沒有對象。

動態類型的問題具有類似的性質。 在C ++ 03中,類類型的右值具有已知的動態類型 - 它是該表達式的靜態類型。 因為有另一種方式,你需要引用或取消引用,評估為左值。 對於未命名的右值引用,這是不正確的,但它們可以顯示多態行為。 所以要解決它,

  • 未命名的右值引用變為xvalues 。 他們可以是合格的,並且可能具有不同的動態類型。 它們像預期的那樣,在重載期間更喜歡右值引用,並且不會綁定到非常量左值引用。

  • 以前是一個右值(文字,通過轉換為非參考類型創建的對象)現在變成了一個前 。 在重載期間,它們與xvalues具有相同的偏好。

  • 以前是一個左值留下一個左值。

還有兩個分組用於捕獲那些可以被限定的,可以有不同的動態類型( glvalues )和那些重載比較喜歡右值引用綁定( rvalues )的類型。


One addendum to the excellent answers above, on a point that confused me even after I had read Stroustrup and thought I understood the rvalue/lvalue distinction. When you see

int&& a = 3 ,

it's very tempting to read the int&& as a type and conclude that a is an rvalue. It's not:

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

a has a name and is ipso facto an lvalue. Don't think of the && as part of the type of a ; it's just something telling you what a is allowed to bind to.

This matters particularly for T&& type arguments in constructors. If you write

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

you will copy _t into t . You need

Foo::Foo(T&& _t) : t{std::move(_t)} {} if you want to move. Would that my compiler warned me when I left out the move !


恕我直言,關於它的含義的最好解釋給了我們stroustrup.com/terminology.pdf +考慮到DánielSándor和Mohan例子:

斯特勞斯:

現在我非常擔心。 顯然,我們正在陷入僵局或混亂或兩者兼而有之。 我花了午餐時間做了分析,看看哪些屬性(值)是獨立的。 只有兩個獨立的屬性:

  • has identity (即,地址)指針,用戶可以確定兩個副本是否相同,等等。
  • can be moved from - 即我們被允許以一些不確定但有效的狀態離開到“複製”的來源

這使我得出結論,確切地說有三種價值觀(使用正則表達式使用大寫字母表示否定的標記技巧 - 我很著急):

  • iM :有身份,不能從中移出
  • im :具有身份並且可以從(例如將左值轉換為右值引用的結果)
  • Im :沒有身份,可以從第四種可能性( IM :沒有身份並且無法移動)在C++ (或者我認為)中用於任何其他語言。

除了這三個基本的價值分類之外,我們還有兩個明顯的概括,它們與兩個獨立的屬性相對應:

  • i :有身份
  • m :可以從中移出

這讓我把這個圖表放在板子上:

命名

我觀察到,我們的名字只有有限的自由度:左邊的兩個點(標記為iMi )是指具有或多或少形式化的人稱為左值,右邊的兩個點(標記為mIm )是人們或多或少的形式稱為rvalues 。 這必須反映在我們的命名中。 也就是說, W的左邊“腿”應該有與lvalue相關的名字,而W的右邊“腿”應該有與rvalue.相關的名字rvalue. 我注意到,整個討論/問題來自於引用右值引用和移動語義。 這些概念根本不存在於由rvaluesrvalues組成的rvalues世界中。 有人觀察到這樣的想法

  • 每個value都是lvaluervalue
  • lvalue不是rvaluervalue不是lvalue

深深植根於我們的意識中,非常有用的特性,這種二分法的痕跡可以在草案標準中找到。 我們都同意我們應該保留這些屬性(並且使它們精確)。 這進一步限制了我們的命名選擇。 我觀察到標準庫的措辭使用rvalue來表示m (泛化),為了保留標準庫的期望和文本, W右下角應該被命名為rvalue.

這導致了對命名的集中討論。 首先,我們需要決定lvalue. 應該lvalue意味著iM還是泛化? 在Doug Gregor的領導下,我們在核心語言文字中列出了lvalue合格的意思。 列表是在大多數情況下做出的,最棘手/脆弱的文本lvalue表示iM 。 這是左值的古典意義,因為“在舊時代”沒有什麼東西被移動; moveC++0x一種新概念。 此外,命名W lvalue的最小值點給了我們屬性,即每個值都是lvaluervalue ,但不是兩者。

所以, W左上角是lvalue ,右下角是rvalue. 這是什麼使左下角和右上角的點? 左下角是經典左值的推廣,允許移動。 所以這是一個generalized lvalue. 我們把它命名為glvalue. 你可以質疑這個縮寫,但是(我認為)不是用邏輯。 我們假設在嚴重使用中, generalized lvalue無論如何都會以某種方式縮寫,所以我們最好立即做(或者冒險)。 W的右上角的點比右下角的一般點(現在,一直稱為rvalue )。 這一點代表了你可以移動的對象的原始純粹概念,因為它不能再被引用(除了析構函數)。 我喜歡短語specialized rvalue generalized lvalue而不是generalized lvalue但是簡單的pure rvalue勝出(也許是正確的)。 所以,W的左腿是lvalue和右lvalueglvaluelvalueprvalue rvalue. 順便說一句,每個值都是一個值或一個值,但不是兩個值。

這留下了Wim的頂部中間; 也就是說,具有身份並且可以移動的值。 我們真的沒有任何東西能夠引導我們為這些神秘的野獸命名。 他們對使用(草案)標准文本的人很重要,但不可能成為家喻戶曉的名字。 我們沒有發現任何真正的命名約束來指導我們,所以我們選擇了'x'作為中心,未知,奇怪,只有xpert,甚至是x-rated。


我將從最後一個問題開始:

為什麼需要這些新類別?

C ++標準包含許多處理表達式值類別的規則。 一些規則區分了左值和右值。 例如,涉及重載分辨率。 其他規則區分了glvalue和prvalue。 例如,可以使用不完整或抽像類型的glvalue,但沒有不完整或抽像類型的prvalue。 在我們使用這個術語之前,實際上需要區分glvalue / prevalue是指左值/右值的規則,它們或者是無意的錯誤,或者是包含了大量的解釋和例外規則,a“......除非右值是由於未命名右值引用...“。 所以,給予他們自己的名字的價值和價值的概念似乎是一個好主意。

這些新的表達類別是什麼? 這些新類別如何與現有的右值和左值類別相關聯?

我們仍然有與C ++ 98兼容的術語左值和右值。 我們剛剛將rvalues分成兩個子組,xvalues和prvalues,我們將lvalues和xvalues稱為glvalues。 Xvalues是未命名右值引用的一種新的值類別。 每個表達式都是以下三個表達式之一:左值,左值,右值。 維恩圖如下所示:

    ______ ______
   /      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
}

我想這個文件可能不是那麼簡短的介紹: n3055

整個屠殺始於移動語義。 一旦我們有了可以移動但不被複製的表達式,突然容易掌握的規則就要求區分可以移動的表達式和在哪個方向上。

根據我對草案的看法,r / l值的區別保持不變,只是在移動東西時變得混亂。

他們需要嗎? 如果我們想放棄新功能,可能不會。 但為了實現更好的優化,我們應該擁抱它們。

引用n3055

  • 一個左值 (所謂的,在歷史上,因為左值可以出現在賦值表達式的左邊)指定一個函數或一個對象。 [例如:如果E是指針類型的表達式,則*E是指向*E指向的對像或函數的左值表達式。 作為另一個例子,調用返回類型為左值引用的函數的結果是一個左值。]
  • xvalue (“eXpiring”值)也指對象,通常接近其生命週期結束時(例如,可以移動其資源)。 xvalue是涉及右值引用的某些表達式的結果。 [例子:調用返回類型是右值引用的函數的結果是一個xvalue。]
  • 一個glvalue (“廣義”左值)是一個左值或一個xvalue
  • 一個右值 (所謂的,歷史上,因為右值可能出現在賦值表達式的右側)是一個xvalue,它的一個臨時對像或子對象,或一個與對象無關的值。
  • 一個prvalue (“純”右值)是一個不是xvalue的右值。 [例子:調用返回類型不是引用的函數的結果是一個prvalue]

所討論的文件對於這個問題來說是一個很好的參考,因為它顯示了標準中引入新術語後發生的確切變化。







c++11