欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

深入解讀C++中的右值引用

 更新時(shí)間:2016年05月31日 17:33:09   作者:wudaijun  
這里來(lái)帶大家深入解讀C++中的右值引用,右值引用是C++新標(biāo)準(zhǔn)中的重要特性,包括C++11中的引用折疊,首先還是先來(lái)看一下右值引用的概念:

右值引用(及其支持的Move語(yǔ)意和完美轉(zhuǎn)發(fā))是C++0x將要加入的最重大語(yǔ)言特性之一,這點(diǎn)從該特性的提案在C++ - State of the Evolution列表上高居榜首也可以看得出來(lái)。
從實(shí)踐角度講,它能夠完美解決C++中長(zhǎng)久以來(lái)為人所詬病的臨時(shí)對(duì)象效率問(wèn)題。從語(yǔ)言本身講,它健全了C++中的引用類型在左值右值方面的缺陷。從庫(kù)設(shè)計(jì)者的角度講,它給庫(kù)設(shè)計(jì)者又帶來(lái)了一把利器。從庫(kù)使用者的角度講,不動(dòng)一兵一卒便可以獲得“免費(fèi)的”效率提升…
在標(biāo)準(zhǔn)C++語(yǔ)言中,臨時(shí)量(術(shù)語(yǔ)為右值,因其出現(xiàn)在賦值表達(dá)式的右邊)可以被傳給函數(shù),但只能被接受為const &類型。這樣函數(shù)便無(wú)法區(qū)分傳給const &的是真實(shí)的右值還是常規(guī)變量。而且,由于類型為const &,函數(shù)也無(wú)法改變所傳對(duì)象的值。C++0x將增加一種名為右值引用的新的引用類型,記作typename &&。這種類型可以被接受為非const值,從而允許改變其值。這種改變將允許某些對(duì)象創(chuàng)建轉(zhuǎn)移語(yǔ)義。比如,一個(gè)std::vector,就其內(nèi)部實(shí)現(xiàn)而言,是一個(gè)C式數(shù)組的封裝。如果需要?jiǎng)?chuàng)建vector臨時(shí)量或者從函數(shù)中返回vector,那就只能通過(guò)創(chuàng)建一個(gè)新的vector并拷貝所有存于右值中的數(shù)據(jù)來(lái)存儲(chǔ)數(shù)據(jù)。之后這個(gè)臨時(shí)的vector則會(huì)被銷毀,同時(shí)刪除其包含的數(shù)據(jù)。有了右值引用,一個(gè)參數(shù)為指向某個(gè)vector的右值引用的std::vector的轉(zhuǎn)移構(gòu)造器就能夠簡(jiǎn)單地將該右值中C式數(shù)組的指針復(fù)制到新的vector,然后將該右值清空。這里沒(méi)有數(shù)組拷貝,并且銷毀被清空的右值也不會(huì)銷毀保存數(shù)據(jù)的內(nèi)存。返回vector的函數(shù)現(xiàn)在只需要返回一個(gè)std::vector<>&&。如果vector沒(méi)有轉(zhuǎn)移構(gòu)造器,那么結(jié)果會(huì)像以前一樣:用std::vector<> &參數(shù)調(diào)用它的拷貝構(gòu)造器。如果vector確實(shí)具有轉(zhuǎn)移構(gòu)造器,那么轉(zhuǎn)移構(gòu)造器就會(huì)被調(diào)用,從而避免大量的內(nèi)存分配。

一. 定義
通常意義上,在C++中,可取地址,有名字的即為左值。不可取地址,沒(méi)有名字的為右值。右值主要包括字面量,函數(shù)返回的臨時(shí)變量值,表達(dá)式臨時(shí)值等。右值引用即為對(duì)右值進(jìn)行引用的類型,在C++98中的引用稱為左值引用。
如有以下類和函數(shù):

class A
{
private:
 int* _p;
};

A ReturnValue()
{
 return A();
}


ReturnValue()的返回值即為右值,它是一個(gè)不具名的臨時(shí)變量。在C++98中,只有常量左值引用才能引用這個(gè)值。
A& a = ReturnValue(); // error: non-const lvalue reference to type 'A' cannot bind to a temporary of type 'A'
   
const A& a2 = ReturnValue(); // ok

