右值引用是C++11中新增的一種新的引用類型,它可以通過減少內(nèi)存的重復(fù)申請(qǐng)、拷貝和釋放,有效的提高C++程序的性能。理解什么是右值引用,首先要理解C++11中的lvalue、rvalue、xvalue,詳情請(qǐng)參考:C++11 中的左值、右值和將亡值.
右值引用和非常量引用的唯一區(qū)別是,非常量引用(non-const reference)通常綁定一個(gè)非臨時(shí)變量,而右值引用通常綁定一個(gè)臨時(shí)變量。
一個(gè)常量引用(const reference)既可以綁定臨時(shí)變量,也可以綁定非臨時(shí)變量。正是因?yàn)槌A恳?const reference)的這個(gè)特性,當(dāng)類的聲明中沒有移動(dòng)構(gòu)造函數(shù)時(shí),即A(A&& rhs){},傳遞臨時(shí)變量,任然可以調(diào)用A(const A& rsh){}構(gòu)造函數(shù)進(jìn)行構(gòu)造。
右值引用,避免深拷貝
一個(gè)深拷貝的例子
class A
{
public:
A() : m_ptr(new int(0))
{
cout << "construct" << endl;
}
A(const A& rhs) : m_ptr(new int(*rhs.m_ptr)) // 拷貝構(gòu)造函數(shù)
{
cout << "copy construct" << endl;
}
~A()
{
cout << "destruct" << endl;
delete m_ptr;
}
private:
int* m_ptr;
};
A Get(bool flag)
{
A a, b;
if (flag)
{
return a;
}
return b;
}
int main()
{
A a = Get(false);//調(diào)用拷貝構(gòu)造函數(shù)
return 0;
}
執(zhí)行結(jié)果:
construct
construct
copy construct
destruct
destruct
destruct
問題:上述例子中Get函數(shù)會(huì)返回臨時(shí)變量,然后通過這臨時(shí)變量拷貝構(gòu)造一個(gè)新的對(duì)象a,臨時(shí)變量在拷貝構(gòu)造完成之后就銷毀了,如果堆內(nèi)存很大,這個(gè)拷貝構(gòu)造就比較消耗時(shí)間,帶來(lái)性能的損耗,是否有方法避免臨時(shí)對(duì)象的拷貝構(gòu)造呢?
答案:右值引用
改進(jìn)深拷貝的例子
在class A中添加如下移動(dòng)構(gòu)造函數(shù)
A(A&& rhs) : m_ptr(rhs.m_ptr) //移動(dòng)構(gòu)造函數(shù)
{
rhs.m_ptr = nullptr;
cout << "move construct" << endl;
}
執(zhí)行結(jié)果:
construct
construct
move construct
destruct
destruct
destruct
- 改進(jìn)之后,不再調(diào)用拷貝構(gòu)造函數(shù),而是調(diào)用了移動(dòng)構(gòu)造函數(shù)(move construct)。從移動(dòng)構(gòu)造函數(shù)的實(shí)現(xiàn)可以看到,它的參數(shù)是一個(gè)右值引用類型的參數(shù)T&&,這里沒有深拷貝,只有淺拷貝,這樣就避免了對(duì)臨時(shí)對(duì)象的深拷貝,和資源的重復(fù)申請(qǐng)和釋放,提高了性能;
- Get(false)函數(shù)返回的是一個(gè)右值,因此調(diào)用了參數(shù)類型為A&&的移動(dòng)構(gòu)造函數(shù);
- 移動(dòng)構(gòu)造函數(shù)只是將臨時(shí)對(duì)象的資源做了淺拷貝,從而避免了深拷貝的開銷,提高了性能;
-
這就是所謂的移動(dòng)語(yǔ)義(move semantic),右值引用的一個(gè)重要目的就是支持移動(dòng)語(yǔ)義;
移動(dòng)構(gòu)造和拷貝構(gòu)造的內(nèi)存變化情況如下圖所示:
image.png
假設(shè)對(duì)象A中有一個(gè)指向堆內(nèi)存的指針m_ptr,則在拷貝構(gòu)造B時(shí),B會(huì)重新申請(qǐng)一份和A相同的內(nèi)存并拷貝A.m_ptr內(nèi)存中的內(nèi)容,然后釋放A.m_ptr。
image.png
若使用移動(dòng)構(gòu)造,則B只需要先將B.m_ptr=A.m_ptr,再將A.m_ptr設(shè)置為nullptr即可,這樣就避免了內(nèi)存的重新申請(qǐng)、拷貝和釋放。
左值引用和右值引用
- 右值引用就是對(duì)一個(gè)右值進(jìn)行引用的類型,因?yàn)橛抑挡痪呙虼酥荒芡ㄟ^引用的方式找到它;
- 通過右值引用的聲明,該右值又“重獲新生”,其聲明周期與右值引用類型變量的生命周期一樣;
- 無(wú)論聲明為右值引用還是左值引用都必須立即初始化;
右值引用主要為了解決下面兩個(gè)問題
- 移動(dòng)語(yǔ)義 (move semantic)
- 完美轉(zhuǎn)發(fā) (perfect forwarding)
移動(dòng)語(yǔ)義(move semantic)
問題: 移動(dòng)語(yǔ)義是通過右值引用來(lái)匹配臨時(shí)值的,那么,普通的左值是否也能借助移動(dòng)語(yǔ)義來(lái)優(yōu)化性能呢?
- C++11 為了解決上述問題,提供了std::move方法來(lái)將左值轉(zhuǎn)換為右值,從而方便應(yīng)用移動(dòng)語(yǔ)義;
- move實(shí)際上并不能移動(dòng)任何東西,他唯一的功能是將一個(gè)左值強(qiáng)制轉(zhuǎn)換為一個(gè)右值引用,使我們可以通過右值引用使用該值;
- It's confusing, because "move" is a verb; it sounds like it should do something. But in fact it's just a cast, just like const_cast or reinterpret_cast. Like those casts, std::move just lets us use an object in a different way; on its own, it doesn't do anything.
模擬string中移動(dòng)構(gòu)造函數(shù)的例子
class string {
public:
string(const string& other); // Copy constructor, exists pre C++11
string(string&& other) { // Move constructor, new in C++11
length = other.length;
capacity = other.capacity;
data = other.data;
other.data = nullptr;
}
private:
size_t length;
size_t capacity;
const char* data;
};
- 移動(dòng)構(gòu)造函數(shù)的處理流程:① 獲取臨時(shí)變量other中的指針、拷貝其他非指針成員,② 將臨時(shí)對(duì)象的指針設(shè)置為空,
other.data = nullptr;
。若不將other.data設(shè)置為空,other.data 將被釋放兩次,導(dǎo)致程序異常。下面是使用string的例子,當(dāng)使用臨時(shí)變量構(gòu)造時(shí),調(diào)用移動(dòng)構(gòu)造函數(shù)。
string a(get_string()); // move constructor
string b(a); // copy constructor
string c(std::move(b)); // move constructor
注意:若string中沒有移動(dòng)構(gòu)造函數(shù)string(string&& other),上述的
string c(std::move(b));
仍然將調(diào)用拷貝構(gòu)造函數(shù)進(jìn)行構(gòu)造,因?yàn)?code>string(const string& other)的參數(shù)是const reference。
模擬string中的賦值操作符
通過上面的分析,你肯定發(fā)現(xiàn)這種寫法也可以應(yīng)用到string的賦值操作符上,代碼如下:
class string {
public:
string& operator=(const string& other); // Copy assn operator, pre C++11
string& operator=(string&& other) { // Move assn operator, new in C++11
length = other.length;
capacity = other.capacity;
delete data; // OK even if data is null
data = other.data;
other.data = nullptr;
return *this;
}
};
string a, b;
a = get_string(); // Move assignment
a = b; // Copy assignment
a = std::move(b); // Move assignment
C++11中,編譯器將會(huì)在類中自動(dòng)添加移動(dòng)構(gòu)造函數(shù)和移動(dòng)賦值操作符,因此C++11中,聲明一個(gè)類,編譯器會(huì)默認(rèn)添加構(gòu)造函數(shù)、拷貝構(gòu)造函數(shù)、析構(gòu)函數(shù)、移動(dòng)構(gòu)造函數(shù)和移動(dòng)賦值操作,號(hào)稱類中的"5駕馬車",而不是C++11之前的“3駕馬車”。
若用戶自定義拷貝構(gòu)造函數(shù),編譯器將不會(huì)自動(dòng)添加移動(dòng)構(gòu)造函數(shù)和移動(dòng)賦值操作。可以在這些函數(shù)之后添加 = delete,顯示的阻止編譯器自動(dòng)添加對(duì)應(yīng)的函數(shù)。
完美轉(zhuǎn)發(fā) (perfect forwarding)
轉(zhuǎn)下一篇C++11 模板推導(dǎo)、引用折疊和完美轉(zhuǎn)發(fā)。