C++11 右值引用與移動構(gòu)造函數(shù)

本文根據(jù)眾多互聯(lián)網(wǎng)博客內(nèi)容整理后形成拴魄,引用內(nèi)容的版權(quán)歸原始作者所有谤狡,僅限于學習研究使用竟终,不得用于任何商業(yè)用途并蝗。

左值(賦值操作符“=”的左側(cè)祭犯,通常是一個變量)與右值(賦值操作符“=”的右側(cè)秸妥,通常是一個常數(shù)、表達式沃粗、函數(shù)調(diào)用)之間的差別可以追溯到 Christopher Strachey (C++的祖先語言CPL之父粥惧,指稱語義學之父)時代。
在C++中陪每,左值可被綁定到非const引用影晓,左值或者右值則可被綁定到const引用。但是卻沒有什么可以綁定到非const的右值(譯注:即右值無法被非const的引用綁定)檩禾,這是為了防止人們修改臨時變量的值挂签,這些臨時變量在被賦予新的值之前,都會被銷毀盼产。例如:

void incr(int& a) { ++a; }
int i = 0;
incr(i);    // i變?yōu)?
//錯誤:0不是一個左值
incr(0);
//(譯注:0不是左值饵婆,無法直接綁定到非const引用:int&。
// 假如可行戏售,那么在調(diào)用時侨核,將會產(chǎn)生一個值為0的臨時變量,
// 用于綁定到int&中灌灾,但這個臨時變量將在函數(shù)返回時被銷毀搓译,
// 因而,對于它的任何更改都是沒有意義的锋喜,
// 所以編譯器拒絕將臨時變量綁定到非const引用些己,但對于const的引用,
// 則是可行的)

假設(shè)incr(0)合法嘿般,那么要么產(chǎn)生一個程序員不可見的臨時變量并進行無意義的遞增操作段标,要么發(fā)生更悲劇的情況:把字面常量“0”的實際值會變成1。盡管后者聽起來是天方夜譚炉奴,但是對于早期Frotran等這一類把字面常量“0”也存到內(nèi)存里的某個位置的編譯器來說逼庞,這真的會變成一個bug。(譯注:指的是假如把用于存儲字面常量0的內(nèi)存單元上的值從0修改為1以后瞻赶,后續(xù)所有使用字面常數(shù)0的地方實際上都在使用“1”)赛糟。

到目前為止,一切都很美好砸逊。但考慮如下函數(shù):

template<class T> swap(T& a, T& b) // 老式的swap函數(shù)
{
    T tmp(a);// 現(xiàn)在有兩份"a"
    a = b;        // 現(xiàn)在有兩份"b"
    b = tmp;    // 現(xiàn)在有兩份tmp(值同a)
}

如果T是一個拷貝代價相當高昂的類型虑灰,例如string和vector,那么上述swap()操作也將煞費氣力(不過對于標準庫來說痹兜,我們已經(jīng)針對string和vector的swap()進行了特化來處理這個問題)。注意這個奇怪的現(xiàn)象颤诀,我們的初衷其實并不是為了把這些變量拷來拷去字旭,我是僅僅是想將變量a,b,tmp的值做一個“移動”(譯注:即通過tmp來交換a,b的值)对湃。

