左值(lvalue),右值(rvalue)是一個比較晦澀的概念,有些人可能甚至沒有聽過,但這個概念到了c++11後,卻變得十分重要,它們是理解move(),forward()等新語義的基礎。 那什麼是左值右值呢? 左值與右值這兩概念是從c中傳承而來的,在c中,左值指的是能夠出現在等號左邊及右邊的變量(表達式), 右值指的是只能出現在等號右邊的變量(表達式). 復制代碼 int a; int b; a = 3; b = 4; a = b; b = a; //不合法。 3 = a; a+b = 4; 復制代碼 通常來說,有名字的變量就是左值(如上面例子中的a, b),而由運算(加減乘除,函數調用返回值等)產生的中間結果(沒有名字)就是右值,如上的3+4, a + b等。 我們暫且可以認為:左值就是在程序中能夠尋值的東西,右值就是沒法取到它的地址的東西(不完全准確)。 如上概念到了c++中,就變得稍有不同。 在c++中,每一個表達式都會產生一個左值,或者右值,相應的,該表達式也就被稱作“左值表達式”,“右值表達式”。對於內置的數據類型來說(build-in types),左值右值的概念和c的沒有太多不同,不同的地方在於自定義的類型。 而且這種不同比較容易讓人混淆: 1)對於內置的類型,右值是不可被修改的(non-modifiable),也不可被const, volatile所修飾(cv-qualitification ignored) 2)對於自定義的類型(user-defined types), 右值卻允許通過它的成員函數進行修改。 對於1),這是和c是一致的,2)卻是c++中所獨有, 因此,如果你看到c++中如下的寫法,也許有會些驚訝: 復制代碼 class cs { public: cs(int i): i_(i) { cout << "cs(" << i <<") constructor!" << endl; } ~cs() { cout << "cs destructor,i(" << i_ << ")" << endl; } cs& operator=(const cs& other) { i_ = other.i_; cout << "cs operator=()" << endl; return *this; } int get_i() const { return i_; } void change(int i) { i_ = i; } private: int i_; }; cs get_cs() { static int i = 0; return cs(i++); } int main() { // 合法 (get_cs() = cs(2)).change(323); get_cs() = cs(2);// operator=() get_cs().change(32); return 0; } 復制代碼 這個特性多少有些奇怪,通常來說,c++中的自定義類型是應該設計地盡量和內置類型一樣才對的,但這個特性卻偏偏違背了這個原則。 對於這個特性,我們其實可以這樣想,也許會好理解點:自定義類型允許有成員函數,而通過右值調用成員函數是被允許的,但成員函數有可能不是const類型,因此通過調用右值的成員函數,也就可能會修改了該右值,done! 關於右值,還有一個需要注意的地方是:右值能被const類型的引用所指向 const cs& ref = get_cs(); 而且只能被const 類型的reference所指向: //error cs& ref = get_cs(); 當一個右值被const reference指向時,它的生命周期就被延長了,這個用法我在前面一篇博客裡講到過它的相關應用,點這。 這裡暗藏邏輯其實就是:右值不能直接轉化成左值(但左值可以轉化為右值). 上面提到的這兩個特性: 1)允許調用成員函數。 2)只能被const reference指向。 導致了一些比較有意思的結果,比如: 復制代碼 void func(cs& c) { cout << "c:" << c.get_i() << endl; } //error func(get_cs()); //正確 func(get_cs() = get_cs()); 復制代碼 其中:func(get_cs() = get_cs());能夠正確的原因就在於,cs的成員函數operator=() 返回的是cs&! 不允許非const reference 引用rvalue並不是完美的,它事實上也引起了一些問題,比如說拷貝構造函數的接口不一致了,這是什麼意思呢? 復制代碼 class cs { public: cs& operator=(const cs& c); }; // 另一種寫法 class cs2 { public: cs2& operator=(cs2& c); }; 復制代碼 上面兩種寫法的不同之處就在於參數,一個是const reference,一個是非const. 通常來說,如果不需要修改傳進來的參數,我們往往就按const reference的寫法,但對於copy constructor來說,經常是需要修改參數的值,比如auto_ptr。 復制代碼 // 類似auto_ptr class auto_ptr { public: auto_ptr(auto_tr& p) { ptr_ = p.ptr_; p.ptr_ = NULL; } private: void* ptr_; }; 復制代碼 所以,對於auto_ptr來說,它的copy constructor傳的參數是non const reference。 這個種寫法本來應該被鼓勵的,non const reference比const reference更能靈活應對各種情況,從而保持一致的接口類型。 但如果拷貝構造函數寫成這樣子,卻又對rvalue的使用帶來了極大的不變,如前面所講的例子,rvalue不能被non const reference所引用,所以像auto_ptr的這樣的copy constructor就不能傳入rvalue. //錯誤 auto_ptr p(get_ptr()); // operator=() 同理,錯誤。 auto_ptr p = get_ptr(); 這也是auto_ptr很不好用的其中一個原因。 為了解決這個問題,c++11中引入了一種新的引用類型,該種引用類型是專門用來指向rvalue的,有了這種新類型,在c++11中,lvalue 和rvalue的引用類型從此區分了開來,而在之前,它們是一樣的。 因為有了這種新的類型,接著就引出了c++11中新的語義,move(), forward()等,這兒先賣個關子,我們下次再講。