本文大部分來自這裡,並不是完全著行翻譯,如有不明白的地方請參考原文。
在c++中,創建臨時對象的開銷對程序的影響一直很大,比如以下這個例子:
name = getName();
name對象的構建可以細分為3步:
1. 用kian構建函數內的局部string對象tmp1
2. 調用復制構造函數將tmp1復制到tmp2,並析構tmp1.
3. 調用賦值拷貝函數將tmp2拷貝到name,並析構tmp2。
所以一共做了3次內存分配,兩次復制拷貝操作,但是tmp1和tmp2都馬上析構了,如果內存分配很大的話,這裡的資源浪費是很可觀的。在c++11之前,編譯器已經會做一些優化了,比如返回值優化RVO(return value optimization)優化了第二步,省略了構建tmp2的開銷,但是第3步直到c++11引入移動語義後才得到了徹底解決。移動語義依賴於右值引用實現,要了解移動語義,必須先要明白什麼是右值和左值。
c++11中存在右值和左值。左值可以取地址,是相對永久的對象,可以被賦值,比如
=; //a a lvalue
左值也可以不是變量,如
&= ;
相應地,右值是一個臨時對象,不可以取地址。
getRef()的值是個右值,它不是x的引用,而是x的拷貝,是一個臨時存在的對象。
在c++11之前,可以使用const引用綁定到臨時對象上,
& name = getName(); & name = getName();
不可以改變一個即將消失的對象,所以將非const引用綁定到臨時對象上是不允許的。到了c++11,引進了右值引用&&,
&& name = getName(); && name = getName();
左值和右值最重要的區別在於做為函數參數時,
printReference ( String&<<&&<<
前者可以接受任何參數,而後者只可以接收右值,及臨時對象,
me( printReference( getName() );
移動構造函數接收一個臨時對象作為參數,直接獲取臨時對象內部資源,避免重新分配內存。
假設我們有這樣一個簡單的ArrayWrapper類:
ArrayWrapper ( ArrayWrapper& ( i = ; I < _size; ++=~ *
可以看出復制構造函數每次都需要分配內存並著個賦值,這是非常消耗資源的,我們加一個移動構造函數,
ArrayWrapper (ArrayWrapper&&==
移動構造函數比復制構造函數簡單多了,不過要注意兩點,
1. 參數必須是非const右值引用
2. 必須將other._p_vals設為NULL
參數不設為非const,就不能將other._p_vals設為NULL,為什麼要設為NULL?,因為 other是一個即將消失的右值,調用其析構函數會釋放_p_vals指向的內存,不設置為NULL,我們得到的對象就會指向垃圾內存。
因為參數是非const的,不能接收const右值,所以千萬不要這樣返回需要使用的右值。
const ArrayWrapper getArrayWrapper (); // makes the move constructor useless, the temporary is const!
如果對象內部包含另一個對象,移動構造函數內會發生什麼?假設ArrayWrapper包含的不只有_size,而是更詳細的數據,比如
size, std::& MetaData ( MetaData& MetaData (MetaData&& getName () { getSize () {
那麼ArrayWrapper需要修改成這樣,
[ ] ), _metadata( , [ n ] ), _metadata( n, ArrayWrapper (ArrayWrapper&&= ArrayWrapper ( ArrayWrapper& ( i = ; i< _metadata.getSize(); ++=~ *
當 ArrayWrapper調用移動構造函數時,_metadata調用的是移動構造函數還是復制構造函數?表面看應該是移動構造函數,因為ArrayWrapper的移動構造函數參數other 是右值引用,但要注意的是,右值引用不是右值!右值引用是一個左值,other._metadata也是一個左值,所以_metadata調用的是復制構造函數。
怎樣讓_metadata也調用移動構造函數,我們需要使用std::move,move並不移動任何東西,只是將對象轉換為右值。使用move後,代碼就是這樣的,
ArrayWrapper (ArrayWrapper&&=&&
move的功能很神奇吧,它是用什麼新技術將對象轉為右值的呢?事實是它用的是c++一直都有的static_cast轉換符。下面是它的源碼,
template <typename _Tp> <P>inline typename ;std::remove_reference<_Tp>:::type&&&&& static_cast<typename std::remove_reference<_Tp>::type&&> (__t);}
看到它的第一感覺是它應該只能接收右值引用啊,為什麼左值沒有問題?
= std:move((“zhang”)); s2 = std:move(s1);
通常我們不能將右值引用綁定到左值上,不過為了支持move語義,c++11定義了兩個例外:
1. 當將左值引用傳遞給函數的右值引用參數,且此右值引用指向模板類型參數(如_Tp&&),編譯器推斷模板類型參數為實參的左值引用類型。即在std:move(s1)中,
_Tp推斷為string&,那麼string& &&又是什麼?這由第二條確定例外定義
2. 引用的引用形成折疊,x& &,x& &&, x&& &都折疊成x&;只有x&& &&折疊成x&&。
所以std:move(s1)會實例化
string&& move(string& t)
而std:move(string(“zhang”))實例化
string&& move(string&& t)
參考:
http://www.cprogramming.com/c++11/rvalue-references-and-move-semantics-in-c++11.html
http://stackoverflow.com/questions/12953127/what-are-copy-elision-and-return-value-optimization