在C++11中,我們可以定義“移動構(gòu)造函數(shù)(move constructors)”“移動賦值操作符(move assignments”來“移動”而非復制它們的參數(shù):

template<class T> class vector {
        // …
        vector(const vector&);  // 拷貝構(gòu)造函數(shù)
        vector(vector&&);   // 移動構(gòu)造函數(shù)
        vector& operator= (const vector&); // 拷貝賦值函數(shù)
        vector& operator =(vector&&);  // 移動賦值函數(shù)
}; //注意:移動構(gòu)造函數(shù)和移動賦值操作符接受
// 非const的右值引用參數(shù)遗淳,而且通常會對傳入的右值引用參數(shù)作修改

”&&”表示“右值引用”拍柒。右值引用可以綁定到右值(但不能綁定到左值):

X a;
X f();
X& r1 = a;        // 將r1綁定到a(一個左值)
X& r2 = f();    // 錯誤:f()的返回值是右值,無法綁定
X&& rr1 = f();    // OK:將rr1綁定到臨時變量
X&& rr2 = a;    // 錯誤:不能將右值引用rr2綁定到左值a

移動賦值操作背后的思想是屈暗,“賦值”不一定要通過“拷貝”來做拆讯,還可以通過把源對象簡單地“偷換”給目標對象來實現(xiàn)。例如對于表達式s1=s2养叛,我們可以不從s2逐字拷貝种呐,而是直接讓s1“侵占”s2內(nèi)部的數(shù)據(jù)存儲(譯注:比如char* p),并以某種方式“刪除”s1中原有的數(shù)據(jù)存儲(或者干脆把它扔給s2弃甥,因為大多情況下s2隨后就會被析構(gòu))爽室。(譯注:仔細體會copy與move的區(qū)別。)

我們?nèi)绾沃涝磳ο竽芊瘛耙苿印蹦叵ィ课覀兛梢赃@樣告訴編譯器:(譯注:通過move()操作符)

template <class T>
void swap(T& a, T& b)  //“完美swap”(大多數(shù)情況下)
{
      T tmp = move(a);  // 變量a現(xiàn)在失效(譯注:內(nèi)部數(shù)據(jù)被move到tmp中了)
      a = move(b);      // 變量b現(xiàn)在失效(譯注:內(nèi)部數(shù)據(jù)被move到a中了阔墩,變量a現(xiàn)在“滿血復活”了)
      b = move(tmp);    // 變量tmp現(xiàn)在失效(譯注:內(nèi)部數(shù)據(jù)被move到b中了,變量b現(xiàn)在“滿血復活”了)
}

move(x) 意味著“你可以把x當做一個右值”瓶珊,把move()改名為rval()或許會更好啸箫,但是事到如今,move()已經(jīng)使用很多年了伞芹。在C++11中忘苛,move()模板函數(shù)(參考“brief introduction”),以及右值引用被正式引入丑瞧。

右值引用同時也可以用作完美轉(zhuǎn)發(fā)(perfect forwarding)柑土。(譯注:比如某個接口函數(shù)什么也不做,只是將工作“委派”給其他工作函數(shù))

在C++11的標準庫中绊汹,所有的容器都提供了移動構(gòu)造函數(shù)和移動賦值操作符稽屏,那些插入新元素的操作,如insert()和push_back(), 也都有了可以接受右值引用的版本西乖。最終的結(jié)果是狐榔,在沒有用戶干預的情況下,標準容器和算法的性能都提升了获雕,而這些都應(yīng)歸功于拷貝操作的減少薄腻。

右值引用
為了解決移動語義及完美轉(zhuǎn)發(fā)問題,C++11標準引入了右值引用(rvalue reference)這一重要的新概念届案。右值引用采用T&&這一語法形式庵楷,比傳統(tǒng)的引用T&(如今被稱作左值引用 lvalue reference)多一個&。
如果把經(jīng)由T&&這一語法形式所產(chǎn)生的引用類型都叫做右值引用,那么這種廣義的右值引用又可分為以下三種類型:

  • 無名右值引用
  • 具名右值引用
  • 轉(zhuǎn)發(fā)型引用

無名右值引用和具名右值引用的引入主要是為了解決移動語義問題尽纽。轉(zhuǎn)發(fā)型引用的引入主要是為了解決完美轉(zhuǎn)發(fā)問題咐蚯。

