C++11中的右值引用和移動(dòng)語(yǔ)義

右值引用是C++11中新增的一種新的引用類型,它可以通過減少內(nèi)存的重復(fù)申請(qǐng)、拷貝和釋放,有效的提高C++程序的性能。理解什么是右值引用,首先要理解C++11中的lvalue、rvalue、xvalue,詳情請(qǐng)參考:C++11 中的左值、右值和將亡值.

右值引用和非常量引用的唯一區(qū)別是,非常量引用(non-const reference)通常綁定一個(gè)非臨時(shí)變量,而右值引用通常綁定一個(gè)臨時(shí)變量。

一個(gè)常量引用(const reference)既可以綁定臨時(shí)變量,也可以綁定非臨時(shí)變量。正是因?yàn)槌A恳?const reference)的這個(gè)特性,當(dāng)類的聲明中沒有移動(dòng)構(gòu)造函數(shù)時(shí),即A(A&& rhs){},傳遞臨時(shí)變量,任然可以調(diào)用A(const A& rsh){}構(gòu)造函數(shù)進(jìn)行構(gòu)造。

image.png

右值引用,避免深拷貝

一個(gè)深拷貝的例子

class A
{
public:
    A() : m_ptr(new int(0))
    {
        cout << "construct" << endl;
    }
    A(const A& rhs) : m_ptr(new int(*rhs.m_ptr)) // 拷貝構(gòu)造函數(shù)
    {
        cout << "copy construct" << endl;
    }
    ~A()
    {
        cout << "destruct" << endl;
        delete m_ptr;
    }
private:
    int* m_ptr;
};

A Get(bool flag)
{
    A a, b;
    if (flag)
    {
        return a;
    }
    return b;
}

int main()
{
    A a = Get(false);//調(diào)用拷貝構(gòu)造函數(shù)
    return 0;
}

執(zhí)行結(jié)果:
construct
construct
copy construct
destruct
destruct
destruct

問題:上述例子中Get函數(shù)會(huì)返回臨時(shí)變量,然后通過這臨時(shí)變量拷貝構(gòu)造一個(gè)新的對(duì)象a,臨時(shí)變量在拷貝構(gòu)造完成之后就銷毀了,如果堆內(nèi)存很大,這個(gè)拷貝構(gòu)造就比較消耗時(shí)間,帶來(lái)性能的損耗,是否有方法避免臨時(shí)對(duì)象的拷貝構(gòu)造呢?
答案:右值引用

改進(jìn)深拷貝的例子

在class A中添加如下移動(dòng)構(gòu)造函數(shù)

A(A&& rhs) : m_ptr(rhs.m_ptr) //移動(dòng)構(gòu)造函數(shù)
{
  rhs.m_ptr = nullptr;
  cout << "move construct" << endl;
}

執(zhí)行結(jié)果:
construct
construct
move construct
destruct
destruct
destruct

  • 改進(jìn)之后,不再調(diào)用拷貝構(gòu)造函數(shù),而是調(diào)用了移動(dòng)構(gòu)造函數(shù)(move construct)。從移動(dòng)構(gòu)造函數(shù)的實(shí)現(xiàn)可以看到,它的參數(shù)是一個(gè)右值引用類型的參數(shù)T&&,這里沒有深拷貝,只有淺拷貝,這樣就避免了對(duì)臨時(shí)對(duì)象的深拷貝,和資源的重復(fù)申請(qǐng)和釋放,提高了性能;
  • Get(false)函數(shù)返回的是一個(gè)右值,因此調(diào)用了參數(shù)類型為A&&的移動(dòng)構(gòu)造函數(shù);
  • 移動(dòng)構(gòu)造函數(shù)只是將臨時(shí)對(duì)象的資源做了淺拷貝,從而避免了深拷貝的開銷,提高了性能;
  • 這就是所謂的移動(dòng)語(yǔ)義(move semantic),右值引用的一個(gè)重要目的就是支持移動(dòng)語(yǔ)義;
    移動(dòng)構(gòu)造和拷貝構(gòu)造的內(nèi)存變化情況如下圖所示:


    image.png

    假設(shè)對(duì)象A中有一個(gè)指向堆內(nèi)存的指針m_ptr,則在拷貝構(gòu)造B時(shí),B會(huì)重新申請(qǐng)一份和A相同的內(nèi)存并拷貝A.m_ptr內(nèi)存中的內(nèi)容,然后釋放A.m_ptr。


    image.png

    若使用移動(dòng)構(gòu)造,則B只需要先將B.m_ptr=A.m_ptr,再將A.m_ptr設(shè)置為nullptr即可,這樣就避免了內(nèi)存的重新申請(qǐng)、拷貝和釋放。

左值引用和右值引用

  • 右值引用就是對(duì)一個(gè)右值進(jìn)行引用的類型,因?yàn)橛抑挡痪呙虼酥荒芡ㄟ^引用的方式找到它;
  • 通過右值引用的聲明,該右值又“重獲新生”,其聲明周期與右值引用類型變量的生命周期一樣;
  • 無(wú)論聲明為右值引用還是左值引用都必須立即初始化;