通過(guò)常量左值引用,可以延長(zhǎng)ReturnValue()返回值的生命周期,但是不能修改它。C++11的右值引用出場(chǎng)了:
A&& a3 = ReturnValue();

右值引用通過(guò)”&&”來(lái)聲明, a3引用了ReturnValue()的返回值,延長(zhǎng)了它的生命周期,并且可以對(duì)該臨時(shí)值進(jìn)行修改。

二. 移動(dòng)語(yǔ)義
右值引用可以引用并修改右值,但是通常情況下,修改一個(gè)臨時(shí)值是沒(méi)有意義的。然而在對(duì)臨時(shí)值進(jìn)行拷貝時(shí),我們可以通過(guò)右值引用來(lái)將臨時(shí)值內(nèi)部的資源移為己用,從而避免了資源的拷貝:

#include<iostream>

class A
{
public:
 A(int a)
 :_p(new int(a))
 {
 }

 // 移動(dòng)構(gòu)造函數(shù) 移動(dòng)語(yǔ)義
 A(A&& rhs)
 : _p(rhs._p)
 {
 // 將臨時(shí)值資源置空 避免多次釋放 現(xiàn)在資源的歸屬權(quán)已經(jīng)轉(zhuǎn)移
 rhs._p = nullptr; 
 std::cout<<"Move Constructor"<<std::endl;
 }
 // 拷貝構(gòu)造函數(shù) 復(fù)制語(yǔ)義
 A(const A& rhs)
 : _p(new int(*rhs._p))
 {
 std::cout<<"Copy Constructor"<<std::endl;
 }
 
private:
 int* _p;
};

A ReturnValue() { return A(5); }

int main()
{
 A a = ReturnValue();
 return 0;
}

運(yùn)行該代碼,發(fā)現(xiàn)Move Constructor被調(diào)用(在g++中會(huì)對(duì)返回值進(jìn)行優(yōu)化,不會(huì)有任何輸出??梢酝ㄟ^(guò)-fno-elide-constructors關(guān)閉這個(gè)選項(xiàng))。在用右值構(gòu)造對(duì)象時(shí),編譯器會(huì)調(diào)用A(A&& rhs)形式的移動(dòng)構(gòu)造函數(shù),在移動(dòng)構(gòu)造函數(shù)中,你可以實(shí)現(xiàn)自己的移動(dòng)語(yǔ)義,這里將臨時(shí)對(duì)象中_p指向內(nèi)存直接移為己用,避免了資源拷貝。當(dāng)資源非常大或構(gòu)造非常耗時(shí)時(shí),效率提升將非常明顯。如果A沒(méi)有定義移動(dòng)構(gòu)造函數(shù),那么像在C++98中那樣,將調(diào)用拷貝構(gòu)造函數(shù),執(zhí)行拷貝語(yǔ)義。移動(dòng)不成,還可以拷貝。
std::move:
C++11提供一個(gè)函數(shù)std::move()來(lái)將一個(gè)左值強(qiáng)制轉(zhuǎn)化為右值:

A a1(5);
A a2 = std::move(a1);

上面的代碼在構(gòu)造a2時(shí)將會(huì)調(diào)用移動(dòng)構(gòu)造函數(shù),并且a1的_p會(huì)被置空,因?yàn)橘Y源已經(jīng)被移動(dòng)了。而a1的生命周期和作用域并沒(méi)有變,仍然要等到main函數(shù)結(jié)束后再析構(gòu),因此之后對(duì)a1的_p的訪問(wèn)將導(dǎo)致運(yùn)行錯(cuò)誤。
std::move乍一看沒(méi)什么用。它主要用在兩個(gè)地方:
  • 幫助更好地實(shí)現(xiàn)移動(dòng)語(yǔ)義
  • 實(shí)現(xiàn)完美轉(zhuǎn)發(fā)(下面會(huì)提到)

考慮如下代碼:

class B
{
public:
 B(B&& rhs)
 : _pb(rhs._pb)
 {
 // how can i move rhs._a to this->_a ?
 rhs._pb = nullptr;
 }

private:
 A _a;
 int * pb;
}


