C++霧中風(fēng)景10:聊聊左值,純右值與將亡值

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

1.左值與右值

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

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

int x;
int y;

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

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

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

2.將亡值

其實上一節(jié)對于左值右值的描述,在我們編寫絕大多數(shù)代碼的場景下并沒有什么影響。而在C++11擴展了右值的的概念,將右值分為了純右值(pure rvalue)將亡值(eXpiring Value)。純右值的概念等同于我們之前所理解的右值,指的是臨時變量或字面量值;而將亡值是C++11新引入的概念,它依托于右值。

左值,純右值與將亡值

在C++之中,使用左值去初始化對象或為對象賦值時,會調(diào)用拷貝構(gòu)造函數(shù)或賦值構(gòu)造函數(shù)。而使用一個右值來初始化或賦值時,會調(diào)用移動構(gòu)造函數(shù)或移動賦值運算符來移動資源,從而避免拷貝,提高效率。 而將亡值可以理解為通過移動構(gòu)造其他變量內(nèi)存空間的方式獲取到的值。在確保其他變量不再被使用、或即將被銷毀時,來延長變量值的生命期。而實際上該右值會馬上被銷毀,所以稱之為:將亡值。

上述概念闡述的略微抽象,我們來看下面這段代碼:
這是我們簡單定義的Time類,在類中我們定義了拷貝構(gòu)造函數(shù)和移動構(gòu)造函數(shù):

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 << "copy" << endl;
        hour = new int(*t.hour);
        minute = new int(*t.minute);
        second = new int(*t.second);
    }

    Time(Time&& t) noexcept:hour(t.hour),minute(t.minute),second(t.second) {
        t.hour = nullptr;
        t.minute = nullptr;
        t.second = nullptr;
        cout << "move" << endl;
    }

    ~Time() {
        cout << "call ~Time()" << endl;
        delete hour;
        delete minute;
        delete second;
    }

};

接下來我們執(zhí)行下面的代碼:

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

執(zhí)行結(jié)果:

  copy
  call ~Time()
  call ~Time()

由上述代碼我們看到test2對象調(diào)用了拷貝構(gòu)造函數(shù)來構(gòu)造了新的對象,這個過程顯然是更占用內(nèi)存的。而接下來,我們嘗試利用move函數(shù)將test強行轉(zhuǎn)化為將亡值,來避免內(nèi)存重新分配的過程。但是之后我們也無法再訪問test對象的內(nèi)容了,因為都在移動構(gòu)造函數(shù)之中置為了空指針。

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

執(zhí)行結(jié)果:

     move
     call ~Time()
     call ~Time()

通過這樣的方式來減少不必要的內(nèi)存操作。但是之后我們也無法再訪問test對象的內(nèi)容了,因為都在移動構(gòu)造函數(shù)之中置為了空指針。將亡值通過移動構(gòu)造函數(shù)”借尸還魂“,通過test2變量延續(xù)了自己的生命周期。

3.左值的一些"坑"

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

  • 條件表達式返回左值
true ? i : i;
  • ++
i++ // 左值
++i // 右值
  • []數(shù)組取值返回左值
i[10]
  • 指針取值操作符返回左值
*i
  • 字符串字面量返回左值
“hello world”

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

  cout << is_lvalue_reference<decltype(i[10])>::value << endl;
  cout << is_rvalue_reference<decltype(i[10])>::value << endl;

返回1則為真,0為假。

4.小結(jié)

這只是我們對C++類型系統(tǒng)的第一篇探討,后續(xù)筆者還會繼續(xù)深入的探討有關(guān)C++11新特性之中與類型系統(tǒng)相關(guān)的內(nèi)容,歡迎大家多多討論,指教。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容