詳解C++右值引用
概述
在C++中,常量、變量或表達式一定是左值(lvalue)或右值(rvalue)。
左值:非臨時的(具名的,可在多條語句中使用,可以被取地址)。可以出現在等號的左邊或右邊。可分為非常量左值和常量左值。
右值:臨時的(不具名的,隻在當前語句中有效,不能取地址)。隻能出現在等號的右邊。可分為非常量右值和常量右值。
左值引用:對左值的引用就是左值引用。可分為非常量左值引用和常量左值引用。
註:常量左值引用是“萬能”的引用類型,可以綁定到所有類型的值,包括非常量左值、常量左值、非常量右值和常量右值。
右值引用(Rvalue References):對右值的引用就是右值引用。可分為非常量右值引用和常量右值引用。
為臨時對象的右值,它的生命周期很短暫,一般在執行完當前這條表達式之後,就釋放瞭。
通過將其賦值給右值引用,可以在不進行昂貴的拷貝操作的情況下被“續命”,讓其生命周期與右值引用類型變量的生命周期一樣長。
右值引用的兩個基本特性:移動語義(Move Semantics)和完美轉發(Perfect Forwarding)
移動語義(Move Semantics)
可將資源從一個對象轉移到另一個對象;主要解決減少不必要的臨時對象的創建、拷貝與銷毀。
移動構造函數MyClass(Type&& a):當構造函數參數是一個右值時,優先使用移動構造函數而不是拷貝構造函數MyClass(const Type& a)。
移動賦值運算符Type& operator = (Type&& a):當賦值的是一個右值時,優先使用移動賦值而不是拷貝賦值運算符Type& operator = (const Type& a)。
#include <iostream> #include <string> #include <utility> struct MyClass { std::string s; MyClass(const char* sz) : s(sz) { std::cout << "MyClass sz:" << sz << std::endl; } MyClass(const MyClass& o) : s(o.s) { std::cout << "copy construct!\n"; } MyClass(MyClass&& o) noexcept : s(std::move(o.s)) { std::cout << "move construct!\n"; } MyClass& operator=(const MyClass& other) { // copy assign std::cout << "copy assign!\n"; s = other.s; return *this; } MyClass& operator=(MyClass&& other) noexcept { // move assign std::cout << "move assign!\n"; s = std::move(other.s); return *this; } static MyClass GetMyClassGo(const char* sz) { MyClass o(sz); // 註意:可能會被NRVO優化掉 return o; } }; void func0(MyClass o) { std::cout << o.s.c_str() << std::endl; } void func1(MyClass& o) { std::cout << o.s.c_str() << std::endl; } void func2(const MyClass& o) { std::cout << o.s.c_str() << std::endl; } void func3(MyClass&& o) { std::cout << o.s.c_str() << std::endl; } int main(int arg, char* argv[]) { MyClass a1("how"); MyClass a2("are"); a2 = a1; // copy assign 註:a1是一個左值 a2 = MyClass("you"); // move assign 註:MyClass("you")是一個右值 MyClass a3(a1); // copy construct 註:a1是一個左值 MyClass&& a4 = MyClass::GetMyClassGo("go"); // move construct 註:發生在MyClass::GetMyClassGo()內部 MyClass a5 = MyClass::GetMyClassGo("china"); // move construct兩次 註:一次發生在MyClass::GetMyClassGo()內部;另一次發生在將返回值賦值給a5 MyClass a6("let"); MyClass a7("it"); MyClass a8("go"); MyClass a9("!"); func0(a6); // copy construct func1(a7); func2(a8); //func3(a9); // 編譯error: 不能把一個左值賦值給右值 func0(MyClass::GetMyClassGo("god")); // move construct兩次 註:一次發生在MyClass::GetMyClassGo()內部;另一次發生在將返回值賦值給foo0參數時 //func1(MyClass::GetMyClassGo("is")); // 編譯error: 不能把一個右值賦值給左值 func2(MyClass::GetMyClassGo("girl")); // move construct 註:發生在MyClass::GetMyClassGo()內部 func3(MyClass::GetMyClassGo("!")); // move construct 註:發生在MyClass::GetMyClassGo()內部 return 0; }
註:測試以上代碼一定要關閉C++編譯器優化技術 — RVO、NRVO和復制省略
使用std::move來實現移動語義
將一個左值或右值強制轉化為右值引用。 註:UE4中對應為MoveTemp模板函數
std::move(en chs)並不會移動任何東西,隻是將對象的狀態或者所有權從一個對象轉移到另一個對象。註:隻是轉移,沒有內存的搬遷或者內存拷貝。
① 基本類型(如:int、double等)被std::move移動後,其數值不會發生變化
② 復合類型被std::move移動後,處於一個未定義,但有效的狀態(大部分成員函數仍有意義)例如:標準庫中的容器類對象被移動後,會變成空容器
完美轉發(Perfect Forwarding)
針對模板函數,使用全能引用將一組參數原封不動的傳遞給另一個函數。
原封不動指:左值、右值、是否為const均不變。帶來如下3方面好處:
① 保證左值、右值的屬性
② 避免不必要的拷貝操作
③避免模版函數需要為左值、右值、是否為const的參數來實現不同的重載
全能引用(universal references、轉發引用)是一種特殊的模板引用類型,采用右值引用的語法形式(但它並不是右值引用)。如:template <class T> void func(T&& t) {}
T&& t在發生自動類型推斷的時候,它是未定的引用類型(universal references),T取決於傳入的參數t是右值還是左值。右值經過T&&變為右值引用,而左值經過T&&變為左值引用。
std::move就是使用全能引用實現的。其定義如下:
template <typename T> typename remove_reference<T>::type&& move(T&& t) { return static_cast<typename remove_reference<T>::type &&>(t); } /***************************************** std::remove_reference功能為去除類型中的引用 std::remove_reference<T &>::type ---> T std::remove_reference<T &&>::type ---> T std::remove_reference<T>::type ---> T ******************************************/ //原始的,最通用的版本 template <typename T> struct remove_reference{ typedef T type; //定義T的類型別名為type }; //部分版本特例化,將用於左值引用和右值引用 template <class T> struct remove_reference<T&> //左值引用 { typedef T type; } template <class T> struct remove_reference<T&&> //右值引用 { typedef T type; }
① 當t為左值時,展開為:U&& move(U& t) 註:右值引用類型變量也是左值
② 當t為右值時,展開為:U&& move(U&& t)
最後,通過static_cast<>進行強制類型轉換返回右值引用。註:static_cast之所以能使用類型轉換,是通過remove_refrence::type模板移除T&&,T&的引用,獲取具體類型T(模板偏特化)。
引用折疊
規律:含左值引用就是左值引用,否則就是右值引用
使用std::forward實現參數的完美轉發。其定義如下(en chs):
template <typename T> T&& forward(remove_reference_t<T>& arg) // forward an lvalue as either an lvalue or an rvalue { return static_cast<T&&>(arg); } template <typename T> T&& forward(remove_reference_t<T>&& arg) // forward an rvalue as an rvalue { static_assert(!is_lvalue_reference_v<T>, "bad forward call"); return static_cast<T&&>(arg); }
最後,通過static_cast<>進行引用折疊,並強制類型轉換後,實現原封不動轉發參數。 註:UE4中對應為Forward模板函數
void bar(int& a, int&& b) { int c = a + b; } void func(int a, int&& b) { int c = a + b; } template <typename A, typename B> void foo(A&& a, B&& b) { // a, b為左值引用或右值引用 bar(std::forward<A>(a), std::forward<B>(b)); // 在std::forward轉發前後,參數a,b的類型完全不變 } int main(int arg, char* argv[]) { int a = 10; foo(a, 20); // 展開為void foo(int& a, int&& b),經過std::forward完美轉發後,會調用到void bar(int& a, int&& b)函數 func(std::forward<int>(a), std::forward<int&&>(30)); // 經過std::forward完美轉發後,會調用到void func(int a, int&& b)函數 return 0; }
以上就是詳解C++右值引用的詳細內容,更多關於C++右值引用的資料請關註WalkonNet其它相關文章!
推薦閱讀:
- 一文搞懂c++中的std::move函數
- C++右值引用與移動構造函數基礎與應用詳解
- 一篇文章弄懂C++左值引用和右值引用
- 解析C++11的std::ref、std::cref源碼
- c++11中std::move函數的使用