std::move是一個用於提示優化的函數,過去的c++98中,因爲沒法將做爲右值的臨時變量從左值當中區別出來,因此程序運行時有大量臨時變量白白的建立後又馬上銷燬,其中又尤爲是返回字符串std::string的函數存在最大的浪費。c++
好比:編程
1 std::string fileContent = 「oldContent」;
2 s = readFileContent(fileName);
由於並非全部狀況下,C++編譯器都能進行返回值優化,因此,向上面的例子中,每每會建立多個字符串。readFileContent若是沒有內 部狀態,那麼,它的返回值多半是std::string(const std::string的作法再也不被推薦了),而不是const std::string&。這是一個浪費,函數的返回值被拷貝到s中後,棧上的臨時對象就被銷燬了。ide
在C++11中,編碼者能夠主動提示編譯器,readFileContent返回的對象是臨時的,能夠被挪做他用:std::move。函數
將上面的例子改爲:post
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中:優化
1 template<typename T>
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 }
總之,std::move是爲性能而生的,正式由於了有了這個主動報告廢棄物的設施,因此C++11中的STL性能大幅提高,即便C++用戶仍然按找舊有的方式來編碼,仍然能因中新版STL等標準庫的強化中收益。編碼
std::forward是用於模板編程中的,若是不須要編寫通用的模板類和函數,可能不怎麼用的上它。spa
要認識它的做用,須要知道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)。
再來,討論一下,一個模板函數若是要保留參數的左右值引用性,爲何應該聲明爲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<typename T> struct TypeName { static const char *get(){ return "Type"; } };
2 template<typename T> struct TypeName<const T> { static const char *get(){ return "const Type"; } };
3 template<typename T> struct TypeName<T&> { static const char *get(){ return "Type&"; } };
4 template<typename T> struct TypeName<const T&> { static const char *get(){ return "const Type&"; } };
5 template<typename T> struct TypeName<T&&> { static const char *get(){ return "Type&&"; } };
6 template<typename T> struct TypeName<const T&&> { static const char *get(){ return "const Type&&"; } };
在模板函數內部將模板參數T傳給TypeName,就能夠訪問T的類型字符串:TypeName<T>::get()。
再一個幫助函數,用於打印一個表達式的類型:
1 template<typename T>
2 void printValType(T &&val)
3 {
4 cout << TypeName<T&&>::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&&>的寫法,由於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的引用行。
1 template<typename T>
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 }
輸出:Type&,const Type&,Type&,const Type&。
可見後兩個輸出錯了,這正是前面規則3描述的,當實參是右值引用時,雖然T被推演爲U,可是參數a退化成了左值引用。
直接應用std::forward:
1 template<typename T>
2 void f(T &&a)
3 {
4 printValType(std::forward<T>(a));
5 }
輸出:Type&,const Type&,Type&&,const Type&&。
輸出正確了,這就是std::forward的做用啊。若是更深層的函數也須要完整的引用信息,如這裏的printValType,那就應該在傳遞形參前先std::forward!
在編寫本身的forward函數以前,先來嘗試直接強制轉化參數a:
1 template<typename T>
2 void f(T &&a)
3 {
4 printValType((T&&)a);
5 }
輸出:Type&,const Type&,Type&&,const Type&&。
正確!由於無論T被推演爲U&仍是U,只要T&&確定能還原爲U&和U&&。
考慮下本身的forward函數應該怎麼寫:
由於在forward的調用方中,形參已經丟失了右值引用信息,惟一的參考依據是T,要根據T還原爲正確的參數,得T&&,因 此,強制轉換和返回類型都是T&&了,固然,forward還必須被以forward<T>()的方式顯示指定模板類型,這 樣才能保證forward的模板參數T和上層函數f的T是相同類型。首先:
1 template<typename T>
2 T&& forward(... a)
3 {
4 return (T&&)a;
5 }
調用方f必定得顯示指定類型forward<T>。
形參怎麼寫?形參a的類型由T構成,並且forward的實參必定是左值(暫時不考慮forward(std::string())的使用方法),也就是說,不管T是U&仍是U,形參a的類型必定都得是U&,才能和實參匹配,因此,結果是:
1 template<typename T>
2 T&& forward(T& a)
3 {
4 return (T&&)a;
5 }
測試,輸出:Type&,const Type&,Type&&,const Type&&。
正確!
再試下,若是f調用forward的時候,使用forward(a)的方式,沒有顯示指定模板類型會怎麼樣:
1 template<typename T>
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<T&&>,規範一下:
1 template<typename T>
2 T&& forward(typename std::identity<T>::type& a)
3 {
4 return static_cast<T&&>(a);
5 }
上面講的是針對T爲U&或U,而實參始終爲左值的狀況,這是常見的情形;不過也有實參爲右值的狀況,還須要改進上面這個forward,但我這裏就不寫了。
這是我手裏的gcc4.5.2的forward實現:
1 /// forward (as per N2835)
2 /// Forward lvalues as rvalues.
3 template<typename _Tp>
4 inline typename enable_if<!is_lvalue_reference<_Tp>::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<typename _Tp>
10 inline typename enable_if<!is_lvalue_reference<_Tp>::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<typename _Tp>
16 inline typename enable_if<is_lvalue_reference<_Tp>::value, _Tp>::type
17 forward(typename std::identity<_Tp>::type __t)
18 { return __t; }
19
20 // Prevent forwarding rvalues as const lvalues.
21 template<typename _Tp>
22 inline typename enable_if<is_lvalue_reference<_Tp>::value, _Tp>::type
23 forward(typename std::remove_reference<_Tp>::type&& __t) = delete;
第1/3版本就至關於我以前的實現,而版本2/4是實參爲右值的狀況,至於後者這種取捨的緣由,還得去本身研究下使用場合和文檔了。
我手裏的vc2010實現的forward和我以前的實現相同,顯然還不夠,不過vc2010原本對標準也就還支持得少...