無名右值引用
無名右值引用(unnamed rvalue reference)是指由右值引用相關(guān)操作所產(chǎn)生的引用類型。
無名右值引用主要通過返回右值引用的類型轉(zhuǎn)換操作產(chǎn)生弄贿, 其語法形式如下:
static_cast<T&&>(t)
標準規(guī)定該語法形式將把表達式 t 轉(zhuǎn)換為T類型的無名右值引用春锋。
無名右值引用是右值,標準規(guī)定無名右值引用和傳統(tǒng)的右值一樣具有潛在的可移動性差凹,即它所占有的資源可以被移動(竊绕诒肌)。

std::move()
由于無名右值引用是右值危尿,借助于類型轉(zhuǎn)換操作產(chǎn)生無名右值引用這一手段呐萌,左值表達式就可以被轉(zhuǎn)換成右值表達式。為了便于利用這一重要的轉(zhuǎn)換操作脚线,標準庫為我們提供了封裝這一操作的函數(shù)搁胆,這就是std::move()。
假設(shè)左值表達式 t 的類型為T&邮绿,利用以下函數(shù)調(diào)用就可以把左值表達式 t 轉(zhuǎn)換為T類型的無名右值引用(右值渠旁,類型為T&&)。
std::move(t)

具名右值引用
如果某個變量或參數(shù)被聲明為T&&類型船逮,并且T無需推導即可確定顾腊,那么這個變量或參數(shù)就是一個具名右值引用(named rvalue reference)。
具名右值引用是左值挖胃,因為具名右值引用有名字杂靶,和傳統(tǒng)的左值引用一樣可以用操作符&取地址。
與廣義的右值引用相對應(yīng)酱鸭,狹義的右值引用僅限指具名右值引用吗垮。
傳統(tǒng)的左值引用可以綁定左值,在某些情況下也可綁定右值凹髓。與此不同的是烁登,右值引用只能綁定右值。
右值引用和左值引用統(tǒng)稱為引用(reference)蔚舀,它們具有引用的共性饵沧,比如都必須在初始化時綁定值,都是左值等等赌躺。

struct X {};  
X a;  
X&& b = static_cast<X&&>(a);  
X&& c = std::move(a);  
//static_cast<X&&>(a) 和 std::move(a) 是無名右值引用狼牺,是右值  
//b 和 c 是具名右值引用,是左值  
X& d = a;  
X& e = b;  
const X& f = c;  
const X& g = X();  
X&& h = X();  
//左值引用d和e只能綁定左值(包括傳統(tǒng)左值:變量a以及新型左值:右值引用b)  
//const左值引用f和g可以綁定左值(右值引用c)礼患,也可以綁定右值(臨時對象X())  
//右值引用b是钥,c和h只能綁定右值(包括新型右值:無名右值引用std::move(a)以及傳統(tǒng)右值:臨時對象X())  

左右值重載策略
有時我們需要在函數(shù)中區(qū)分參數(shù)的左右值屬性掠归,根據(jù)參數(shù)左右值屬性的不同做出不同的處理。適當?shù)夭捎米笥抑抵剌d策略咏瑟,借助于左右值引用參數(shù)不同的綁定特性拂到,我們可以利用函數(shù)重載來做到這一點。常見的左右值重載策略如下:

struct X {};  
//左值版本  
void f(const X& param1){/*處理左值參數(shù)param1*/}  
//右值版本  
void f(X&& param2){/*處理右值參數(shù)param2*/}  
  
X a;  
f(a);            //調(diào)用左值版本  
f(X());          //調(diào)用右值版本  
f(std::move(a)); //調(diào)用右值版本  

即在函數(shù)重載中分別重載const左值引用和右值引用码泞。
重載const左值引用的為左值版本,這是因為const左值引用參數(shù)能綁定左值狼犯,而右值引用參數(shù)不能綁定左值余寥。
重載右值引用的為右值版本,這是因為雖然const左值引用參數(shù)和右值引用參數(shù)都能綁定右值悯森,但標準規(guī)定右值引用參數(shù)的綁定優(yōu)先度要高于const左值引用參數(shù)宋舷。