對(duì)于B的移動(dòng)構(gòu)造函數(shù)來(lái)說(shuō),由于rhs是右值,即將被釋放,因此我們不只希望將_pb的資源移動(dòng)過(guò)來(lái),還希望利用A類的移動(dòng)構(gòu)造函數(shù),將A的資源也執(zhí)行移動(dòng)語(yǔ)義。然而問(wèn)題出在如果我們直接在初始化列表中使用:_a(rhs._a) 將調(diào)用A的拷貝構(gòu)造函數(shù)。因?yàn)閰?shù) rhs._a 此時(shí)是一個(gè)具名值,并且可以取址。實(shí)際上,B的移動(dòng)構(gòu)造函數(shù)的參數(shù)rhs也是一個(gè)左值,因?yàn)樗簿呙⑶铱扇≈?。這是在C++11右值引用中讓人很迷惑的一點(diǎn):可以接受右值的右值引用本身卻是個(gè)左值
這一點(diǎn)在后面的完美轉(zhuǎn)發(fā)還會(huì)提到。現(xiàn)在我們可以用std::move來(lái)將rhs._a轉(zhuǎn)換為右值:_a(std::move(rhs._a)),這樣將調(diào)用A的移動(dòng)構(gòu)造。實(shí)現(xiàn)移動(dòng)語(yǔ)義。當(dāng)然這里我們確信rhs._a之后不會(huì)在使用,因?yàn)閞hs即將被釋放。

三. 完美轉(zhuǎn)發(fā)
如果僅僅為了實(shí)現(xiàn)移動(dòng)語(yǔ)義,右值引用是沒(méi)有必要被提出來(lái)的,因?yàn)槲覀冊(cè)谡{(diào)用函數(shù)時(shí),可以通過(guò)傳引用的方式來(lái)避免臨時(shí)值的生成,盡管代碼不是那么直觀,但效率比使用右值引用只高不低。
右值引用的另一個(gè)作用是完美轉(zhuǎn)發(fā),完美轉(zhuǎn)發(fā)出現(xiàn)在泛型編程中,將模板函數(shù)參數(shù)傳遞給該函數(shù)調(diào)用的下一個(gè)模板函數(shù)。如:

template<typename T>
void Forward(T t)
{
 Do(t);
}

上面的代碼中,我們希望Forward函數(shù)將傳入?yún)?shù)類型原封不動(dòng)地傳遞給Do函數(shù),即Forward函數(shù)接收的左值,則Do接收到左值,F(xiàn)orward接收到右值,Do也將得到右值。上面的代碼能夠正確轉(zhuǎn)發(fā)參數(shù),但是是不完美的,因?yàn)镕orward接收參數(shù)時(shí)執(zhí)行了一次拷貝。
考慮到避免拷貝,我們可以傳遞引用,形如Forward(T& t),但是這種形式的Forward并不能接收右值作為參數(shù),如Forward(5)。因?yàn)榉浅A孔笾挡荒芙壎ǖ接抑???紤]常量左值引用:Forward(const T& t),這種形式的Forward能夠接收任何類型(常量左值引用是萬(wàn)能引用),但是由于加上了常量修飾符,因此無(wú)法正確轉(zhuǎn)發(fā)非常量左值引用:
void Do(int& i)
{
 // do something...
}

template<typename T>
void Forward(const T& t)
{
 Do(t);
}

int main()
{
 int a = 8;
 Forward(a); // error. 'void Do(int&)' : cannot convert argument 1 from 'const int' to 'int&'
 return 0;
}

基于這種情況, 我們可以對(duì)Forward的參數(shù)進(jìn)行const重載,即可正確傳遞左值引用。但是當(dāng)Do函數(shù)參數(shù)為右值引用時(shí),F(xiàn)orward(5)仍然不能正確傳遞,因?yàn)镕orward中的參數(shù)都是左值引用。
下面介紹在 C++11 中的解決方案。
PS:引用折疊
C++11引入了引用折疊規(guī)則,結(jié)合右值引用來(lái)解決完美轉(zhuǎn)發(fā)問(wèn)題:

typedef const int T;
typedef T& TR;
TR& v = 1; // 在C++11中 v的實(shí)際類型為 const int&

如上代碼中,發(fā)生了引用折疊,將TR展開,得到 T& & v = 1(注意這里不是右值引用)。 這里的 T& + & 被折疊為 T&。更為詳細(xì)的,根據(jù)TR的類型定義,以及v的聲明,發(fā)生的折疊規(guī)則如下:
T& + &  = T&
T& + && = T&
T&& + &  = T&
T&& + && = T&&

