右值引用作用是可以減少內存拷貝次數,從而優化性能。
首先,什麼是右值?右值是一個與左值相區分的概念。左值是:既能出現在等號左邊也能出現在等號右邊的變量或表達式,比如int a = 5,那麼a就是一個左值,因爲它可以出現在等號左邊被賦值,也可以在等號右邊給別人賦值。右值:因爲聲明結束後會被銷燬,所以不能放在等號左邊,比如上面int a = 5;這句話的5,就是一個明顯的右值。
我們知道複製構造函數調用的一個條件,就是函數返回值是一個類對象的時候。比如說下面的場景:
String test()
{
String teststr = "this is a test";//調用拷貝構造函數
return teststr;//返回的時候同樣調用拷貝構造函數
}
它的返回值是一個類string的對象。在返回teststr的時候,會調用拷貝構造函數,在內存空間中再次分配一塊空間存放類對象。但是這種拷貝構造是完全沒有必要的,因爲作爲一個teststr在實際中並沒有派上用場。如果堆內存很大,就可能出現額外的內存消耗,所以我們能不能想個辦法避免這多餘的一次拷貝呢?答案就是右值引用。右值引用避免了拷貝構造函數的調用,使得新構造出來的對象直接使用原來臨時右值對象的地址空間,避免了內存空間重複復製造成的浪費。
MyString& operator=(MyString&& other)//&&爲右值引用
{
if (*this != other){
m_nLen = other.m_nLen;
m_pData = other.m_pData;
other.m_pData = NULL;
}
return *this;
}
利用右值引用,相當於只進行了指針的轉移,並沒有真正進行復制。避免無意義的複製,使得被移走資源的右值在廢棄時已經成爲空殼,析構的開銷也會降低。
2. 移動語義
移動語義和移動構造函數相關聯,同樣避免的是內存重複複製帶來的性能損耗。當函數返回一個類對象時,臨時對象會析構掉之前我們又重新申請與其相同內存且複製內容,而移動構造函數避免了這個過程,從而減少複製次數。複製構造函數和移動構造函數對比如下圖所示:
#include <iostream>
using namespace std;
class HasPtrMem
{
public:
HasPtrMem()//構造函數
{
cout << "Construct! "<< endl;
}
HasPtrMem(const HasPtrMem & h)//複製構造函數
{
cout << "Copy construct! "<< endl;
}
HasPtrMem(HasPtrMem&& h) // 移動構造函數
{
cout << "Move construct! "<< endl;
}
~ HasPtrMem()
{
cout << "Destruct! " << endl;
}
};
HasPtrMem GetTemp()
{
return HasPtrMem();
}
int main()
{
HasPtrMem a = GetTemp();
}
/*
Construct! // 調用構造函數,構造對象h
Move construct! // 由對象h,調用移動構造函數,構造臨時對象
Destruct! // 析構掉對象h
Move construct! // 由臨時對象,調用移動構造函數,構造對象a
Destruct! // 析構掉臨時對象
Destruct! // 析構掉對象a
*/
我們可以發現對於一個臨時對象HasPtrMem()生成之後,如果調用拷貝構造函數,那麼這個a對象和HasPtrMem()實際上是不同地址的,因爲這是拷貝構造函數進行的一次操作;而當有了移動構造函數之後,實際上就不會調用拷貝構造函數,而會調用移動構造函數,那麼用移動構造函數生成的對象a的地址,實際上就是那個臨時對象HasPtrMem()的地址,這樣避免新開闢內存空間,而這個臨時對象的內存,也不會因爲這句話的結束而被析構掉。所以移動語義實際上和右值引用相輔相成,共同完成減少內存拷貝次數,節省堆空間,優化性能的作用。
3. move()語義:
因爲右值引用是通過移動構造函數直接指向臨時對象的內存空間從而達到節省內存空間的作用,而臨時對象往往是右值,也就是右值引用。那麼左值可不可以借鑑這個方法呢?實際上也是可以的,這就是move()函數的功能:std::move()實際上把一個左值轉換成右值,從而方便使用移動語義。move()只是把左值轉換成右值,不涉及到內存拷貝。
MyString str1 = "hello";
MyString str2(str1);//複製構造函數,調用MyString(const MyString &str)
MyString str3 = move(str2);//移動語義,調用MyString(MyString && str);