本文適合對c++11剛入門的小夥伴閱讀。
本文加深對左值引用、右值引用、左值右值的理解。
首先要理解一下將亡值:將亡值就是在這一行結束之後該值將被析構。最典型的將亡值就是匿名對象,如:Test()
。
c++11之前,有一些讓人們蛋疼的地方。
需求1:需要轉遞引用來提高效率,那麼我們函數定義是這樣的void foo(Test &t){...}
。
需求2:現在我們想這樣調用函數,foo(Test())
。what?編譯不過。稍微解釋一下,將引用綁定到一個匿名對象,完全沒有意義,因爲它可能很快就不在了。訪問一個不在的對象,是多麼恐怖的事情。
我們又想出了其他的辦法,重載函數void foo(Test t){...}
。what?還編譯不過。因爲這樣重載又二義性,編譯器不知道你到底要調用哪個。
最後沒轍了,我們只能放大招了,void foo(const Test& t){...}
只能這樣了。const
不是隻讀的意思嗎,怎麼還可以這樣用。對的const
乾的活比較多,const Test& t{Test()};
這種用法就退化回const Test t{Test()};
,這兩種寫法都會創建一個新的對象,所以const
幹了一個不屬於它的活,這樣即保障了傳引用的高效又可以傳入匿名對象。
需求3:但是我們又需要改變它的值,那好吧,我們只能用const_cast<Test&>(t)
強轉後來改變他的值。
c++11引入了右值引用來幫助const
分擔工作。
現在完全可以用void foo(Test&& t)
和void foo(Test& t)
兩個函數來區分傳入值是否是將亡值,並且可以重載,無二義性。
注意下面情況:
#include <iostream>
using namespace std;
class Test {
public:
Test() : x(0) {cout << "構造函數 this = " << this << endl;}
Test(int x) : x(x) {cout << "構造函數 this = " << this << endl;}
Test(const Test& another) : x(another.x) {cout << "拷貝構造 this = " << this << " from " << &another << endl;}
Test(const Test&& another) noexcept : x(another.x) {cout << "移動構造 this = " << this << " from " << &another << endl;}
~Test() {cout << "析構函數 this = " << this << endl;}
int x;
};
ostream& operator<<(ostream& out, const Test& t) {
out << "&t = " << &t << ", x = " << t.x;
return out;
}
class Aa {
public:
Aa(const Test& t) : t(t) { cout << t << endl; cout << this->t << endl;}
//Aa(const Test&& t) = delete; //這行就可以禁止將亡值來賦值,使編譯時報錯。
void foo() {cout << t << endl;}
private:
const Test &t;
};
int main()
{
Aa a{Test()};
a.foo();
return 0;
}
輸出:
構造函數 this = 0x61fe0c
&t = 0x61fe0c, x = 0
&t = 0x61fe0c, x = 0
析構函數 this = 0x61fe0c
&t = 0x61fe0c, x = 0
解釋:
第一步:入參const Test& t{Test()};
產生了一個臨時的匿名只讀對象,還是將亡值。
第二步:初始化列表t(t)
相當於將剛剛產生的匿名對象,賦值給成員對象&t
。
第三步:構造函數結束,匿名對象析構,之後再使用Aa
時很危險的。
將移動構造函數顯視刪除,可以避免這一點。
移動構造函數當然不只是這一點功能,它主要是在stl
中和std::move
配合提高效率。
左值引用和右值引用的區別
左值引用是起別名,如果這個對象已經析構,那麼這個別名也應該一起失效。言外之意就是左值引用一定要保證它的生命週期小於等於它被引用的對象。
當將亡值出現的時候,左值引用表示無能爲力,所以右值引用出現了。
右值引用也可以看作起名,只是它起名的對象是一個將亡值。然後延續這個將亡值的生命,直到這個的右值的生命也結束了。
除了入參時可以用到右值引用外,其他右值引用都顯得多餘。
比如Test&& t{Test()};
它和Test t{Test()}
是一樣的,甚至可以這樣Test&& t{}
它們都只一個普通對象,只調用一次構造函數。
總結
左值引用只能起別名,但不能給匿名對象起名。
右值引用其實就是給匿名(天生匿名或者通過std::move
將名字失效,這樣的對象即將被析構)對象重新起名字。
我們一直所說的將亡值其實就是所謂的右值,其它有名字的都是左值,左值引用與左值配合,右值引用與右值配合。