移動構(gòu)造器和移動賦值運算符
在類的構(gòu)造器和賦值運算符中運用上述左右值重載策略,就會產(chǎn)生兩個新的特殊成員函數(shù):移動構(gòu)造器(move constructor)和移動賦值運算符(move assignment operator)瓢姻。

struct X  
{  
    X();                         //缺省構(gòu)造器  
    X(const X& that);            //拷貝構(gòu)造器  
    X(X&& that);                 //移動構(gòu)造器  
    X& operator=(const X& that); //拷貝賦值運算符  
    X& operator=(X&& that);      //移動賦值運算符  
};  
  
X a;                             //調(diào)用缺省構(gòu)造器  
X b = a;                         //調(diào)用拷貝構(gòu)造器  
X c = std::move(b);              //調(diào)用移動構(gòu)造器  
b = a;                           //調(diào)用拷貝賦值運算符  
c = std::move(b);                //調(diào)用移動賦值運算符  

移動語義
無名右值引用和具名右值引用的引入主要是為了解決移動語義問題祝蝠。
移動語義問題是指在某些特定情況下(比如用右值來賦值或構(gòu)造對象時)如何采用廉價的移動語義替換昂貴的拷貝語義的問題。
移動語義(move semantics)是指某個對象接管另一個對象所擁有的外部資源的所有權(quán)幻碱。移動語義需要通過移動(竊纫锵痢)其他對象所擁有的資源來完成。移動語義的具體實現(xiàn)(即一次that對象到this對象的移動(move))通常包含以下若干步驟:

  • 如果this對象自身也擁有資源褥傍,釋放該資源
  • 將this對象的指針或句柄指向that對象所擁有的資源
  • 將that對象原本指向該資源的指針或句柄設(shè)為空值

上述步驟可簡單概括為①釋放this(this非空時)②移動that儡嘶。
移動語義通常在移動構(gòu)造器和移動賦值運算符中得以具體實現(xiàn)。兩者的區(qū)別在于移動構(gòu)造對象時this對象為空恍风,因而①釋放this無須進行蹦狂。

與移動語義相對,傳統(tǒng)的拷貝語義(copy semantics)是指某個對象拷貝(復制)另一個對象所擁有的外部資源并獲得新生資源的所有權(quán)朋贬】ǎ拷貝語義的具體實現(xiàn)(即一次that對象到this對象的拷貝(copy))通常包含以下若干步驟:

  • 如果this對象自身也擁有資源,釋放該資源
  • 拷貝(復制)that對象所擁有的資源
  • 將this對象的指針或句柄指向新生的資源
  • 如果that對象為臨時對象(右值)锦募,那么拷貝完成之后that對象所擁有的資源將會因that對象被銷毀而即刻得以釋放

上述步驟可簡單概括為①釋放this(this非空時)②拷貝that③釋放that(that為右值時)
拷貝語義通常在拷貝構(gòu)造器和拷貝賦值運算符中得以具體實現(xiàn)摆屯。兩者的區(qū)別在于拷貝構(gòu)造對象時this對象為空,因而①釋放this無須進行御滩。

比較移動語義與拷貝語義的具體步驟可知鸥拧,在賦值或構(gòu)造對象時,

  • 如果源對象that為左值削解,由于兩者效果不同(移動that ≠ 拷貝that)富弦,此時移動語義不能用來替換拷貝語義。
  • 如果源對象that為右值氛驮,由于兩者效果相同(移動that = 拷貝that + 釋放that)腕柜,此時廉價的移動語義(通過指針操作來移動資源)便可以用來替換昂貴的拷貝語義(生成,拷貝然后釋放資源)。

由此可知盏缤,只要在進行相關(guān)操作(比如賦值或構(gòu)造)時砰蠢,采取適當?shù)淖笥抑抵剌d策略區(qū)分源對象的左右值屬性,根據(jù)其左右值屬性分別采用拷貝語義和移動語義唉铜,移動語義問題便可以得到解決台舱。

