意義:可以避免無謂的複製,提高程序的性能。
左值:表達式結束後依然存在的持久化對象
右值:表達式結束後不再存在的臨時對象
所有的具名變量和對象都是左值,而右值不具名。
區分左值和右值的快捷方法:
看能不能對錶達式取地址,如果能則是左值,否則就是右值。
右值分爲純右值和將亡值。
純右值是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)編譯器有返回值優化功能,但不可過於依賴。