以下文章來(lái)源于雨樂(lè)聊編程,作者雨樂(lè)
今天我們聊聊Modern cpp的兩個(gè)非常重要的概念移動(dòng)語(yǔ)義和轉(zhuǎn)發(fā)引用。
概念
值類別
在C++11之前,值類別分為左值和右值兩種,但是自C++11起,引入了純右值,消亡值兩種。其中,左值和將亡值合稱為泛左值,純右值和將亡值合稱為右值(C++11之前的右值等同于C++11中的純右值)。因?yàn)楸疚哪康牟辉谟诜治鲋殿悇e,所以本文意義中的左值和右值就是字面意義上的左值右值。
右值(RVALUE),即立即創(chuàng)建和使用的臨時(shí)值。在C++中,右值是與內(nèi)存地址無(wú)關(guān)的表達(dá)式,這意味著其沒(méi)有地址,也不能被修改。通常3、1.0以及std::string("abc")這種都屬于右值。
PS:需要注意的是常量字符串"abc"等這種屬于左值。
與右值相反,左值(LVALUE),其具有內(nèi)存地址和可修改,其可以用于分配新值或者獲取對(duì)象的地址。
可能有人有疑問(wèn),就是如何區(qū)分左值和右值,目前一個(gè)比較通用的判斷方式就是:判斷其是否可以取地址。
左值引用 & 右值引用
既然有左值和右值,那么相應(yīng)的,也就存在左值引用和右值引用,常常如下這種表示:
inta=0; int&la=a; int&&r=3;
在上述示例中,a、la以及r都屬于左值,其中l(wèi)a是左值引用,r是右值引用。
看下面一個(gè)例子:
#includevoidPrint(int&lref){ std::cout<"Lvalue?reference"?<
上述示例輸出如下:
Lvaluereference constLvaluereference Rvaluereference
std::move
std::move是C++中的一個(gè)常用函數(shù),它執(zhí)行到右值引用的轉(zhuǎn)換,允許您將左值轉(zhuǎn)換為右值。這在您想要轉(zhuǎn)移所有權(quán)或啟用對(duì)象的移動(dòng)語(yǔ)義的情況下非常有用。移動(dòng)語(yǔ)義允許開發(fā)人員有效地將資源(如內(nèi)存或文件句柄)從一個(gè)對(duì)象傳輸?shù)搅硪粋€(gè)對(duì)象,而無(wú)需進(jìn)行不必要的復(fù)制。
正如字面意義所理解的,移動(dòng)語(yǔ)義允許將對(duì)象有效地從一個(gè)位置“移動(dòng)”到另一個(gè)位置,而不是復(fù)制,這對(duì)于管理資源的對(duì)象特別有用。它實(shí)際上并沒(méi)有移動(dòng)任何東西;它只是將表達(dá)式的類型更改為右值引用。這允許調(diào)用移動(dòng)構(gòu)造函數(shù)或移動(dòng)賦值運(yùn)算符,而不是調(diào)用復(fù)制構(gòu)造函數(shù)或復(fù)制賦值運(yùn)算符。
gcc對(duì)move的實(shí)現(xiàn)如下:
templateinlinetypenamestd::remove_reference<_Tp>::type&& move(_Tp&&__t) {returnstatic_cast ::type&&>(__t);}
也就是說(shuō),其僅僅通過(guò)static_cast<>做了類型轉(zhuǎn)換~
std::move僅僅將對(duì)象轉(zhuǎn)換為右值引用,僅此而已
#include#include classObj{ public: Obj(){ std::cout<"Default?constructor "; ????} ????Obj(const?Obj&)?{ ????????std::cout?<"Copy?constructor "; ????} ????Obj(Obj&&)?noexcept?{ ????????std::cout?<"Move?constructor "; ????} ????Obj&?operator=(Obj&&?other)?noexcept?{ ????????std::cout?<"Move?assignment?operator "; ???????? ????????return?*this; ????} }; int?main()?{ ????Obj?obj1;???????????????????/*?Default?constructor?*/ ????Obj?obj2?=?std::move(obj1);?/*?Move?constructor????*/ ????Obj?obj3; ????obj3?=?std::move(obj2);??????????/*?Move?assignment?operator?*/ ????return?0; }
輸出如下:
Defaultconstructor Moveconstructor Defaultconstructor Moveassignmentoperator
在上述示例中:
?Obj1創(chuàng)建對(duì)象并調(diào)用構(gòu)造函數(shù)?obj2是通過(guò)使用std::move移動(dòng)obj1創(chuàng)建的,它調(diào)用移動(dòng)構(gòu)造函數(shù)?創(chuàng)建obj3并調(diào)用默認(rèn)構(gòu)造函數(shù)?當(dāng)使用std::move將obj2移動(dòng)到 obj3 時(shí),將調(diào)用移動(dòng)賦值運(yùn)算符
在此示例中,使用std::move操作, obj1到obj2 以及 obj2到obj3調(diào)用的是移動(dòng)的行為,這樣可以提高性能,尤其是在移動(dòng)大型數(shù)據(jù)結(jié)構(gòu)或資源時(shí)。但是,重要的是要注意移動(dòng)對(duì)象的狀態(tài)及其擁有的資源。
#include#include classObj{ public: Obj(){ std::cout<"Obj?constructed"?<p1=std::make_unique (); std::unique_ptr p2=std::move(p1); if(p1){ std::cout<"p1?is?not?empty"?<fun(); return0; }
在這個(gè)例子中,首先創(chuàng)建了一個(gè)類型為std::unique_ptr的指針p1,然后通過(guò)調(diào)用std::move()將p1的所有權(quán)轉(zhuǎn)移至p2,接著判斷p1是否為有效的指針,如果是則輸出,接著p2調(diào)用fun()函數(shù)。
上述示例輸出結(jié)果如下:
Objconstructed infun Objdestructed
從這個(gè)輸出結(jié)果可以看出,通過(guò)std::move()將所有權(quán)從p1轉(zhuǎn)移至p2后,p1不再持有任何資源。
std::forward
std::forward是 C++ 標(biāo)準(zhǔn)庫(kù)中的一個(gè)函數(shù)模板,用于在模板函數(shù)中進(jìn)行完美轉(zhuǎn)發(fā)。它允許在模板函數(shù)中將參數(shù)轉(zhuǎn)發(fā)到另一個(gè)函數(shù),同時(shí)保持參數(shù)的值類別(value category)和 cv 限定符(const 或 volatile 限定符)不變。
std::forward通常與右值引用(&&)結(jié)合使用,用于轉(zhuǎn)發(fā)傳遞給模板函數(shù)的參數(shù)。在模板函數(shù)內(nèi)部,你可以使用std::forward來(lái)將參數(shù)轉(zhuǎn)發(fā)給其他函數(shù),并保持原始參數(shù)的性質(zhì)。
示例如下:
#includevoidPrint(constint&lref){ std::cout<"Lvalue?reference"?< voidFun(T&¶m){ Print(std::forward (param)); } intmain(){ intx=5; constinty=10; Fun(x);//lvaluereference Fun(y);//lvaluereference Fun(20);//rvaluereference return0; }
在這個(gè)例子中,我們創(chuàng)建了一個(gè)模板函數(shù)Fun(),其參數(shù)類型為T&&,當(dāng)使用左值調(diào)用Fun()時(shí)候,它將param作為左值進(jìn)行轉(zhuǎn)發(fā),當(dāng)使用右值調(diào)用Fun()時(shí)候,它將param作為右值進(jìn)行轉(zhuǎn)發(fā),然后調(diào)用對(duì)應(yīng)的函數(shù),這樣可保證在不損失真實(shí)類型的情況下調(diào)用正確的函數(shù)。
move vs forward
對(duì)于第一次接觸這塊知識(shí)點(diǎn)的開發(fā)人員來(lái)說(shuō),可能有點(diǎn)疑惑,是否可以用move來(lái)替代forward,我們且看一個(gè)例子,相信你看了之后就不會(huì)對(duì)這塊一目了然:
#includevoidPrint(int&a){ std::cout<"int&:?"?< voidfunc1(T&&a){ Print(std::move(a)); } template voidfunc2(T&&a){ Print(std::forward (a)); } intmain(){ intarg=10; std::cout<"Calling?func1?with?std::move()..."?<
上述代碼輸出如下:
Callingfunc1withstd::move()... int&&:10 int&&:25 Callingfunc2withstd::forward()... int&:10 int&&:25
在上述代碼中:
?創(chuàng)建了兩個(gè)重載函數(shù)Print,其參數(shù)類型分別為**int &和int &&**,函數(shù)的功能是輸出其參數(shù)的類型
?模板函數(shù)func1(),函數(shù)參數(shù)a為轉(zhuǎn)發(fā)引用(T&&,也有地方稱之為萬(wàn)能引用),函數(shù)體內(nèi)調(diào)用參數(shù)為std::move(a)的Print()函數(shù),將a轉(zhuǎn)換為右值引用,這意味著,如果a是左值,則傳遞給Print()函數(shù)的參數(shù)類型為右值引用
?模板函數(shù)func2(),與模板函數(shù)func1()一樣,該函數(shù)也采用轉(zhuǎn)發(fā)引用(T&&)。但是,它使用 std::forward來(lái)保留a的原始值類別。這意味著如果a是左值,它將作為左值傳遞給Print()函數(shù),如果它是右值,它將作為右值傳遞
?在 main() 中,使用左值和右值調(diào)用函數(shù)func1和func2,以觀察對(duì)應(yīng)的行為
通過(guò)上面輸出,基本可以區(qū)分這倆,在此,做下簡(jiǎn)單的總結(jié):
?目的
?std::forward:用于完全按照傳遞的參數(shù)轉(zhuǎn)發(fā),保留其值類別(左值或右值)
?std::move:用于將對(duì)象轉(zhuǎn)換為右值引用,通常用于啟用移動(dòng)語(yǔ)義并轉(zhuǎn)移所有權(quán)
?用法
?std::forward:通常用于轉(zhuǎn)發(fā)引用(通用引用),以保留傳遞給另一個(gè)函數(shù)的參數(shù)的值類別
?std::move:用于將對(duì)象顯式轉(zhuǎn)換為右值引用
?影響
?std::forward:不更改參數(shù)的值類別。如果原始參數(shù)是右值引用,則它返回右值引用,否則返回左值引用
?std::move:將其參數(shù)轉(zhuǎn)換為右值引用,將其值類別更改為右值
?安全
?std::forward:可以安全地與轉(zhuǎn)發(fā)引用 (T&&) 一起使用,以確保正確轉(zhuǎn)發(fā)參數(shù),而不會(huì)產(chǎn)生不必要的副本。
?std::move:應(yīng)謹(jǐn)慎使用,因?yàn)樗赡軙?huì)導(dǎo)致從其他地方仍需要的對(duì)象移動(dòng),從而導(dǎo)致未定義的行為
?場(chǎng)景
?std::forward:用于需要完美轉(zhuǎn)發(fā)參數(shù)的場(chǎng)景,例如模板函數(shù)和類中。
?std::move:在顯式轉(zhuǎn)移所有權(quán)或調(diào)用移動(dòng)語(yǔ)義時(shí)使用,例如從函數(shù)返回僅移動(dòng)類型時(shí)
?返回類型
?std::forward:返回類型取決于傳遞給它的參數(shù)的值類別,它可以返回左值引用或右值引用。
?std::move:始終返回右值引用
-
內(nèi)存
+關(guān)注
關(guān)注
8文章
2998瀏覽量
73881 -
字符串
+關(guān)注
關(guān)注
1文章
577瀏覽量
20485 -
函數(shù)
+關(guān)注
關(guān)注
3文章
4304瀏覽量
62427 -
C++
+關(guān)注
關(guān)注
22文章
2104瀏覽量
73487
原文標(biāo)題:性能大殺器:std::move 和 std::forward
文章出處:【微信號(hào):CPP開發(fā)者,微信公眾號(hào):CPP開發(fā)者】歡迎添加關(guān)注!文章轉(zhuǎn)載請(qǐng)注明出處。
發(fā)布評(píng)論請(qǐng)先 登錄
相關(guān)推薦
評(píng)論