下面用MemoryBlock這個自我管理內(nèi)存塊的類來具體說明移動語義問題。

#include <iostream>  
  
class MemoryBlock  
{  
public:  
  
    // 構(gòu)造器(初始化資源)  
    explicit MemoryBlock(size_t length)  
        : _length(length)  
        , _data(new int[length])  
    {  
    }  
  
    // 析構(gòu)器(釋放資源)  
    ~MemoryBlock()  
    {  
        if (_data != nullptr)  
        {  
            delete[] _data;  
        }  
    }  
  
    // 拷貝構(gòu)造器(實現(xiàn)拷貝語義:拷貝that)  
    MemoryBlock(const MemoryBlock& that)  
        // 拷貝that對象所擁有的資源  
        : _length(that._length)  
        , _data(new int[that._length])  
    {  
        std::copy(that._data, that._data + _length, _data);  
    }  
  
    // 拷貝賦值運算符(實現(xiàn)拷貝語義:釋放this + 拷貝that)  
    MemoryBlock& operator=(const MemoryBlock& that)  
    {  
        if (this != &that)  
        {  
            // 釋放自身的資源  
            delete[] _data;  
  
            // 拷貝that對象所擁有的資源  
            _length = that._length;  
            _data = new int[_length];  
            std::copy(that._data, that._data + _length, _data);  
        }  
        return *this;  
    }  
  
    // 移動構(gòu)造器(實現(xiàn)移動語義:移動that)  
    MemoryBlock(MemoryBlock&& that)  
        // 將自身的資源指針指向that對象所擁有的資源  
        : _length(that._length)  
        , _data(that._data)  
    {  
        // 將that對象原本指向該資源的指針設(shè)為空值  
        that._data = nullptr;  
        that._length = 0;  
    }  
  
    // 移動賦值運算符(實現(xiàn)移動語義:釋放this + 移動that)  
    MemoryBlock& operator=(MemoryBlock&& that)  
    {  
        if (this != &that)  
        {  
            // 釋放自身的資源  
            delete[] _data;  
  
            // 將自身的資源指針指向that對象所擁有的資源  
            _data = that._data;  
            _length = that._length;  
  
            // 將that對象原本指向該資源的指針設(shè)為空值  
            that._data = nullptr;  
            that._length = 0;  
        }  
        return *this;  
    }  
private:  
    size_t _length; // 資源的長度  
    int* _data; // 指向資源的指針潭流,代表資源本身  
};  
  
MemoryBlock f() { return MemoryBlock(50); }  
  
int main()  
{  
    MemoryBlock a = f();            // 調(diào)用移動構(gòu)造器竞惋,移動語義  
    MemoryBlock b = a;              // 調(diào)用拷貝構(gòu)造器,拷貝語義  
    MemoryBlock c = std::move(a);   // 調(diào)用移動構(gòu)造器灰嫉,移動語義  
    a = f();                        // 調(diào)用移動賦值運算符拆宛,移動語義  
    b = a;                          // 調(diào)用拷貝賦值運算符,拷貝語義  
    c = std::move(a);               // 調(diào)用移動賦值運算符讼撒,移動語義  
}  

轉(zhuǎn)發(fā)型引用
如果某個變量或參數(shù)被聲明為T&&類型浑厚,并且T需要經(jīng)過推導才可確定,那么這個變量或參數(shù)就是一個轉(zhuǎn)發(fā)型引用(forwarding reference)根盒。
轉(zhuǎn)發(fā)型引用由以下兩種語法形式產(chǎn)生钳幅,

  • 如果某個變量被聲明為auto&&類型,那么這個變量就是一個轉(zhuǎn)發(fā)型引用
  • 在函數(shù)模板中郑象,如果某個參數(shù)被聲明為T&&類型贡这,并且T是一個需要經(jīng)過推導才可確定的模板參數(shù)類型,那么這個參數(shù)就是一個轉(zhuǎn)發(fā)型引用

