C++11中的右值引用與移動語義

C++11的一個最主要的特性就是可以移動而非拷貝對象的能力。很多情況都會發(fā)生對象的拷貝,有時對象拷貝后就立即銷毀锨咙,在這些情況下眉踱,移動而非拷貝對象會大幅度提升性能挤忙。

右值與右值引用

為了支持移動操作,新標準引入了一種新的引用類型——右值引用谈喳,就是必須綁定到右值的引用册烈。我們通過&&而不是&來獲得右值引用。右值引用一個重要的特性就是只能綁定到將要銷毀的對象婿禽。

左值和右值是表達式的屬性赏僧,一些表達式生成或要求左值,而另一些則生成或要求右值扭倾。一般而言淀零,一個左值表達式表示的是一個對象的身份,而右值表達式表示的是對象的值膛壹。(可以取地址的驾中、有名字的就是左值唉堪;不能取地址的、沒有名字的就是右值哀卫。)兩者明顯的區(qū)別就是左值有持久的狀態(tài)巨坊,而右值要么是字面常量,要么是在表達式求值過程中創(chuàng)建的臨時對象此改。

類似于常規(guī)引用(左值引用)趾撵,一個右值引用也不過是某個對象的另一個名字而已。我們不能將左值引用綁定到要求轉(zhuǎn)換的表達式共啃、字面常量或是返回值的表達式占调,也不能把右值應用直接綁定到一個左值上。但是移剪,常量左值引用可以綁定到非常量左值究珊、常量左值、右值纵苛,是一個萬能引用類型剿涮。不過相比右值引用所引用的右值,常量左值引用所引用的右值在它的“余生”中只能是只讀的攻人。

int i = 42;
int &r = i;          //r引用i
int &r2 = i*2;       //錯誤取试,i*2是一個右值
int &&rr = i;       //錯誤怀吻,不能將一個右值引用綁定到一個左值上
int &&rr2 = i*2;     //正確瞬浓,將rr2綁定到一個乘法結(jié)果上
const int &r3 = i*2; //正確,將一個常量引用綁定到一個右值上

變量可以看做只有一個運算對象而沒有運算符的表達式蓬坡,是一個左值猿棉。我們不能將一個右值引用直接綁定到一個變量上,即使這個變量是右值引用類型屑咳。但是萨赁,我們可以通過新標準庫中的move函數(shù)來獲得綁定到左值上的右值引用。

int &&rr3 = std::move(rr2);

注意乔宿,被轉(zhuǎn)化的左值位迂,其生命周期并沒有隨著左右至的轉(zhuǎn)化而改變,在轉(zhuǎn)換之后使用左值可能造成運行時錯誤详瑞。因此,調(diào)用move就意味著承諾:除了對原左值變量賦值或銷毀它外臣缀,我們將不再使用它坝橡。不過更多的時候,我們需要轉(zhuǎn)換成右值引用的還是一個確實生命周期即將結(jié)束的對象精置。

移動構(gòu)造函數(shù)和移動賦值運算符

為了讓自定義類型也支持移動操作计寇,需要為其定義移動構(gòu)造函數(shù)和移動賦值運算符。這兩個成員類似對應的拷貝操作,但它們從給定對象竊取資源而不是拷貝資源番宁。類似于拷貝構(gòu)造函數(shù)元莫,移動構(gòu)造函數(shù)的第一個參數(shù)是該類類型的一個右值引用,任何額外的參數(shù)都必須有默認實參蝶押。除了完成資源移動外踱蠢,移動構(gòu)造函數(shù)還必須確保移后源對象處于有效的、可析構(gòu)的狀態(tài)棋电。

#include <iostream>  
#include <algorithm>  
  
class MemoryBlock  
{  
public:  
    // 構(gòu)造函數(shù)
    explicit MemoryBlock(size_t length) : _length(length) , _data(new int[length])  {} 
  
    // 析構(gòu)函數(shù) 
    ~MemoryBlock()  
    {
        if (_data != nullptr)   delete[] _data;
    } 
    
    // 拷貝賦值運算符 
    MemoryBlock& operator=(const MemoryBlock& other)  
    { 
        if (this != &other)  
        {  
            delete[] _data;  
            _length = other._length;  
            _data = new int[_length];  
            std::copy(other._data, other._data + _length, _data);  
        }  
        return *this;  
    } 
  
    // 拷貝構(gòu)造函數(shù) 
    MemoryBlock(const MemoryBlock& other)  
        : _length(0)  
        , _data(nullptr)  
    { 
        *this = other; 
    } 
    
    // 移動賦值運算符茎截,通知標準庫該構(gòu)造函數(shù)不拋出任何異常(如果拋出異常會怎么樣?)
    MemoryBlock& operator=(MemoryBlock&& other)  noexcept
    {
        if (this != &other)  
        {    
            delete[] _data;  
            // 移動資源
            _data = other._data;  
            _length = other._length;  
            // 使移后源對象處于可銷毀狀態(tài)
            other._data = nullptr;  
            other._length = 0;  
        }  
        return *this;  
    }
    