右值引用主要為了解決下面兩個(gè)問題

  1. 移動(dòng)語(yǔ)義 (move semantic)
  2. 完美轉(zhuǎn)發(fā) (perfect forwarding)

移動(dòng)語(yǔ)義(move semantic)

問題: 移動(dòng)語(yǔ)義是通過右值引用來(lái)匹配臨時(shí)值的,那么,普通的左值是否也能借助移動(dòng)語(yǔ)義來(lái)優(yōu)化性能呢?

  • C++11 為了解決上述問題,提供了std::move方法來(lái)將左值轉(zhuǎn)換為右值,從而方便應(yīng)用移動(dòng)語(yǔ)義;
  • move實(shí)際上并不能移動(dòng)任何東西,他唯一的功能是將一個(gè)左值強(qiáng)制轉(zhuǎn)換為一個(gè)右值引用,使我們可以通過右值引用使用該值;
  • It's confusing, because "move" is a verb; it sounds like it should do something. But in fact it's just a cast, just like const_cast or reinterpret_cast. Like those casts, std::move just lets us use an object in a different way; on its own, it doesn't do anything.

模擬string中移動(dòng)構(gòu)造函數(shù)的例子

  class string {
 public:
  string(const string& other);  // Copy constructor, exists pre C++11

  string(string&& other) {      // Move constructor, new in C++11
    length = other.length;
    capacity = other.capacity;
    data = other.data;
    other.data = nullptr;
  }

 private:
  size_t length;
  size_t capacity;
  const char* data;
};
  • 移動(dòng)構(gòu)造函數(shù)的處理流程:① 獲取臨時(shí)變量other中的指針、拷貝其他非指針成員,② 將臨時(shí)對(duì)象的指針設(shè)置為空,other.data = nullptr;。若不將other.data設(shè)置為空,other.data 將被釋放兩次,導(dǎo)致程序異常。下面是使用string的例子,當(dāng)使用臨時(shí)變量構(gòu)造時(shí),調(diào)用移動(dòng)構(gòu)造函數(shù)。
string a(get_string());  // move constructor
string b(a);             // copy constructor
string c(std::move(b));  // move constructor

注意:若string中沒有移動(dòng)構(gòu)造函數(shù)string(string&& other),上述的string c(std::move(b));仍然將調(diào)用拷貝構(gòu)造函數(shù)進(jìn)行構(gòu)造,因?yàn)?code>string(const string& other)的參數(shù)是const reference。

模擬string中的賦值操作符

通過上面的分析,你肯定發(fā)現(xiàn)這種寫法也可以應(yīng)用到string的賦值操作符上,代碼如下:

class string {
 public:
  string& operator=(const string& other); // Copy assn operator, pre C++11
  string& operator=(string&& other) {     // Move assn operator, new in C++11
    length = other.length;
    capacity = other.capacity;
    delete data;  // OK even if data is null
    data = other.data;
    other.data = nullptr;
    return *this;
  }
};

string a, b;
a = get_string();  // Move assignment
a = b;             // Copy assignment
a = std::move(b);  // Move assignment

C++11中,編譯器將會(huì)在類中自動(dòng)添加移動(dòng)構(gòu)造函數(shù)和移動(dòng)賦值操作符,因此C++11中,聲明一個(gè)類,編譯器會(huì)默認(rèn)添加構(gòu)造函數(shù)、拷貝構(gòu)造函數(shù)、析構(gòu)函數(shù)、移動(dòng)構(gòu)造函數(shù)和移動(dòng)賦值操作,號(hào)稱類中的"5駕馬車",而不是C++11之前的“3駕馬車”。

若用戶自定義拷貝構(gòu)造函數(shù),編譯器將不會(huì)自動(dòng)添加移動(dòng)構(gòu)造函數(shù)和移動(dòng)賦值操作。可以在這些函數(shù)之后添加 = delete,顯示的阻止編譯器自動(dòng)添加對(duì)應(yīng)的函數(shù)。

完美轉(zhuǎn)發(fā) (perfect forwarding)

轉(zhuǎn)下一篇C++11 模板推導(dǎo)、引用折疊和完美轉(zhuǎn)發(fā)

參考

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 229,619評(píng)論 6 539
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 99,155評(píng)論 3 425
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人,你說(shuō)我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 177,635評(píng)論 0 382
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我,道長(zhǎng),這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,539評(píng)論 1 316
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 72,255評(píng)論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,646評(píng)論 1 326
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,655評(píng)論 3 444
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,838評(píng)論 0 289
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 49,399評(píng)論 1 335
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 41,146評(píng)論 3 356
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 43,338評(píng)論 1 372
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,893評(píng)論 5 363
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,565評(píng)論 3 348
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,983評(píng)論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,257評(píng)論 1 292
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 52,059評(píng)論 3 397
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 48,296評(píng)論 2 376