轉(zhuǎn)發(fā)型引用是不穩(wěn)定的厂榛,它的實際類型由它所綁定的值來確定盖矫。
轉(zhuǎn)發(fā)型引用既可以綁定左值,也可以綁定右值击奶。
如果綁定左值辈双,轉(zhuǎn)發(fā)型引用就成了左值引用。
如果綁定右值柜砾,轉(zhuǎn)發(fā)型引用就成了右值引用湃望。
轉(zhuǎn)發(fā)型引用在被C++標準所承認之前曾經(jīng)被稱作萬能引用(universal reference)。萬能引用這一術(shù)語的發(fā)明者痰驱,Effective C++系列的作者Scott Meyers認為证芭,如此異常靈活的引用類型不屬于右值引用,它應(yīng)該擁有自己的名字担映。
對于某個轉(zhuǎn)發(fā)型引用類型的變量(auto&&類型)來說废士,

  • 如果初始化表達式為左值(類型為U&),該變量將成為左值引用(類型為U&)蝇完。
  • 如果初始化表達式為右值(類型為U&&)官硝,該變量將成為右值引用(類型為U&&)矗蕊。

對于函數(shù)模板中的某個轉(zhuǎn)發(fā)型引用類型的形參(T&&類型)來說,

  • 如果對應(yīng)的實參為左值(類型為U&)氢架,模板參數(shù)T將被推導為引用類型U&傻咖,該形參將成為左值引用(類型為U&)。
  • 如果對應(yīng)的實參為右值(類型為U&&)岖研,模板參數(shù)T將被推導為非引用類型U卿操,該形參將成為右值引用(類型為U&&)。
struct X {};  
X&& var1 = X();                            // var1是右值引用孙援,只能綁定右值X()  
auto&& var2 = var1;                        // var2是轉(zhuǎn)發(fā)型引用硬纤,可以綁定左值var1  
                                           // var2的實際類型等同于左值var1,即X&  
auto&& var3 = X();                         // var3是轉(zhuǎn)發(fā)型引用赃磨,可以綁定右值X()  
                                           // var3的實際類型等同于右值X(),即X&&  
template<typename T>  
void g(std::vector<typename T>&& param1);  // param1是右值引用  
template<typename T>  
void f(T&& param2);                        // param2是轉(zhuǎn)發(fā)型引用  
  
X a;  
f(a);                // 模板函數(shù)f()的形參param2是轉(zhuǎn)發(fā)型引用洼裤,可以綁定左值a  
                     // 在此次調(diào)用中模板參數(shù)T將被推導為引用類型X&  
                     // 而形參param2的實際類型將等同于左值a邻辉,即X&  
f(X());              // 模板函數(shù)f()的形參param2是轉(zhuǎn)發(fā)型引用,可以綁定右值X()  
                     // 在此次調(diào)用中模板參數(shù)T將被推導為非引用類型X  
                     // 而形參param2的實際類型將等同于右值X()腮鞍,即X&&  
  
// 更多右值引用和轉(zhuǎn)發(fā)型引用  
const auto&& var4 = 10;                           // 右值引用  
template<typename T>  
void h(const T&& param1);                         // 右值引用  
template <typename T/*, class Allocator = allocator*/>  
class vector  
{  
public:  
    void push_back( T&& t );                      // 右值引用  
    template <typename Args...>  
    void emplace_back( Args&&... args );          // 轉(zhuǎn)發(fā)型引用  
};  

