在c++ 11之前,我們所說的引用只有左值引用,現在我們通常所說的引用也是左值引用,但是準確一點說,c++11之後,除了左值引用,還有右值引用。
在之前所謂左值就是表達式左邊的值,所謂右值就是表達式右邊的值,比如:
int a = 10;
//其中 a就是左值, 10就是右值
int b = a;
//其中 b是左值,a 是右值
在c++11之後,又有了明確的定義:
L-value(左值):是指可尋址的,L指的是location,Avalue (computer science)that has an address
R-value(右值):其中的R指的是Read,in computer science, a value that does not have an address in a computer language.
左值,指的是如果一個表達式可以引用到某一個對象,並且這個對象是一塊內存空間且可以被檢查和存儲,那麼這個表達式就可以作爲一個左值。
右值,指的是引用了一個存儲在某個內存地址裏的“數據”。
上面的兩個定義可以看出,左值其實要引用一個對象,而一個對象在我們的程序中又肯定有一個名字或者可以通過一個名字訪問到,所以左值又可以歸納爲:左值表示程序中必須有一個特定的名字引用到這個值。而右值引用的是地址裏的內容,所以相反右值又可以歸納爲:右值表示程序中沒有一個特定的名字引用到這個值除了用地址。
理解了上面的定義之後,再理解左值引用和右值引用就好理解了,左值引用也就是我們之前所說的引用是給變量起了一個別名,而右值引用的實際上是對“數據”的引用,比如:
int a = 10;
int& b = a; //b是a 的別名
int& c = 10 //編譯錯誤,因爲 10是右值(沒有變量聲明)
const int& c = 10 //編譯成功,對於右值的左值引用,必須是const的方式
int&& e = 10;//正確
int&& f = std::move(c); //正確,c的地址和f的地址是一樣的
int& m=a++;//錯誤,因爲 a++會返回(10)一個新的右值,將 a值變爲11,存到a裏面
int&& k = a++; //正確,k和a的地址是不同的
通過上面的例子可以看出,右值引用只能綁定到將要銷燬的對象上,不能綁定左值,比如:
int a = 10;
int&& c = a;//錯誤
int&& d = std::move(a);//正確,std::move()實際上只是做了一個類型轉換而已,返回一個右值,
右值引用的作用:
1. 爲了提高效率,在我們進行變量賦值的時候,有的時候會調用複製構造函數,複製構造函數往往是將對象裏面保存的內容複製了一份,然後再把之前的變量銷燬(析構函數),這樣就花了很昂貴的代價:
class A
{
public:
A(int a):iA(a){}
A(const A& a) //複製構造函數,如果沒有自己寫,會默認有一個
{
this->ptr = a.ptr;
this->iA = iA;
}
A& operator=(const A& a) //賦值函數,如果沒有自己寫一個覆蓋,也有默認一個
{
this->ptr = a.ptr;
this->iA = a.iA;
}
~A()
{
ptr = nullptr;
}
private:
int iA;
object* ptr;
}
在上面的類中,
A a;
A b(a); //調用複製構造函數
A&& c = std::move(a); //不會調用複製構造函數
通過右值引用,可以節約時間和空間成本。
2. 實現對象移動(std::move())
通過看std::move的源代碼,就發現,實際上只是對引用對象進行了類型轉換而已,並沒有做實際的內存和複製操作,std::move()會返回一個右值,付給一個新的變量,最簡單的實現就是swap:
//老的方式
void swap(T& a, T& b)
{
T tmp(a); //複製一份
b = a; //調用賦值函數(assignment operator),
a = tmp; //調用賦值函數(assignment operator)
}
//新的方式
void swap(T& a, T& b)
{
T&& tmp = std::move(a);
a = std::move(b);
b = std::move(tmp);
}