上面的規(guī)則被簡(jiǎn)化為:只要出現(xiàn)左值引用,規(guī)則總是優(yōu)先折疊為左值引用。僅當(dāng)出現(xiàn)兩個(gè)右值引用才會(huì)折疊為右值引用。
再談轉(zhuǎn)發(fā)
那么上面的引用折疊規(guī)則,對(duì)完美轉(zhuǎn)發(fā)有什么用呢?我們注意到,對(duì)于T&&類型,它和左值引用折疊為左值引用,和右值引用折疊為右值引用。基于這種特性,我們可以用 T&& 作為我們的轉(zhuǎn)發(fā)函數(shù)模板參數(shù):
template<typename T>
void Forward(T&& t)
{
 Do(static_cast<T&&>(t));
}

這樣,無(wú)論Forward接收到的是左值,右值,常量,非常量,t都能保持為其正確類型。
當(dāng)傳入左值引用 X& 時(shí):
void Forward(X& && t)
{
 Do(static_cast<X& &&>(t));
}

折疊后:
void Forward(X& t)
{
 Do(static_cast<X&>(t));
}

這里的static_cast看起來(lái)似乎是沒(méi)有必要,而它實(shí)際上是為右值引用準(zhǔn)備的:
void Forward(X&& && t)
{
 Do(static_cast<X&& &&>(t));
}

折疊后:
void Forward(X&& t)
{
 Do(static_cast<X&&>(t));
}

前面提到過(guò),可以接收右值的右值引用本身卻是個(gè)左值,因?yàn)樗呙⑶铱梢匀≈怠R虼嗽贔orward(X&& t)中,參數(shù)t已經(jīng)是一個(gè)左值了,此時(shí)我們需要將其轉(zhuǎn)換為它本身傳入的類型,即為右值。由于static_cast中引用折疊的存在,我們總能還原參數(shù)本來(lái)的類型。
在C++11中,static_cast<T&&>(t) 可以通過(guò) std::forward<T>(t) 來(lái)替代,std::forward是C++11用于實(shí)現(xiàn)完美轉(zhuǎn)發(fā)的一個(gè)函數(shù),它和std::move一樣,都通過(guò)static_cast來(lái)實(shí)現(xiàn)。我們的Forward函數(shù)最終變成了:
template<typename T>
void Forward(T&& t)
{
 Do(std::forward<T>(t));
}

可以通過(guò)如下代碼來(lái)測(cè)試:
#include<iostream>
using namespace std;

void Do(int& i)    { cout << "左值引用"  << endl; }
void Do(int&& i)   { cout << "右值引用"  << endl; }
void Do(const int& i) { cout << "常量左值引用" << endl; }
void Do(const int&& i) { cout << "常量右值引用" << endl; }

template<typename T>
void PerfectForward(T&& t){ Do(forward<T>(t)); }

int main()
{
 int a;
 const int b;
 
 PerfectForward(a);  // 左值引用
 PerfectForward(move(a)); // 右值引用
 PerfectForward(b);  // 常量左值引用
 PerfectForward(move(b)); // 常量右值引用
 return 0;
}

四. 附注
左值和左值引用,右值和右值引用都是同一個(gè)東西,引用不是一個(gè)新的類型,僅僅是一個(gè)別名。這一點(diǎn)對(duì)于理解模板推導(dǎo)很重要。對(duì)于以下兩個(gè)函數(shù)

template<typename T>
void Fun(T t)
{
 // do something...
}

template<typename T>
void Fun(T& t)
{
 // do otherthing...
}

Fun(T t)和Fun(T& t)他們都能接受左值(引用),它們的區(qū)別在于對(duì)參數(shù)作不同的語(yǔ)義,前者執(zhí)行拷貝語(yǔ)義,后者只是取個(gè)新的別名。因此調(diào)用Fun(a)編譯器會(huì)報(bào)錯(cuò),因?yàn)樗恢滥阋獙?duì)a執(zhí)行何種語(yǔ)義。另外,對(duì)于Fun(T t)來(lái)說(shuō),由于它執(zhí)行拷貝語(yǔ)義,因此它還能接受右值。因此調(diào)用Fun(5)不會(huì)報(bào)錯(cuò),因?yàn)樽笾狄脽o(wú)法引用到右值,因此只有Fun(T t)能執(zhí)行拷貝。
最后,附上VS中 std::move 和 std::forward 的源碼:

