久久国产成人av_抖音国产毛片_a片网站免费观看_A片无码播放手机在线观看,色五月在线观看,亚洲精品m在线观看,女人自慰的免费网址,悠悠在线观看精品视频,一级日本片免费的,亚洲精品久,国产精品成人久久久久久久

分享

[C/C++]關(guān)于C++11中的std::move和std::forward

 quasiceo 2014-01-18

關(guān)于C++11中的std::move和std::forward

std::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”;
2 s = readFileContent(fileName);

因?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”;
2 s = std::move(readFileContent(fileName));

后,,對象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中:

復(fù)制代碼
1 template<typename T>
2 void swap(T& a, T& b)
3 {
4 T t(std::move(a)); // a為空,t占有a的初始數(shù)據(jù)
5 a = std::move(b); // b為空,, a占有b的初始數(shù)據(jù)
6 b = std::move(t); // t為空,,b占有a的初始數(shù)據(jù)
7 }
復(fù)制代碼

總之,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&
X&& + & => X&
X& + && => X&
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"; } };
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&&"; } };

在模板函數(shù)內(nèi)部將模板參數(shù)T傳給TypeName,,就可以訪問T的類型字符串:TypeName<T>::get()。

 

再一個(gè)幫助函數(shù),,用于打印一個(gè)表達(dá)式的類型:

1 template<typename T>
2 void printValType(T &&val)
3 {
4 cout << TypeName<T&&>::get() << endl;
5 }

注意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{}; // 測試類
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(); } // 常右值

測試一下上面的表達(dá)式類型:

1 printValType(lRefA());
2 printValType(clRefA());
3 printValType(rRefA());
4 printValType(crRefA());

輸出依次是: 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的引用行,。

復(fù)制代碼
 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 }
復(fù)制代碼

輸出: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>
2 void f(T &&a)
3 {
4 printValType(std::forward<T>(a));
5 }

輸出: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>
2 void f(T &&a)
3 {
4 printValType((T&&)a);
5 }

輸出: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>
2 T&& forward(... a)
3 {
4 return (T&&)a;
5 }

調(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>
2 T&& forward(T& a)
3 {
4 return (T&&)a;
5 }

測試,,輸出:Type&,const Type&,,Type&&,,const Type&&。
正確,!

 

再試下,,如果f調(diào)用forward的時(shí)候,使用forward(a)的方式,,沒有顯示指定模板類型會(huì)怎么樣:

1 template<typename T>
2 void f(T &&a)
3 {
4 printValType(forward(a));
5 }

輸出: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>
2 T&& forward(typename std::identity<T>::type& a)
3 {
4 return static_cast<T&&>(a);
5 }

 

上面講的是針對T為U&或U,,而實(shí)參始終為左值的情況,,這是常見的情形;不過也有實(shí)參為右值的情況,,還需要改進(jìn)上面這個(gè)forward,,但我這里就不寫了。

這是我手里的gcc4.5.2的forward實(shí)現(xiàn):

復(fù)制代碼
 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;
復(fù)制代碼

第1/3版本就相當(dāng)于我之前的實(shí)現(xiàn),,而版本2/4是實(shí)參為右值的情況,,至于后者這種取舍的原因,還得去自己研究下使用場合和文檔了,。

我手里的vc2010實(shí)現(xiàn)的forward和我之前的實(shí)現(xiàn)相同,,顯然還不夠,不過vc2010本來對標(biāo)準(zhǔn)也就還支持得少...

    本站是提供個(gè)人知識(shí)管理的網(wǎng)絡(luò)存儲(chǔ)空間,,所有內(nèi)容均由用戶發(fā)布,,不代表本站觀點(diǎn)。請注意甄別內(nèi)容中的聯(lián)系方式,、誘導(dǎo)購買等信息,,謹(jǐn)防詐騙。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,,請點(diǎn)擊一鍵舉報(bào),。
    轉(zhuǎn)藏 分享 獻(xiàn)花(0

    0條評(píng)論

    發(fā)表

    請遵守用戶 評(píng)論公約

    類似文章 更多