完美轉(zhuǎn)發(fā)
完美轉(zhuǎn)發(fā)(perfect forwarding)問題是指函數(shù)模板在向其他函數(shù)轉(zhuǎn)發(fā)(傳遞)自身參數(shù)(形參)時該如何保留該參數(shù)(實參)的左右值屬性的問題值骇。也就是說函數(shù)模板在向其他函數(shù)轉(zhuǎn)發(fā)(傳遞)自身形參時,如果相應(yīng)實參是左值移国,它就應(yīng)該被轉(zhuǎn)發(fā)為左值吱瘩;同樣如果相應(yīng)實參是右值,它就應(yīng)該被轉(zhuǎn)發(fā)為右值迹缀。這樣做是為了保留在其他函數(shù)針對轉(zhuǎn)發(fā)而來的參數(shù)的左右值屬性進行不同處理(比如參數(shù)為左值時實施拷貝語義使碾;參數(shù)為右值時實施移動語義)的可能性。如果將自身參數(shù)不分左右值一律轉(zhuǎn)發(fā)為左值祝懂,其他函數(shù)就只能將轉(zhuǎn)發(fā)而來的參數(shù)視為左值票摇,從而失去針對該參數(shù)的左右值屬性進行不同處理的可能性。

轉(zhuǎn)發(fā)型引用的引入主要是為了解決完美轉(zhuǎn)發(fā)問題砚蓬。在函數(shù)模板中需要保留左右值屬性的參數(shù)矢门,也就是要被完美轉(zhuǎn)發(fā)的參數(shù)須被聲明為轉(zhuǎn)發(fā)型引用類型,即參數(shù)必須被聲明為T&&類型灰蛙,而T必須被包含在函數(shù)模板的模板參數(shù)列表之中祟剔。按照轉(zhuǎn)發(fā)型引用類型形參的特點,該形參將根據(jù)所對應(yīng)的實參的左右值屬性而分別蛻變成左右值引用摩梧。但無論該形參成為左值引用還是右值引用物延,該形參在函數(shù)模板內(nèi)都將成為左值。這是因為該形參有名字障本,左值引用是左值教届,具名右值引用也同樣是左值响鹃。如果在函數(shù)模板內(nèi)照原樣轉(zhuǎn)發(fā)該形參,其他函數(shù)就只能將轉(zhuǎn)發(fā)而來的參數(shù)視為左值案训,完美轉(zhuǎn)發(fā)任務(wù)將會失敗买置。

#include<iostream>  
using namespace std;  
  
struct X {};  
void inner(const X&) {cout << "inner(const X&)" << endl;}  
void inner(X&&) {cout << "inner(X&&)" << endl;}  
template<typename T>  
void outer(T&& t) {inner(t);}  
  
int main()  
{  
    X a;  
    outer(a);  
    outer(X());  
}  
//inner(const X&)  
//inner(const X&)  

std::forward()
要在函數(shù)模板中完成完美轉(zhuǎn)發(fā)轉(zhuǎn)發(fā)型引用類型形參的任務(wù),我們必須在相應(yīng)實參為左值强霎,該形參成為左值引用時把它轉(zhuǎn)發(fā)成左值忿项,在相應(yīng)實參為右值,該形參成為右值引用時把它轉(zhuǎn)發(fā)成右值城舞。此時我們需要標準庫函數(shù)std::forward()轩触。
標準庫函數(shù) std::forward<T>(t) 有兩個參數(shù):模板參數(shù) T 與 函數(shù)參數(shù) t。函數(shù)功能如下:

  • 當T為左值引用類型U&時家夺,t 將被轉(zhuǎn)換為無名左值引用(左值脱柱,類型為U&)。
  • 當T為非引用類型U或右值引用類型U&&時拉馋,t 將被轉(zhuǎn)換為無名右值引用(右值榨为,類型為U&&)。

使用此函數(shù)煌茴,我們在函數(shù)模板中轉(zhuǎn)發(fā)類型為T&&的轉(zhuǎn)發(fā)型引用參數(shù) t 時随闺,只需將參數(shù) t 替換為std::forward<T>(t)即可完成完美轉(zhuǎn)發(fā)任務(wù)。這是因為蔓腐,

  • 如果 t 對應(yīng)的實參為左值(類型為U&)矩乐,模板參數(shù)T將被推導為引用類型U&,t 成為具名左值引用(類型為U&)回论,std::forward<T>(t)就會把 t 轉(zhuǎn)換成無名左值引用(左值散罕,類型為U&)。
  • 如果 t 對應(yīng)的實參為右值(類型為U&&)透葛,模板參數(shù)T將被推導為非引用類型U笨使,t 成為具名右值引用(類型為U&&),std::forward<T>(t)就會把 t 轉(zhuǎn)換成無名右值引用(右值僚害,類型為U&&)硫椰。
