C++11——左值、右值和將亡值

轉載來自:https://blog.51cto.com/u_15155100/2869859

 

C++98 中表達式值的類型只有左值和右值兩種類型,可以取到地址的表達式就是左值,不是左值的值就是右值,而C++11中將表達式的值類型劃分成了lvalue(左值)、rvalue(右值)、prvalue(純右值)、xvalue(將亡值)、gvalue(泛左值) 5種。下文從基本概念並結合實際的例子區分C++11中的表達式值類型。

基本概念

表達式

  • 表達式的定義
    定義: 由運算符(operator)和運算對象(operand)構成的計算式(類似於數學上的算術表達式)。
    舉例: 字面值(literal)和變量(variable)是最簡單的表達式,函數的返回值也被認爲是表達式。

值類別

  • 表達式是可求值的對錶達式求值將得到一個結果(result)。這個結果有兩個屬性:類型和值類別(value categories)。

  • 在c++11以後,表達式按值類別分,必然屬於以下三者之一:左值(left value,lvalue),將亡值(expiring value,xvalue),純右值(pure rvalue,pralue)。其中,左值和將亡值合稱泛左值(generalized lvalue,glvalue),純右值和將亡值合稱右值(right value,rvalue)。見下圖,

 

 

 

C++11中右值rvalue的概念包括兩個部分:

1、將亡值(xvalue. expiring value),xvalue是C++11新增的概念,與右值引用相關的表達式,
如: 將要被移動的對象、T&&函數返回值、std::move返回值和轉換爲 T&&的類型的轉換函數的返回值等; 2、純右值(pvalue, pure right value),如:非引用返回的臨時變量、運算表達式產生的臨時變量、原始字面量和lambda表達式等屬於pvalue;
注意:“左值”是表達式的結果的一種屬性,但更爲普遍地,通常用“左值”來指代左值表達式。
所謂左值表達式,就是指求值結果的值類別爲左值的表達式。在後文中,我們依然用左值指代左值表達式。對於純右值和將亡值,亦然。

 

 

左值、純右值和將亡值的描述

左值

 

  • 描述:能夠用&取地址的表達式是左值表達式。

一個區分左值和右值的便捷方法:看能不能對錶達式取地址,若能,則爲左值,若不能則爲右值。所有的具名變量都是左值,而右值是不具名的

舉例:

 

  • 函數名和變量名

  • 返回左值引用的函數調用

  • 前置自增/自減運算符連接的表達式++i/--i

  • 由賦值運算符或複合賦值運算符連接的表達式(a=b、a+=b、a%=b)

  • 解引用表達式*p

  • 字符串字面值"abc"

 

純右值

 

描述:滿足下列條件之一的:

 

  • 1)本身就是赤裸裸的、純粹的字面值,如3、false;

  • 2)求值結果相當於字面值或是一個不具名的臨時對象。

 

舉例:

 

  • 除字符串字面值以外的字面值

  • 返回非引用類型的函數調用

  • 後置自增/自減運算符連接的表達式i++/i--

  • 算術表達式(a+b、a&b、a<<b)

  • 邏輯表達式(a&&b、a||b、~a)

  • 比較表達式(a==b、a>=b、a<b)

  • 取地址表達式(&a)

 

將亡值

 

描述: 在C++11之前的右值和C++11中的純右值是等價的。C++11中的將亡值是隨着右值引用的引入而新引入的。換言之,“將亡值”概念的產生,是由右值引用的產生而引起的,將亡值與右值引用息息相關。所謂的將亡值表達式,就是下列表達式

 

  • 1)返回右值引用的函數的調用表達式

  • 2)轉換爲右值引用的轉換函數的調用表達

在C++11中,用左值去初始化一個對象或爲一個已有對象賦值時,會調用拷貝構造函數或拷貝賦值運算符來拷貝資源而當用一個右值(包括純右值和將亡值)來初始化或賦值時,會調用移動構造函數或移動賦值運算符來移動資源,從而避免拷貝,提高效率。當該右值完成初始化或賦值的任務時,它的資源已經移動給了被初始化者或被賦值者,同時該右值也將會馬上被銷燬(析構)。也就是說,當一個右值準備完成初始化或賦值任務時,它已經“將亡”了。這種右值常用來完成移動構造或移動賦值的特殊任務,扮演着“將亡”的角色,所以C++11給這類右值起了一個新的名字——將亡值。

  • 舉例:std::move()、tsatic_cast<X&&>(x)(X是自定義的類,x是類對象,這兩個函數常用來將左值強制轉換成右值,從而使拷貝變成移動,提高效率。

C++11中值的類型總結

  • gvalue: has identity

  • lvalue:  has identity and can not be moved from

  • rvalue: can be moved from

  • xvalue: has identity and can be moved from

  • prvalue: does not have identity and can be moved from

對比說明

情況1:++i是左值,而i++是右值

 

  • ++i對i加1後再賦給i,最終的返回值就是i,所以,++i的結果是具名的,名字就是i;

  • 而對於i++而言,是先對i進行一次拷貝,將得到的副本作爲返回結果,然後再對i加1,由於i++的結果是對i加1前i的一份拷貝,所以它是不具名的。假設自增前i的值是6,那麼,++i得到的結果是7,這個7有個名字,就是i;而i++得到的結果是6,這個6是i加1前的一個副本,它沒有名字,i不是它的名字,i的值此時也是7。

  • 可見,++i和i++都達到了使i加1的目的,但兩個表達式的結果不同。

 

情況2:解引用表達式*p是左值,取地址表達式&a是純右值

 

  • &(p)一定是正確的,因爲p得到的是p指向的實體,&(p)得到的就是這一實體的地址,正是p的值。由於&(p)的正確,所以*p是左值。

  • 而對&a而言,得到的是a的地址,相當於unsigned int型的字面值,所以是純右值

 

情況3:a+b、a&&b、a==b都是純右值

 

  • a+b得到的是不具名的臨時對象,而a&&b和a==b的結果非true即false,相當於字面值。

 

情況4:字符串字面值是左值,而非字符串的字面量是純右值

 

  • 不是所有的字面值都是純右值,字符串字面值是唯一例外。

  • 早期C++將字符串字面值實現爲char型數組,實實在在地爲每個字符都分配了空間並且允許程序員對其進行操作,所以類似

 

cout<<&("abc")<<endl;
char *p_char="abc";//注意不是char *p_char=&("abc");

 

情況5:具名的右值引用是左值,不具名的右值引用是右值。

 

  • 如:

 

void foo(int&& t) {
    // t 被一個右值表達式初始化
    // 但是,t 本身是一個左值
}

繼續深入學習

C++11中的右值引用和移動語義https://www.jianshu.com/p/61ea80fcf943

參考

  • Value categorieshttps://en.cppreference.com/w/cpp/language/value_category

  • 話說C++中的左值、純右值、將亡值http://www.cnblogs.com/zpcdbky/p/5275959.html

  • What are rvalues, lvalues, xvalues, glvalues, and prvalues?https://stackoverflow.com/questions/3601602/what-are-rvalues-lvalues-xvalues-glvalues-and-prvalues

  • Value categorieshttps://en.cppreference.com/w/cpp/language/value_category

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章