http://blog.sina.com.cn/s/blog_53b7ddf00101p5t0.html
std::move是一個用于提示優化的函數,過去的c++98中,由于無法將作為右值的臨時變量從左值當中區別出來,所以程序運行時有大量臨時變量白白的創建后又立刻銷毀,其中又尤其是返回字符串std::string的函數存在最大的浪費。
比如:
1 std::string fileContent = “oldContent”;
2 s = readFileContent(fileName);
因為并不是所有情況下,C++編譯器都能進行返回值優化,所以,向上面的例子中,往往會創建多個字符串。readFileContent如果沒有內部狀態,那么,它的返回值多半是std::string(const std::string的做法不再被推薦了),而不是const std::string&。這是一個浪費,函數的返回值被拷貝到s中后,棧上的臨時對象就被銷毀了。
在C++11中,編碼者可以主動提示編譯器,readFileContent返回的對象是臨時的,可以被挪作他用:std::move。
將上面的例子改成:
1 std::string fileContent = “oldContent”;
2 s = std::move(readFileContent(fileName));
后,對象s在被賦值的時候,方法std::string::operator =(std::string&&)會被調用,符號&&告訴std::string類的編寫者,傳入的參數是一個臨時對象,可以挪用其數據,于是std::string::operator =(std::string&&)的實現代碼中,會置空形參,同時將原本保存在中形參中的數據移動到自身。
不光是臨時變量,只要是你認為不再需要的數據,都可以考慮用std::move移動。
比較有名的std::move用法是在swap中:
![【轉】[C/C++]關于C++11中的std::move和std::forward 復制代碼](http://common.cnblogs.com/images/copycode.gif)
1 template
2 void swap(T& a, T& b)
3 {
4 T t(std::move(a)); // a為空,t占有a的初始數據
5 a = std::move(b); // b為空, a占有b的初始數據
6 b = std::move(t); // t為空,b占有a的初始數據
7 }
![【轉】[C/C++]關于C++11中的std::move和std::forward 復制代碼](http://common.cnblogs.com/images/copycode.gif)
總之,std::move是為性能而生的,正式因為了有了這個主動報告廢棄物的設施,所以C++11中的STL性能大幅提升,即使C++用戶仍然按找舊有的方式來編碼,仍然能因中新版STL等標準庫的強化中收益。
?
std::forward是用于模板編程中的,如果不需要編寫通用的模板類和函數,可能不怎么用的上它。
要認識它的作用,需要知道C++中的幾條規則:(這里有篇挺好的文章:http://blog.csdn.net/zwvista/article/details/6848582,但似乎因標準的更新,其中的規則已不完全成立了)
1.?
X& + & => X&
X&& + & => X&
X& + && => X&
X&& + && => X&&
2. 對于模板函數中的形參聲明T&&(這里的模板參數T,最終推演的結果可能不是一個純類型,它可能還會帶有引用/常量修飾符,如,T推演為const int時,實際形參為const int &&),會有如下規則:
如果調用函數時的實參為U&(這里的U可能有const/volatile修飾,但沒有左/右引用修飾了),那么T推演為U&,顯然根據上面的引用折疊規則,U& &&=>U&。
如果調用實參為U&&,雖然將T推導為U&&和U都能滿足折疊規則(U&& &&=> U&&且U &&=>U&&),但標準規定,這里選擇將T推演為U而非U&&。
總結一下第2條規則:當形參聲明為T&&時,對于實參U&,T被推演為U&;當實參是U&&時,T被推演為U。當然,T和U具有相同的const/volatile屬性。
3.這點很重要,也是上面zwvista的文章中沒有提到的:形參T&& t中的變量t,始終是左值引用,即使調用函數的實參是右值引用也不例外。可以這么理解,本來,左值和右值概念的本質區別就是,左值是用戶顯示聲明或分配內存的變量,能夠直接用變量名訪問,而右值主要是臨時變量。當一個臨時變量傳入形參為T&& t的模板函數時,T被推演為U,參數t所引用的臨時變量因為開始能夠被據名訪問了,所以它變成了左值。這也就是std::forward存在的原因!當你以為實參是右值所以t也應該是右值時,它跟你開了個玩笑,它是左值!如果你要進一步調用的函數會根據左右值引用性來進行不同操作,那么你在將t傳給其他函數時,應該先用std::forward恢復t的本來引用性,恢復的依據是模板參數T的推演結果。雖然t的右值引用行會退化,變成左值引用,但根據實參的左右引用性不同,T會被分別推演為U&和U,這就是依據!因此傳給std::forward的兩個參數一個都不能少:std::forward(t)。
?
再來,討論一下,一個模板函數如果要保留參數的左右值引用性,為什么應該聲明為T&&:
如果聲明函數f(T t):實參會直接進行值傳遞,失去了引用性。
如果聲明函數f(T &t): 根據引用折疊法則,無論T是U&還是U&&,T&的折疊結果都只會是U&,即,這個聲明不能用于匹配右值引用實參。
如果聲明函數f(T &&t): 如果T為U&,T&&的結果是U&,可以匹配左值實參;如果T為U&&,T&&的結果是U&&,可以匹配右值實參。又因為T的cv性和U相同,所以這種聲明能夠保留實參的類型信息。
?
先來看一組幫助類:
1 template struct TypeName { static const char *get(){ return "Type"; } };
2 template struct TypeName<<SPAN style="PADDING-BOTTOM: 0px; LINE-HEIGHT: 1.5; MARGIN: 0px; PADDING-LEFT: 0px; PADDING-RIGHT: 0px; FONT-FAMILY: 'Courier New'; COLOR: rgb(0,0,255); FONT-SIZE: 12px; PADDING-TOP: 0px">const T> { static const char *get(){ return "const Type"; } };
3 template struct TypeName { static const char *get(){ return "Type&"; } };
4 template struct TypeName<<SPAN style="PADDING-BOTTOM: 0px; LINE-HEIGHT: 1.5; MARGIN: 0px; PADDING-LEFT: 0px; PADDING-RIGHT: 0px; FONT-FAMILY: 'Courier New'; COLOR: rgb(0,0,255); FONT-SIZE: 12px; PADDING-TOP: 0px">const T&> { static const char *get(){ return "const Type&"; } };
5 template struct TypeName { static const char *get(){ return "Type&&"; } };
6 template struct TypeName<<SPAN style="PADDING-BOTTOM: 0px; LINE-HEIGHT: 1.5; MARGIN: 0px; PADDING-LEFT: 0px; PADDING-RIGHT: 0px; FONT-FAMILY: 'Courier New'; COLOR: rgb(0,0,255); FONT-SIZE: 12px; PADDING-TOP: 0px">const T&&> { static const char *get(){ return "const Type&&"; } };
在模板函數內部將模板參數T傳給TypeName,就可以訪問T的類型字符串:TypeName::get()。
?
再一個幫助函數,用于打印一個表達式的類型:
1 template
2 void printValType(T &&val)
3 {
4 cout << TypeName::get() << endl;
5 }
注意3條規則在這個模板函數上的應用。規則1,解釋了T&& val的聲明足以保留實參的類型信息。規則2,說明了,當實參是string&時,T就是string&;當實參是const string&&時,T就是const string(而非const string&&)。規則3,強調,無論實參是string&還是string&&,形參val的類型都是string&!
注意TypeName的寫法,因為T只能為U&或者U,顯然T&&可以根據折疊法則還原為實參類型U&和U&&。
?
這里是常見的const/左右引用組合的情形:
1 class A{}; // 測試類
2 A& lRefA() { static A a; return a;} // 左值
3 const A& clRefA() { static A a; return a;} // 常左值
4 A rRefA() { return A(); } // 右值
5 const A crRefA() { return A(); } // 常右值
測試一下上面的表達式類型:
1 printValType(lRefA());
2 printValType(clRefA());
3 printValType(rRefA());
4 printValType(crRefA());
輸出依次是: Type&,const Type&,Type&&,const Type&&。
?
現在正式來探討std::forward的實現。
回顧一下使用std::forward的原因:由于聲明為f(T&& t)的模板函數的形參t會失去右值引用性質,所以在將t傳給更深層函數前,可能會需要回復t的正確引用行,當然,修改t的引用性辦不到,但根據t返回另一個引用還是可以的。恰好,上面的函數printValType是一個會根據實參類型不同,作出不同反映的函數,所以可以把它作為f的內層函數,來檢測f有沒有正確的修正t的引用行。
![【轉】[C/C++]關于C++11中的std::move和std::forward 復制代碼](http://common.cnblogs.com/images/copycode.gif)
1 template
2 void f(T &&a)
3 {
4 printValType(a);
5 }
6
7 int main()
8 {
9 f(lRefA());
10 f(clRefA());
11 f(rRefA());
12 f(crRefA());
13 }
![【轉】[C/C++]關于C++11中的std::move和std::forward 復制代碼](http://common.cnblogs.com/images/copycode.gif)
輸出:Type&,const Type&,Type&,const Type&。
可見后兩個輸出錯了,這正是前面規則3描述的,當實參是右值引用時,雖然T被推演為U,但是參數a退化成了左值引用。
直接應用std::forward:
1 template
2 void f(T &&a)
3 {
4 printValType(std::forward(a));
5 }
輸出:Type&,const Type&,Type&&,const Type&&。
輸出正確了,這就是std::forward的作用啊。如果更深層的函數也需要完整的引用信息,如這里的printValType,那就應該在傳遞形參前先std::forward!
?
在編寫自己的forward函數之前,先來嘗試直接強制轉化參數a:
1 template
2 void f(T &&a)
3 {
4 printValType((T&&)a);
5 }
輸出:Type&,const Type&,Type&&,const Type&&。
正確!因為不管T被推演為U&還是U,只要T&&肯定能還原為U&和U&&。
?
考慮下自己的forward函數應該怎么寫:
?
1 template
2 T&& forward(... a)
3 {
4 return (T&&)a;
5 }
調用方f一定得顯示指定類型forward。
形參怎么寫?形參a的類型由T構成,而且forward的實參一定是左值(暫時不考慮forward(std::string())的使用方法),也就是說,無論T是U&還是U,形參a的類型一定都得是U&,才能和實參匹配,所以,結果是:
1 template
2 T&& forward(T& a)
3 {
4 return (T&&)a;
5 }
測試,輸出:Type&,const Type&,Type&&,const Type&&。
正確!
?
再試下,如果f調用forward的時候,使用forward(a)的方式,沒有顯示指定模板類型會怎么樣:
1 template
2 void f(T &&a)
3 {
4 printValType(forward(a));
5 }
輸出:T&&,const Type&&,Type&&,const Type&&。
錯了。分析下,因為實參始終是左值,所以forward的形參T& a中,T就被推演為U,因此(T&&)a也就是(U&&)a所以結果錯誤。
為了避免用戶使用forward(a),因此應該禁用forward的自動模板參數推演功能!可以借助std::identity,另外,將(T&&)換成static_cast,規范一下:
1 template
2 T&& forward(typename std::identity::type& a)
3 {
4 return static_cast(a);
5 }
?
上面講的是針對T為U&或U,而實參始終為左值的情況,這是常見的情形;不過也有實參為右值的情況,還需要改進上面這個forward,但我這里就不寫了。
這是我手里的gcc4.5.2的forward實現:
![【轉】[C/C++]關于C++11中的std::move和std::forward 復制代碼](http://common.cnblogs.com/images/copycode.gif)
1 /// forward (as per N2835)
2 /// Forward lvalues as rvalues.
3 template
4 inline typename enable_if::value, _Tp&&>::type
5 forward(typename std::identity<_Tp>::type& __t)
6 { return static_cast<_Tp&&>(__t); }
7
8 /// Forward rvalues as rvalues.
9 template
10 inline typename enable_if::value, _Tp&&>::type
11 forward(typename std::identity<_Tp>::type&& __t)
12 { return static_cast<_Tp&&>(__t); }
13
14 // Forward lvalues as lvalues.
15 template
16 inline typename enable_if::value, _Tp>::type
17 forward(typename std::identity<_Tp>::type __t)
18 { return __t; }
19
20 // Prevent forwarding rvalues as const lvalues.
21 template
22 inline typename enable_if::value, _Tp>::type
23 forward(typename std::remove_reference<_Tp>::type&& __t) = delete;
![【轉】[C/C++]關于C++11中的std::move和std::forward 復制代碼](http://common.cnblogs.com/images/copycode.gif)
第1/3版本就相當于我之前的實現,而版本2/4是實參為右值的情況,至于后者這種取舍的原因,還得去自己研究下使用場合和文檔了。
我手里的vc2010實現的forward和我之前的實現相同,顯然還不夠,不過vc2010本來對標準也就還支持得少..