C++ 右值引用與左值引用

意義:可以避免無謂的複製,提高程序的性能。

左值:表達式結束後依然存在的持久化對象
右值:表達式結束後不再存在的臨時對象

所有的具名變量和對象都是左值,而右值不具名。
區分左值和右值的快捷方法:
看能不能對錶達式取地址,如果能則是左值,否則就是右值。

右值分爲純右值和將亡值。
純右值是C++98中的右值概念,如非引用函數返回的臨時變量;
一些運算表達式,如4+6產生的臨時變量;不和對象關聯的字面量值,
如10,‘s’,true,“hello”等這些不能被取地址的值。

將亡值:c++11中新增的和右值引用相關的表達式,這樣的表達式通常是將要移動的對象
T&&函數返回值,std::move()函數的返回值等。
將亡值和純右值統一看成右值,不影響使用。

c++98中引用很常見,就是給變量取一個別名,在c++11中,因爲增加了右值引用的概念,
所以c++98中的引用都稱爲左值引用。

int a = 10;
int &refA = a; 
int &b = 1;   //編譯錯誤,1是右值,不能使用左值引用

c++11中的右值引用使用&&符號,如

int &&a = 1;
int b = 1;
int &&c = b;  //編譯錯誤,不能將左值賦值給一個右值引用
class A
{
public:
    int a;
};
A getTemp()
{
    return A();
}
A &&a = getTemp();  //getTemp()返回值是右值(臨時變量)

getTemp()返回的右值本來在表達式語句結束後,其生命也就該終結了(臨時變量),而通過
右值引用,該右值又獲得了新生,其生命週期與右值引用類型變量a的生命一樣,只要a活着,該
右值臨時變量將會一直存活下去,實際上就是給臨時變量去了一個名字。

a的類型爲右值引用類型(int &&),如果從左值和右值的角度區分它,它實際上是一個左值。
因爲可以對它取地址,而且它還有名字,是一個已經命名的右值。

所以,左值引用只能綁定左值,右值引用只能綁定右值。常量左值引用是個特例,它可以算一個
萬能的引用類型,可以綁定非常量左值,常量左值,右值,而且在綁定右值的時候,可以像右值引用一樣
將右值的生命期延長,缺點是隻能讀不能改。例子如下:

const int &a = 1;  //常量左值引用綁定右值,不會報錯
class A
{
public:
    int a;
};
A getTemp()
{
    return A();
}
const A &a =getTemp();   //不會報錯,而A& a會報錯

實際上,我們在很多情況下都使用了常量左值引用這個功能,例子如下:

class Copyable
{
public:
    Copyable() {}
    Copyable(const Copyable &o)
    {
        std::cout << "Copied" << std::endl;
    }
};

Copyable ReturnRvalue()
{
    return Copyable(); //返回一個臨時對象
}

void AcceptVal(Copyable a)
{
}
void AcceptRef(const Copyable &a)
{
}

int main()
{
    std::cout<<"pass by value"<<std::endl;
    AcceptVal(ReturnRvalue());  //應該調用2次拷貝構造函數
    std::cout<<"pass by reference"<<std::endl;
    AcceptRef(ReturnRvalue());  //應該只調用一次拷貝構造函數
}

上述例子運行之後,結果和預想的不一樣。AcceptVal(ReturnRvalue())需要調用兩次拷貝構造函數,一次在ReturnRvalue()函數中,構造一個Copyable()對象,返回的時候會調用拷貝構造函數生成一個臨時對象。在調用AcceptVal()時,會將這個對象拷貝給函數的局部對象a,一共調用了兩次拷貝構造函數。而AcceptRef()的不同之處在於形參是常量左值引用,它能接收一個右值,而不需要拷貝。

實際的結果是,不管哪種方式,一次拷貝構造函數都沒有調用。

這是因爲編譯器開啓了返回值優化(RVO/NRVO,RVO,Return Value Optimization返回值優化;NRVO,Nameed Return Valude Optimization)。編譯器發現ReturnRvalue內部生成了一個對象,返回之後還需要生成一個臨時對象調用拷貝構造函數,很麻煩,所以直接優化成一個對象,避免拷貝,而這個臨時變量又被賦值給了函數的形參,還是沒必要,這3個變量都用一個變量代替了,不需要調用拷貝構造函數。

爲了能夠更好的觀測結果,可以在編譯的時候加上-fno-elide-constructors選項(關閉返回值優化),此時結果和預想的一樣。上述的例子是想說明常量左值可以綁定一個右值,可以減少一次拷貝(使用非常量左值引用會使失敗,因爲ReturnRvalue()返回的是臨時對象(右值))。

//g++ test.cpp -o test -fno-elide-constructors

總結:T是一個具體類型

(1)左值引用,使用T&,只能綁定左值

(2)右值引用,使用T&&,只能綁定右值

(3)常量左值,使用conts T&,可以綁定左值和右值。

(4)已命名的右值引用,編譯器會認爲是左值。

(5)編譯器有返回值優化功能,但不可過於依賴。

參考:https://www.jianshu.com/p/d19fc8447eaa

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