以下文章來源于雨樂聊編程,作者雨樂
今天我們聊聊Modern cpp的兩個非常重要的概念移動語義和轉發引用。
概念
值類別
在C++11之前,值類別分為左值和右值兩種,但是自C++11起,引入了純右值,消亡值兩種。其中,左值和將亡值合稱為泛左值,純右值和將亡值合稱為右值(C++11之前的右值等同于C++11中的純右值)。因為本文目的不在于分析值類別,所以本文意義中的左值和右值就是字面意義上的左值右值。
右值(RVALUE),即立即創建和使用的臨時值。在C++中,右值是與內存地址無關的表達式,這意味著其沒有地址,也不能被修改。通常3、1.0以及std::string("abc")這種都屬于右值。
PS:需要注意的是常量字符串"abc"等這種屬于左值。
與右值相反,左值(LVALUE),其具有內存地址和可修改,其可以用于分配新值或者獲取對象的地址。
可能有人有疑問,就是如何區分左值和右值,目前一個比較通用的判斷方式就是:判斷其是否可以取地址。
左值引用 & 右值引用
既然有左值和右值,那么相應的,也就存在左值引用和右值引用,常常如下這種表示:
inta=0; int&la=a; int&&r=3;
在上述示例中,a、la以及r都屬于左值,其中la是左值引用,r是右值引用。
看下面一個例子:
#includevoidPrint(int&lref){ std::cout<"Lvalue?reference"?<
上述示例輸出如下:
Lvaluereference constLvaluereference Rvaluereference
std::move
std::move是C++中的一個常用函數,它執行到右值引用的轉換,允許您將左值轉換為右值。這在您想要轉移所有權或啟用對象的移動語義的情況下非常有用。移動語義允許開發人員有效地將資源(如內存或文件句柄)從一個對象傳輸到另一個對象,而無需進行不必要的復制。
正如字面意義所理解的,移動語義允許將對象有效地從一個位置“移動”到另一個位置,而不是復制,這對于管理資源的對象特別有用。它實際上并沒有移動任何東西;它只是將表達式的類型更改為右值引用。這允許調用移動構造函數或移動賦值運算符,而不是調用復制構造函數或復制賦值運算符。
gcc對move的實現如下:
templateinlinetypenamestd::remove_reference<_Tp>::type&& move(_Tp&&__t) {returnstatic_cast ::type&&>(__t);}
也就是說,其僅僅通過static_cast<>做了類型轉換~
std::move僅僅將對象轉換為右值引用,僅此而已
#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創建對象并調用構造函數?obj2是通過使用std::move移動obj1創建的,它調用移動構造函數?創建obj3并調用默認構造函數?當使用std::move將obj2移動到 obj3 時,將調用移動賦值運算符
在此示例中,使用std::move操作, obj1到obj2 以及 obj2到obj3調用的是移動的行為,這樣可以提高性能,尤其是在移動大型數據結構或資源時。但是,重要的是要注意移動對象的狀態及其擁有的資源。
#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; }
在這個例子中,首先創建了一個類型為std::unique_ptr的指針p1,然后通過調用std::move()將p1的所有權轉移至p2,接著判斷p1是否為有效的指針,如果是則輸出,接著p2調用fun()函數。
上述示例輸出結果如下:
Objconstructed infun Objdestructed
從這個輸出結果可以看出,通過std::move()將所有權從p1轉移至p2后,p1不再持有任何資源。
std::forward
std::forward是 C++ 標準庫中的一個函數模板,用于在模板函數中進行完美轉發。它允許在模板函數中將參數轉發到另一個函數,同時保持參數的值類別(value category)和 cv 限定符(const 或 volatile 限定符)不變。
std::forward通常與右值引用(&&)結合使用,用于轉發傳遞給模板函數的參數。在模板函數內部,你可以使用std::forward來將參數轉發給其他函數,并保持原始參數的性質。
示例如下:
#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; }
在這個例子中,我們創建了一個模板函數Fun(),其參數類型為T&&,當使用左值調用Fun()時候,它將param作為左值進行轉發,當使用右值調用Fun()時候,它將param作為右值進行轉發,然后調用對應的函數,這樣可保證在不損失真實類型的情況下調用正確的函數。
move vs forward
對于第一次接觸這塊知識點的開發人員來說,可能有點疑惑,是否可以用move來替代forward,我們且看一個例子,相信你看了之后就不會對這塊一目了然:
#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
在上述代碼中:
?創建了兩個重載函數Print,其參數類型分別為**int &和int &&**,函數的功能是輸出其參數的類型
?模板函數func1(),函數參數a為轉發引用(T&&,也有地方稱之為萬能引用),函數體內調用參數為std::move(a)的Print()函數,將a轉換為右值引用,這意味著,如果a是左值,則傳遞給Print()函數的參數類型為右值引用
?模板函數func2(),與模板函數func1()一樣,該函數也采用轉發引用(T&&)。但是,它使用 std::forward來保留a的原始值類別。這意味著如果a是左值,它將作為左值傳遞給Print()函數,如果它是右值,它將作為右值傳遞
?在 main() 中,使用左值和右值調用函數func1和func2,以觀察對應的行為
通過上面輸出,基本可以區分這倆,在此,做下簡單的總結:
?目的
?std::forward:用于完全按照傳遞的參數轉發,保留其值類別(左值或右值)
?std::move:用于將對象轉換為右值引用,通常用于啟用移動語義并轉移所有權
?用法
?std::forward:通常用于轉發引用(通用引用),以保留傳遞給另一個函數的參數的值類別
?std::move:用于將對象顯式轉換為右值引用
?影響
?std::forward:不更改參數的值類別。如果原始參數是右值引用,則它返回右值引用,否則返回左值引用
?std::move:將其參數轉換為右值引用,將其值類別更改為右值
?安全
?std::forward:可以安全地與轉發引用 (T&&) 一起使用,以確保正確轉發參數,而不會產生不必要的副本。
?std::move:應謹慎使用,因為它可能會導致從其他地方仍需要的對象移動,從而導致未定義的行為
?場景
?std::forward:用于需要完美轉發參數的場景,例如模板函數和類中。
?std::move:在顯式轉移所有權或調用移動語義時使用,例如從函數返回僅移動類型時
?返回類型
?std::forward:返回類型取決于傳遞給它的參數的值類別,它可以返回左值引用或右值引用。
?std::move:始終返回右值引用
-
內存
+關注
關注
8文章
3019瀏覽量
74003 -
字符串
+關注
關注
1文章
578瀏覽量
20506 -
函數
+關注
關注
3文章
4327瀏覽量
62569 -
C++
+關注
關注
22文章
2108瀏覽量
73618
原文標題:性能大殺器:std::move 和 std::forward
文章出處:【微信號:CPP開發者,微信公眾號:CPP開發者】歡迎添加關注!文章轉載請注明出處。
發布評論請先 登錄
相關推薦
評論