#include<iostream>  
using namespace std;  
  
struct X {};  
void inner(const X&) {cout << "inner(const X&)" << endl;}  
void inner(X&&) {cout << "inner(X&&)" << endl;}  
template<typename T>  
void outer(T&& t) {inner(forward<T>(t));}  
  
int main()  
{  
    X a;  
    outer(a);  
    outer(X());  
}  
//inner(const X&)  
//inner(X&&)  

參考資料
右值引用與移動構(gòu)造函數(shù) | cpp11新特性詳解與應(yīng)用
C++11嘗鮮:右值引用和轉(zhuǎn)發(fā)型引用
右值引用 | c++11 FAQ 中文版
cpp11 sniper

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市萨蚕,隨后出現(xiàn)的幾起案子靶草,更是在濱河造成了極大的恐慌,老刑警劉巖岳遥,帶你破解...
    沈念sama閱讀 218,451評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件奕翔,死亡現(xiàn)場離奇詭異,居然都是意外死亡浩蓉,警方通過查閱死者的電腦和手機派继,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,172評論 3 394
  • 文/潘曉璐 我一進店門宾袜,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人驾窟,你說我怎么就攤上這事庆猫。” “怎么了绅络?”我有些...
    開封第一講書人閱讀 164,782評論 0 354
  • 文/不壞的土叔 我叫張陵月培,是天一觀的道長。 經(jīng)常有香客問我恩急,道長杉畜,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,709評論 1 294
  • 正文 為了忘掉前任衷恭,我火速辦了婚禮此叠,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘随珠。我一直安慰自己拌蜘,他們只是感情好,可當我...
    茶點故事閱讀 67,733評論 6 392
  • 文/花漫 我一把揭開白布牙丽。 她就那樣靜靜地躺著,像睡著了一般兔魂。 火紅的嫁衣襯著肌膚如雪烤芦。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,578評論 1 305
  • 那天析校,我揣著相機與錄音构罗,去河邊找鬼。 笑死智玻,一個胖子當著我的面吹牛遂唧,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播吊奢,決...
    沈念sama閱讀 40,320評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼盖彭,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了页滚?” 一聲冷哼從身側(cè)響起召边,我...
    開封第一講書人閱讀 39,241評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎裹驰,沒想到半個月后隧熙,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,686評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡幻林,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,878評論 3 336
  • 正文 我和宋清朗相戀三年贞盯,在試婚紗的時候發(fā)現(xiàn)自己被綠了音念。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,992評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡躏敢,死狀恐怖闷愤,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情父丰,我是刑警寧澤肝谭,帶...
    沈念sama閱讀 35,715評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站蛾扇,受9級特大地震影響攘烛,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜镀首,卻給世界環(huán)境...
    茶點故事閱讀 41,336評論 3 330
  • 文/蒙蒙 一坟漱、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧更哄,春花似錦芋齿、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,912評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至麻敌,卻和暖如春栅炒,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背术羔。 一陣腳步聲響...
    開封第一講書人閱讀 33,040評論 1 270
  • 我被黑心中介騙來泰國打工赢赊, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人级历。 一個月前我還...
    沈念sama閱讀 48,173評論 3 370
  • 正文 我出身青樓释移,卻偏偏與公主長得像,于是被迫代替她去往敵國和親寥殖。 傳聞我的和親對象是個殘疾皇子玩讳,可洞房花燭夜當晚...
    茶點故事閱讀 44,947評論 2 355

推薦閱讀更多精彩內(nèi)容