    // 移動構(gòu)造函數(shù)
    MemoryBlock(MemoryBlock&& other)  noexcept
        _data(nullptr)  
        , _length(0)  
    {  
        *this = std::move(other);  
    }  

    size_t Length() const  
    {  
        return _length;  
    }  

private:  
    size_t _length; // The length of the resource.  
    int* _data; // The resource.  
}; 

只有當一個類沒有定義任何自己版本的拷貝控制成員赶盔,且類的每個非static數(shù)據(jù)成員都可移動時企锌,編譯器才會為它合成移動構(gòu)造函數(shù)會移動賦值運算符。編譯器可以移動內(nèi)置類型于未;如果一個類類型有對應的移動操作撕攒,編譯器也能移動這個類型的成員。此外烘浦,定義了一個移動構(gòu)造函數(shù)或移動賦值運算符的類必須也定義自己的拷貝操作抖坪;否則,這些成員默認地定義為刪除的谎倔。而移動操作則不同柳击,它永遠不會隱式定義為刪除的。但如果我們顯式地要求編譯器生成=defualt的移動操作片习,且編譯器不能移動所有成員捌肴,則編譯器會將移動操作定義為刪除的函數(shù)。

如果一個類既有移動構(gòu)造函數(shù)又有拷貝構(gòu)造函數(shù)藕咏,編譯會使用普通的函數(shù)匹配規(guī)則來確定使用哪個構(gòu)造函數(shù)状知。但如果只定義了拷貝操作而未定義移動操作,編譯器不會合成移動構(gòu)造函數(shù)孽查,此時即使調(diào)用move來移動它們饥悴,也是調(diào)用的拷貝操作。

class Foo{
public:
    Foo() = default;
    Foo(const Foo&);
    // 為定義移動構(gòu)造函數(shù)
}盲再;
Foo x;
Foo y(x);                 //調(diào)用拷貝構(gòu)造函數(shù)
Foo z(std::move(x));      //調(diào)用拷貝構(gòu)造函數(shù)西设,因為未定義移動構(gòu)造函數(shù)
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市答朋,隨后出現(xiàn)的幾起案子贷揽,更是在濱河造成了極大的恐慌,老刑警劉巖梦碗,帶你破解...
    沈念sama閱讀 218,682評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件禽绪,死亡現(xiàn)場離奇詭異蓖救,居然都是意外死亡,警方通過查閱死者的電腦和手機印屁,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,277評論 3 395
  • 文/潘曉璐 我一進店門循捺,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人雄人,你說我怎么就攤上這事从橘。” “怎么了柠衍?”我有些...
    開封第一講書人閱讀 165,083評論 0 355
  • 文/不壞的土叔 我叫張陵洋满,是天一觀的道長。 經(jīng)常有香客問我珍坊,道長牺勾,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,763評論 1 295
  • 正文 為了忘掉前任阵漏,我火速辦了婚禮驻民,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘履怯。我一直安慰自己回还,他們只是感情好,可當我...
    茶點故事閱讀 67,785評論 6 392
  • 文/花漫 我一把揭開白布叹洲。 她就那樣靜靜地躺著柠硕,像睡著了一般。 火紅的嫁衣襯著肌膚如雪运提。 梳的紋絲不亂的頭發(fā)上蝗柔,一...
    開封第一講書人閱讀 51,624評論 1 305
  • 那天,我揣著相機與錄音民泵,去河邊找鬼癣丧。 笑死,一個胖子當著我的面吹牛栈妆,可吹牛的內(nèi)容都是我干的胁编。 我是一名探鬼主播,決...
    沈念sama閱讀 40,358評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼鳞尔,長吁一口氣:“原來是場噩夢啊……” “哼嬉橙!你這毒婦竟也來了唇牧?” 一聲冷哼從身側(cè)響起绰播,我...
    開封第一講書人閱讀 39,261評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎躬柬,沒想到半個月后昧旨,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體拾给,經(jīng)...
    沈念sama閱讀 45,722評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,900評論 3 336
  • 正文 我和宋清朗相戀三年兔沃,在試婚紗的時候發(fā)現(xiàn)自己被綠了蒋得。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,030評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡乒疏,死狀恐怖额衙,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情怕吴,我是刑警寧澤窍侧,帶...
    沈念sama閱讀 35,737評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站转绷,受9級特大地震影響伟件,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜议经,卻給世界環(huán)境...
    茶點故事閱讀 41,360評論 3 330
  • 文/蒙蒙 一斧账、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧煞肾,春花似錦咧织、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,941評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至蝙昙,卻和暖如春闪萄,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背耸黑。 一陣腳步聲響...
    開封第一講書人閱讀 33,057評論 1 270
  • 我被黑心中介騙來泰國打工桃煎, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人大刊。 一個月前我還...
    沈念sama閱讀 48,237評論 3 371
  • 正文 我出身青樓为迈,卻偏偏與公主長得像,于是被迫代替她去往敵國和親缺菌。 傳聞我的和親對象是個殘疾皇子葫辐,可洞房花燭夜當晚...
    茶點故事閱讀 44,976評論 2 355

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