標籤:保留 構造 沒有 不可 記憶體 相同 back tor dash
轉載請保留以下聲明
趙宗晟
出處:http://www.cnblogs.com/zhao-zongsheng/p/value_categories_and_move_semantics.html
C++11之前value categories只有兩類,lvalue和rvalue,在C++11之後出現了新的value categories,即prvalue, glvalue, xvalue。不理解value categories可能會讓我們遇到一些坑時不知怎麼去修改,所以理解value categories對於寫C++的人來說時比較重要的。而理解value categories離不開一個概念——move semantics。瞭解C++11的人我相信都瞭解了std::move,右值引用和移動構造/移動複製等概念,但是對move semantics這個概念的準確定義,還是有很多人比較模糊的。我想通過這篇文章談一談我對value categories和move semantics的理解。首先從move semantics開始。
什麼是move semantics(移動語意)?
semantics是來自語言學的一個概念,翻譯成中文就是“語義”。說到電腦語言,可能有很多人認為他是電腦科學下面的子門類。實際上他是電腦科學和語言學的交叉科目,裡面有很多概念都來自語言學的內容,也有語言學科班的學生之後去做編譯的研究/工作。所以我們先從自然語言入手,通過類比能夠更好地理解move semantics。下面有兩個句子:
- 他是飯桶。
- 這是飯桶。
這兩句話裡面都有“飯桶”這個詞,但是兩個句子中“飯桶”意思卻不一樣。從文法上來看,這倆都是“<代詞>是飯桶”的形式,只有代詞不一樣,但句子意思卻完全不一樣了。句子1的意思是罵一個人很沒用,句子2的意思是說明這個物體是盛飯的桶。這個例子說明,要理解一個單詞的意思(例如“飯桶”)是要結合句中其他單詞,結合整個句子,甚至要結合前後句理解。
而在C++語言中也是類似的。下面有兩個“句子”(語句):
- vec = vector<int>();
- vec = another_vec;
其中,vec和another_vec都是vector<int>類型的變數。
這兩個語句都是“vec = XXXX;”的形式,但是語句1是把XXXX移動到變數vec,語句2是把XXXX拷貝給vec。兩個語句中都有“=”運算子,但是語句1中的意思是“移動到”,語句2中的意思是“拷貝給”。所以“=”運算子和整個句子的意思是由XXXX的類型決定的。我們可以說語句1有移動的意思,語句2有拷貝的意思,或者說,語句1中的“=”是移動的意思,語句2中的“=”是拷貝的意思。更正式地說,語句1呈現了移動語意,語句2呈現了拷貝語義,語句1中的“=”呈現了移動語意,語句2中的“=”呈現了拷貝語義。用英文說則是,statement 1 displayed move semantics; statement 2 displayed copy semantics; operator= in statement 1 displayed move semantics; operator= in statement 2 displayed copy semantics。
其實說白了,“移動語意”翻譯成白話就是“移動的意思”。
怎麼理解5種value categories(值類別)?
C++中的每個運算式都有兩種屬性,一個是類型type,另一個就是值類別value category。每個運算式的值類別一定屬於且僅屬於prvalue (pure rvalue), xvalue, lvalue三種中的一種。prvalue和xvalue統稱為rvalue,xvalue和lvalue統稱為glvalue (generalized lvalue),如所示:
那麼,prvalue,xvalue和lvalue是怎麼定義的?
其實所有運算式都有以下兩種屬性:
- 是否有identity(同一性,或者說“有身份”):是否可以與另一個運算式或對象比較,判斷是否是同一個實體。比如,如果有地址,可以比較他們的地址相同;
- 是否可以移動:如果出現在賦值,初始化等語句中,是否會使語句呈現移動語意。
於是有:
- 有identity,也可以移動的運算式為xvalue;
- 有identity,但不能移動的運算式為lvalue;
- 沒有identity,但是可以移動的運算式為prvalue;
至於沒有identity,也不可以移動的運算式,在實際應用中不存在這樣的運算式,也沒必要有這樣的運算式。
對於另外兩種值類別,我們可以這麼總結:
- 有identity的運算式,值類別為glvalue;
- 可以移動的運算式,值類別為rvalue。
分析理解C++標準中值類別的規則
舉例來理解的話,對於xvalue運算式,有這樣的規則:
如果一個運算式是函數調用或重載運算子運算式,且其傳回型別為右值引用,例如 std::move(x),那麼這個運算式是xvalue運算式
對於這個規則,我們可以這麼理解。首先返回一個對象,肯定是要在棧上面預留記憶體空間的,所以這個對象是由identity的。傳回型別是右值引用,所以它會讓使用這個運算式的語句呈現移動語意,所以是可以動的。因此,這個運算式是xvalue運算式。
對於xvalue還有這樣的規則
對象成員運算式,即"a.m",如果 a
是右值且 m
是非參考型別的非待用資料成員,則這個運算式是xvalue運算式
這條規則可以這麼理解,a是右值,也就是可以移動,那麼對於a對象的一部分,m也應當是可以移動的。訪問對象的“.”運算子實際上是指標的位移運算,既然要用指標,那麼肯定是有地址的。因此,這個運算式是xvalue運算式。
再比如:
對象成員運算式,即"a.m",如果 m
是成員枚舉符或非靜態成員函數,則這個運算式是prvalue運算式
不管是靜態枚舉符實際上是一個數字,成員函數實際上是指向程式碼片段的地址,實際上也是一個數字,而且都是在編譯時間期就決定了的數字。cpu對這些數字操作時,這些數字是直接放在指令內部的,或者是放在寄存器中,而不會在記憶體中存在,所以他們是沒有identity的。所以這個運算式是prvalue運算式。
C++標準還定義了很多規則,詳細規定了哪些運算式是prvalue,哪些是xvalue,哪些又是lvalue。這些規則都可以用類似的方法分析並理解,而不需要去死記硬背。
C++11的value category(值類別)以及move semantics(移動語意)