// move
template<class _Ty> 
inline typename remove_reference<_Ty>::type&& move(_Ty&& _Arg) _NOEXCEPT
{ 
 return ((typename remove_reference<_Ty>::type&&)_Arg);
}

// forward
template<class _Ty> 
inline _Ty&& forward(typename remove_reference<_Ty>::type& _Arg)
{ // forward an lvalue
 return (static_cast<_Ty&&>(_Arg));
}

template<class _Ty> 
inline _Ty&& forward(typename remove_reference<_Ty>::type&& _Arg) _NOEXCEPT
{ // forward anything
 static_assert(!is_lvalue_reference<_Ty>::value, "bad forward call");
 return (static_cast<_Ty&&>(_Arg));
}

相關(guān)文章

  • MFC Frame-Splitter模型實(shí)例原理解析

    MFC Frame-Splitter模型實(shí)例原理解析

    這篇文章主要介紹了MFC Frame-Splitter模型實(shí)例原理解析,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下
    2020-07-07
  • C語(yǔ)言數(shù)據(jù)結(jié)構(gòu)之雙向循環(huán)鏈表的實(shí)例

    C語(yǔ)言數(shù)據(jù)結(jié)構(gòu)之雙向循環(huán)鏈表的實(shí)例

    這篇文章主要介紹了C語(yǔ)言數(shù)據(jù)結(jié)構(gòu)之雙向循環(huán)鏈表的實(shí)例的相關(guān)資料,需要的朋友可以參考下
    2017-06-06
  • C++中vector的常用接口詳析說(shuō)明

    C++中vector的常用接口詳析說(shuō)明

    vector類我們可以將其看作是一個(gè)能夠動(dòng)態(tài)擴(kuò)容的數(shù)組,下面這篇文章主要給大家介紹了關(guān)于?C++?vector常用接口的相關(guān)資料,文中通過(guò)實(shí)例代碼介紹的非常詳細(xì),需要的朋友可以參考下
    2022-08-08
  • VS?Code安裝及C、C++環(huán)境配置詳細(xì)教程(Windows系統(tǒng))

    VS?Code安裝及C、C++環(huán)境配置詳細(xì)教程(Windows系統(tǒng))

    這篇文章主要介紹了VS?Code安裝及C、C++環(huán)境配置詳細(xì)教程(Windows系統(tǒng)),本文通過(guò)圖文并茂的形式給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2023-02-02
  • C++基于文件流與armadillo讀取mnist示例詳解

    C++基于文件流與armadillo讀取mnist示例詳解

    這篇文章主要給大家介紹了關(guān)于C++基于文件流與armadillo讀取mnist的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2021-05-05
  • OpenCV圖像處理之常見(jiàn)的圖像灰度變換

    OpenCV圖像處理之常見(jiàn)的圖像灰度變換

    這篇文章主要介紹了OpenCV圖像處理之常見(jiàn)的圖像灰度變換,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2019-07-07
  • C++實(shí)現(xiàn)動(dòng)態(tài)順序表(vector)

    C++實(shí)現(xiàn)動(dòng)態(tài)順序表(vector)

    這篇文章主要為大家詳細(xì)介紹了C++實(shí)現(xiàn)動(dòng)態(tài)順序表,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2020-05-05
  • 解析C語(yǔ)言中結(jié)構(gòu)體struct的對(duì)齊問(wèn)題

    解析C語(yǔ)言中結(jié)構(gòu)體struct的對(duì)齊問(wèn)題

    這篇文章主要介紹了C語(yǔ)言中結(jié)構(gòu)體struct的對(duì)齊問(wèn)題,作者深入到內(nèi)存分配方面來(lái)進(jìn)行解析,需要的朋友可以參考下
    2016-04-04
  • C++實(shí)現(xiàn)連連看游戲

    C++實(shí)現(xiàn)連連看游戲

    這篇文章主要為大家詳細(xì)介紹了C++實(shí)現(xiàn)連連看游戲,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2022-01-01
  • 探究C++中string類的實(shí)現(xiàn)原理以及擴(kuò)展使用

    探究C++中string類的實(shí)現(xiàn)原理以及擴(kuò)展使用

    這篇文章主要介紹了C++中string類的實(shí)現(xiàn)原理以及擴(kuò)展使用,從內(nèi)存分配角度進(jìn)行了深入探究,需要的朋友可以參考下
    2015-12-12

最新評(píng)論