詳解C++右值引用
概述
在C++中,常量、變量或表達式一定是左值(lvalue)或右值(rvalue)。
左值:非臨時的(具名的,可在多條語句中使用,可以被取地址)。可以出現(xiàn)在等號的左邊或右邊??煞譃榉浅A孔笾岛统A孔笾?。

右值:臨時的(不具名的,只在當(dāng)前語句中有效,不能取地址)。只能出現(xiàn)在等號的右邊??煞譃榉浅A坑抑岛统A坑抑怠?/p>

左值引用:對左值的引用就是左值引用??煞譃榉浅A孔笾狄煤统A孔笾狄?。

注:常量左值引用是“萬能”的引用類型,可以綁定到所有類型的值,包括非常量左值、常量左值、非常量右值和常量右值。
右值引用(Rvalue References):對右值的引用就是右值引用??煞譃榉浅A坑抑狄煤统A坑抑狄谩?/p>

為臨時對象的右值,它的生命周期很短暫,一般在執(zhí)行完當(dāng)前這條表達式之后,就釋放了。
通過將其賦值給右值引用,可以在不進行昂貴的拷貝操作的情況下被“續(xù)命”,讓其生命周期與右值引用類型變量的生命周期一樣長。
右值引用的兩個基本特性:移動語義(Move Semantics)和完美轉(zhuǎn)發(fā)(Perfect Forwarding)
移動語義(Move Semantics)
可將資源從一個對象轉(zhuǎn)移到另一個對象;主要解決減少不必要的臨時對象的創(chuàng)建、拷貝與銷毀。
移動構(gòu)造函數(shù)MyClass(Type&& a):當(dāng)構(gòu)造函數(shù)參數(shù)是一個右值時,優(yōu)先使用移動構(gòu)造函數(shù)而不是拷貝構(gòu)造函數(shù)MyClass(const Type& a)。
移動賦值運算符Type& operator = (Type&& a):當(dāng)賦值的是一個右值時,優(yōu)先使用移動賦值而不是拷貝賦值運算符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優(yōu)化掉
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 注:發(fā)生在MyClass::GetMyClassGo()內(nèi)部
MyClass a5 = MyClass::GetMyClassGo("china"); // move construct兩次 注:一次發(fā)生在MyClass::GetMyClassGo()內(nèi)部;另一次發(fā)生在將返回值賦值給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兩次 注:一次發(fā)生在MyClass::GetMyClassGo()內(nèi)部;另一次發(fā)生在將返回值賦值給foo0參數(shù)時
//func1(MyClass::GetMyClassGo("is")); // 編譯error: 不能把一個右值賦值給左值
func2(MyClass::GetMyClassGo("girl")); // move construct 注:發(fā)生在MyClass::GetMyClassGo()內(nèi)部
func3(MyClass::GetMyClassGo("!")); // move construct 注:發(fā)生在MyClass::GetMyClassGo()內(nèi)部
return 0;
}
注:測試以上代碼一定要關(guān)閉C++編譯器優(yōu)化技術(shù) -- RVO、NRVO和復(fù)制省略
使用std::move來實現(xiàn)移動語義
將一個左值或右值強制轉(zhuǎn)化為右值引用。 注:UE4中對應(yīng)為MoveTemp模板函數(shù)
std::move(en chs)并不會移動任何東西,只是將對象的狀態(tài)或者所有權(quán)從一個對象轉(zhuǎn)移到另一個對象。注:只是轉(zhuǎn)移,沒有內(nèi)存的搬遷或者內(nèi)存拷貝。
① 基本類型(如:int、double等)被std::move移動后,其數(shù)值不會發(fā)生變化
② 復(fù)合類型被std::move移動后,處于一個未定義,但有效的狀態(tài)(大部分成員函數(shù)仍有意義)例如:標(biāo)準(zhǔn)庫中的容器類對象被移動后,會變成空容器
完美轉(zhuǎn)發(fā)(Perfect Forwarding)
針對模板函數(shù),使用全能引用將一組參數(shù)原封不動的傳遞給另一個函數(shù)。
原封不動指:左值、右值、是否為const均不變。帶來如下3方面好處:
① 保證左值、右值的屬性
② 避免不必要的拷貝操作
③避免模版函數(shù)需要為左值、右值、是否為const的參數(shù)來實現(xiàn)不同的重載
全能引用(universal references、轉(zhuǎn)發(fā)引用)是一種特殊的模板引用類型,采用右值引用的語法形式(但它并不是右值引用)。如:template <class T> void func(T&& t) {}
T&& t在發(fā)生自動類型推斷的時候,它是未定的引用類型(universal references),T取決于傳入的參數(shù)t是右值還是左值。右值經(jīng)過T&&變?yōu)橛抑狄?,而左值?jīng)過T&&變?yōu)樽笾狄谩?/p>
std::move就是使用全能引用實現(xiàn)的。其定義如下:
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; }
① 當(dāng)t為左值時,展開為:U&& move(U& t) 注:右值引用類型變量也是左值
② 當(dāng)t為右值時,展開為:U&& move(U&& t)
最后,通過static_cast<>進行強制類型轉(zhuǎn)換返回右值引用。注:static_cast之所以能使用類型轉(zhuǎn)換,是通過remove_refrence::type模板移除T&&,T&的引用,獲取具體類型T(模板偏特化)。
引用折疊
規(guī)律:含左值引用就是左值引用,否則就是右值引用

使用std::forward實現(xiàn)參數(shù)的完美轉(zhuǎn)發(fā)。其定義如下(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<>進行引用折疊,并強制類型轉(zhuǎn)換后,實現(xiàn)原封不動轉(zhuǎn)發(fā)參數(shù)。 注:UE4中對應(yīng)為Forward模板函數(shù)
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轉(zhuǎn)發(fā)前后,參數(shù)a,b的類型完全不變
}
int main(int arg, char* argv[])
{
int a = 10;
foo(a, 20); // 展開為void foo(int& a, int&& b),經(jīng)過std::forward完美轉(zhuǎn)發(fā)后,會調(diào)用到void bar(int& a, int&& b)函數(shù)
func(std::forward<int>(a), std::forward<int&&>(30)); // 經(jīng)過std::forward完美轉(zhuǎn)發(fā)后,會調(diào)用到void func(int a, int&& b)函數(shù)
return 0;
}
以上就是詳解C++右值引用的詳細內(nèi)容,更多關(guān)于C++右值引用的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
基于C語言實現(xiàn)創(chuàng)意多彩貪吃蛇游戲
這篇文章主要介紹了如何利用C語言實現(xiàn)一個創(chuàng)意多彩貪吃蛇游戲,這是一個純C語言外加easyx庫的繪圖函數(shù)制作而成的有趣小游戲,無需引入額外資源,感興趣的可以動手嘗試一下2022-08-08
VC實現(xiàn)讓關(guān)閉按鈕成灰色不可用的方法
這篇文章主要介紹了VC實現(xiàn)讓關(guān)閉按鈕成灰色不可用的方法,比較實用的一個特殊功能,需要的朋友可以參考下2014-08-08
C語言中的結(jié)構(gòu)體的入門學(xué)習(xí)教程
這篇文章主要介紹了C語言中的結(jié)構(gòu)體的入門學(xué)習(xí)教程,以struct語句定義的結(jié)構(gòu)體是C語言編程中的重要基礎(chǔ),需要的朋友可以參考下2015-12-12

