關(guān)于C++11中的std::move和std::forwardstd::move是一個(gè)用于提示優(yōu)化的函數(shù),,過去的c++98中,,由于無法將作為右值的臨時(shí)變量從左值當(dāng)中區(qū)別出來,,所以程序運(yùn)行時(shí)有大量臨時(shí)變量白白的創(chuàng)建后又立刻銷毀,其中又尤其是返回字符串std::string的函數(shù)存在最大的浪費(fèi),。 比如: 1 std::string fileContent = “oldContent”; 因?yàn)椴⒉皇撬星闆r下,,C++編譯器都能進(jìn)行返回值優(yōu)化,所以,,向上面的例子中,,往往會(huì)創(chuàng)建多個(gè)字符串。readFileContent如果沒有內(nèi)部狀態(tài),,那么,,它的返回值多半是std::string(const std::string的做法不再被推薦了),而不是const std::string&,。這是一個(gè)浪費(fèi),,函數(shù)的返回值被拷貝到s中后,棧上的臨時(shí)對象就被銷毀了,。 在C++11中,,編碼者可以主動(dòng)提示編譯器,readFileContent返回的對象是臨時(shí)的,可以被挪作他用:std::move,。 將上面的例子改成: 1 std::string fileContent = “oldContent”; 后,,對象s在被賦值的時(shí)候,方法std::string::operator =(std::string&&)會(huì)被調(diào)用,,符號(hào)&&告訴std::string類的編寫者,,傳入的參數(shù)是一個(gè)臨時(shí)對象,,可以挪用其數(shù)據(jù),,于是std::string::operator =(std::string&&)的實(shí)現(xiàn)代碼中,會(huì)置空形參,,同時(shí)將原本保存在中形參中的數(shù)據(jù)移動(dòng)到自身,。 不光是臨時(shí)變量,只要是你認(rèn)為不再需要的數(shù)據(jù),,都可以考慮用std::move移動(dòng),。 比較有名的std::move用法是在swap中: 1 template<typename T> 總之,std::move是為性能而生的,,正式因?yàn)榱擞辛诉@個(gè)主動(dòng)報(bào)告廢棄物的設(shè)施,,所以C++11中的STL性能大幅提升,即使C++用戶仍然按找舊有的方式來編碼,,仍然能因中新版STL等標(biāo)準(zhǔn)庫的強(qiáng)化中收益,。
std::forward是用于模板編程中的,如果不需要編寫通用的模板類和函數(shù),,可能不怎么用的上它,。 要認(rèn)識(shí)它的作用,需要知道C++中的幾條規(guī)則:(這里有篇挺好的文章:http://blog.csdn.net/zwvista/article/details/6848582,,但似乎因標(biāo)準(zhǔn)的更新,,其中的規(guī)則已不完全成立了) 1. 引用折疊規(guī)則: X& + & => X& 2. 對于模板函數(shù)中的形參聲明T&&(這里的模板參數(shù)T,最終推演的結(jié)果可能不是一個(gè)純類型,,它可能還會(huì)帶有引用/常量修飾符,,如,T推演為const int時(shí),,實(shí)際形參為const int &&),,會(huì)有如下規(guī)則: 如果調(diào)用函數(shù)時(shí)的實(shí)參為U&(這里的U可能有const/volatile修飾,但沒有左/右引用修飾了),,那么T推演為U&,,顯然根據(jù)上面的引用折疊規(guī)則,U& &&=>U&。 如果調(diào)用實(shí)參為U&&,,雖然將T推導(dǎo)為U&&和U都能滿足折疊規(guī)則(U&& &&=> U&&且U &&=>U&&),,但標(biāo)準(zhǔn)規(guī)定,這里選擇將T推演為U而非U&&,。 總結(jié)一下第2條規(guī)則:當(dāng)形參聲明為T&&時(shí),,對于實(shí)參U&,T被推演為U&,;當(dāng)實(shí)參是U&&時(shí),,T被推演為U。當(dāng)然,,T和U具有相同的const/volatile屬性,。 3.這點(diǎn)很重要,也是上面zwvista的文章中沒有提到的:形參T&& t中的變量t,,始終是左值引用,,即使調(diào)用函數(shù)的實(shí)參是右值引用也不例外??梢赃@么理解,,本來,左值和右值概念的本質(zhì)區(qū)別就是,,左值是用戶顯示聲明或分配內(nèi)存的變量,,能夠直接用變量名訪問,而右值主要是臨時(shí)變量,。當(dāng)一個(gè)臨時(shí)變量傳入形參為T&& t的模板函數(shù)時(shí),,T被推演為U,參數(shù)t所引用的臨時(shí)變量因?yàn)殚_始能夠被據(jù)名訪問了,,所以它變成了左值,。這也就是std::forward存在的原因!當(dāng)你以為實(shí)參是右值所以t也應(yīng)該是右值時(shí),,它跟你開了個(gè)玩笑,,它是左值!如果你要進(jìn)一步調(diào)用的函數(shù)會(huì)根據(jù)左右值引用性來進(jìn)行不同操作,,那么你在將t傳給其他函數(shù)時(shí),,應(yīng)該先用std::forward恢復(fù)t的本來引用性,恢復(fù)的依據(jù)是模板參數(shù)T的推演結(jié)果,。雖然t的右值引用行會(huì)退化,,變成左值引用,但根據(jù)實(shí)參的左右引用性不同,,T會(huì)被分別推演為U&和U,,這就是依據(jù),!因此傳給std::forward的兩個(gè)參數(shù)一個(gè)都不能少:std::forward<T>(t)。
再來,,討論一下,,一個(gè)模板函數(shù)如果要保留參數(shù)的左右值引用性,為什么應(yīng)該聲明為T&&: 如果聲明函數(shù)f(T t):實(shí)參會(huì)直接進(jìn)行值傳遞,,失去了引用性,。 如果聲明函數(shù)f(T &t): 根據(jù)引用折疊法則,無論T是U&還是U&&,,T&的折疊結(jié)果都只會(huì)是U&,,即,這個(gè)聲明不能用于匹配右值引用實(shí)參,。 如果聲明函數(shù)f(T &&t): 如果T為U&,,T&&的結(jié)果是U&,可以匹配左值實(shí)參,;如果T為U&&,T&&的結(jié)果是U&&,,可以匹配右值實(shí)參,。又因?yàn)門的cv性和U相同,所以這種聲明能夠保留實(shí)參的類型信息,。
先來看一組幫助類: 1 template<typename T> struct TypeName { static const char *get(){ return "Type"; } }; 在模板函數(shù)內(nèi)部將模板參數(shù)T傳給TypeName,,就可以訪問T的類型字符串:TypeName<T>::get()。
再一個(gè)幫助函數(shù),,用于打印一個(gè)表達(dá)式的類型: 1 template<typename T> 注意3條規(guī)則在這個(gè)模板函數(shù)上的應(yīng)用,。規(guī)則1,解釋了T&& val的聲明足以保留實(shí)參的類型信息,。規(guī)則2,,說明了,當(dāng)實(shí)參是string&時(shí),,T就是string&,;當(dāng)實(shí)參是const string&&時(shí),T就是const string(而非const string&&),。規(guī)則3,,強(qiáng)調(diào),無論實(shí)參是string&還是string&&,,形參val的類型都是string&! 注意TypeName<T&&>的寫法,,因?yàn)門只能為U&或者U,顯然T&&可以根據(jù)折疊法則還原為實(shí)參類型U&和U&&,。
這里是常見的const/左右引用組合的情形: 1 class A{}; // 測試類 測試一下上面的表達(dá)式類型: 1 printValType(lRefA()); 輸出依次是: Type&,,const Type&,Type&&,const Type&&,。
現(xiàn)在正式來探討std::forward的實(shí)現(xiàn),。 回顧一下使用std::forward的原因:由于聲明為f(T&& t)的模板函數(shù)的形參t會(huì)失去右值引用性質(zhì),所以在將t傳給更深層函數(shù)前,,可能會(huì)需要回復(fù)t的正確引用行,,當(dāng)然,修改t的引用性辦不到,,但根據(jù)t返回另一個(gè)引用還是可以的,。恰好,上面的函數(shù)printValType是一個(gè)會(huì)根據(jù)實(shí)參類型不同,,作出不同反映的函數(shù),,所以可以把它作為f的內(nèi)層函數(shù),來檢測f有沒有正確的修正t的引用行,。 1 template<typename T> 輸出:Type&,,const Type&,Type&,,const Type&,。 可見后兩個(gè)輸出錯(cuò)了,這正是前面規(guī)則3描述的,,當(dāng)實(shí)參是右值引用時(shí),,雖然T被推演為U,但是參數(shù)a退化成了左值引用,。 直接應(yīng)用std::forward: 1 template<typename T> 輸出:Type&,,const Type&,Type&&,,const Type&&,。 輸出正確了,這就是std::forward的作用啊,。如果更深層的函數(shù)也需要完整的引用信息,,如這里的printValType,那就應(yīng)該在傳遞形參前先std::forward,!
在編寫自己的forward函數(shù)之前,,先來嘗試直接強(qiáng)制轉(zhuǎn)化參數(shù)a: 1 template<typename T> 輸出:Type&,const Type&,,Type&&,,const Type&&。 正確,!因?yàn)椴还躎被推演為U&還是U,,只要T&&肯定能還原為U&和U&&,。
考慮下自己的forward函數(shù)應(yīng)該怎么寫: 因?yàn)樵趂orward的調(diào)用方中,形參已經(jīng)丟失了右值引用信息,,唯一的參考依據(jù)是T,,要根據(jù)T還原為正確的參數(shù),得T&&,,因此,,強(qiáng)制轉(zhuǎn)換和返回類型都是T&&了,當(dāng)然,,forward還必須被以forward<T>()的方式顯示指定模板類型,,這樣才能保證forward的模板參數(shù)T和上層函數(shù)f的T是相同類型。首先: 1 template<typename T> 調(diào)用方f一定得顯示指定類型forward<T>,。 形參怎么寫,?形參a的類型由T構(gòu)成,而且forward的實(shí)參一定是左值(暫時(shí)不考慮forward(std::string())的使用方法),,也就是說,,無論T是U&還是U,形參a的類型一定都得是U&,,才能和實(shí)參匹配,,所以,結(jié)果是: 1 template<typename T> 測試,,輸出:Type&,const Type&,,Type&&,,const Type&&。
再試下,,如果f調(diào)用forward的時(shí)候,使用forward(a)的方式,,沒有顯示指定模板類型會(huì)怎么樣: 1 template<typename T> 輸出:T&&,,const Type&&,Type&&,,const Type&&,。 錯(cuò)了。分析下,,因?yàn)閷?shí)參始終是左值,,所以forward的形參T& a中,T就被推演為U,,因此(T&&)a也就是(U&&)a所以結(jié)果錯(cuò)誤,。 為了避免用戶使用forward(a),,因此應(yīng)該禁用forward的自動(dòng)模板參數(shù)推演功能!可以借助std::identity,,另外,,將(T&&)換成static_cast<T&&>,規(guī)范一下: 1 template<typename T>
上面講的是針對T為U&或U,,而實(shí)參始終為左值的情況,,這是常見的情形;不過也有實(shí)參為右值的情況,,還需要改進(jìn)上面這個(gè)forward,,但我這里就不寫了。 這是我手里的gcc4.5.2的forward實(shí)現(xiàn): 1 /// forward (as per N2835) 第1/3版本就相當(dāng)于我之前的實(shí)現(xiàn),,而版本2/4是實(shí)參為右值的情況,,至于后者這種取舍的原因,還得去自己研究下使用場合和文檔了,。 我手里的vc2010實(shí)現(xiàn)的forward和我之前的實(shí)現(xiàn)相同,,顯然還不夠,不過vc2010本來對標(biāo)準(zhǔn)也就還支持得少... |
|