C++霧中風景10:聊聊左值,純右值與將亡值

post200發表於2021-09-09

C++11的版本在型別系統上下了很大的功夫,新增了諸如auto,decltype,move等新的關鍵詞來簡化程式碼的編寫與降低閱讀程式碼的難度。為了更好的理解這些新的語義,筆者確定透過幾篇文章來簡單窺探一下C++型別系統的冰山一角,如果加深了對C++型別系統的理解,想必大家也能夠更好的應用由C++11帶給我們的新"利器"。

1.左值與右值

左值(lvalue)和右值(rvalue)是C++型別系統之中的基礎概念,我們不需要了解這些基礎概念,同樣也能寫出程式碼。但是如果沒有弄清左右值的概念,對於許多C++高階特性的探索會一葉障目,所以筆者嘗試總結一下自己對於左值與右值的理解。

在C++11之前的版本,基本沿用了C語言之中對於左值與右值的定義,說起來也很簡單:“在C++之中的變數只有左值與右值兩種:其中凡是可以取地址的變數就是左值,而沒有名字的臨時變數,字面量就是右值”。 正是因為這兩種變數分別位於=的左右兩側,所以被命名為左值與右值。下面,舉個例子:

int x;
int y;

x = 1;
y = 2;
x = y;
y = x;

// 以下程式碼有誤
3 = x;
x + y = 4;

透過上述的程式碼我們可以快速的理解,顯然x,y作為變數可以存在=的左側,而稱之為左值,而3,x + y作為字面量或中間結果,沒有辦法取得地址,則稱之為右值。 這裡筆者也給一個簡單判定的左右值的方式:
判斷能否取值的地址,能取地址的就是左值。

2.將亡值

其實上一節對於左值右值的描述,在我們編寫絕大多數程式碼的場景下並沒有什麼影響。而在C++11擴充套件了右值的的概念,將右值分為了純右值(pure rvalue)將亡值(eXpiring Value)。純右值的概念等同於我們之前所理解的右值,指的是臨時變數或字面量值;而將亡值是C++11新引入的概念,它依託於右值。
圖片描述

在C++之中,使用左值去初始化物件或為物件賦值時,會呼叫複製建構函式或賦值建構函式。而使用一個右值來初始化或賦值時,會呼叫移動建構函式或移動賦值運算子來移動資源,從而避免複製,提高效率。 而將亡值可以理解為透過移動構造其他變數記憶體空間的方式獲取到的值。在確保其他變數不再被使用、或即將被銷燬時,來延長變數值的生命期。而實際上該右值會馬上被銷燬,所以稱之為:將亡值

上述概念闡述的略微抽象,我們來看下面這段程式碼:
這是我們簡單定義的Time類,在類中我們定義了複製建構函式和移動建構函式:

class Time {public:    int* hour;    int* minute;    int* second;

    Time(int h, int m, int s) {
        hour = new int(h);
        minute = new int(m);
        second = new int(s);
    }

    Time(const Time& t) {        cout 

接下來我們執行下面的程式碼:

int main(){    Time test(10,25,12);    Time test2(test);    return 0;
}

執行結果:

  copy
  call ~Time()  call ~Time()

由上述程式碼我們看到test2物件呼叫了複製建構函式來構造了新的物件,這個過程顯然是更佔用記憶體的。而接下來,我們嘗試利用move函式將test強行轉化為將亡值,來避免記憶體重新分配的過程。但是之後我們也無法再訪問test物件的內容了,因為都在移動建構函式之中置為了空指標

int main(){    Time test(10,25,12);    Time test2(move(test));    return 0;
}

執行結果:

     move
     call ~Time()     call ~Time()

透過這樣的方式來減少不必要的記憶體操作。但是之後我們也無法再訪問test物件的內容了,因為都在移動建構函式之中置為了空指標。將亡值透過移動建構函式”借屍還魂“,透過test2變數延續了自己的生命週期。

3.左值的一些"坑"

雖然筆者給出了左右值分辨的一些基本標準,但是還是有下面一些很讓人迷惑的場景:

  • 條件表示式返回左值

true ? i : i;
  • ++

i++ // 左值++i // 右值
  • []陣列取值返回左值

i[10]
  • 指標取值運算子返回左值

*i
  • 字串字面量返回左值

“hello world”

這是一些表示左值的特殊情況,這裡筆者也不展開一一贅述了,希望大家可以簡單的進行記憶。當然,筆者從來不去記一些太瑣碎的問題,因為太他喵難記了,所以在C++11之中,可以標準庫中新增的模板類is_lvalue_reference來判斷表示式是否為左值,is_rvalue_reference來判斷是否為右值。

  cout ::value ::value 

返回1則為真,0為假。

4.小結

這只是我們對C++型別系統的第一篇探討,後續筆者還會繼續深入的探討有關C++11新特性之中與型別系統相關的內容,歡迎大家多多討論,指教。


來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/2249/viewspace-2804885/,如需轉載,請註明出處,否則將追究法律責任。

相關文章