C++11新增的移動(dòng)語義

基礎(chǔ)

  1. C++11的所有值必屬于以下三種之一:
  • 左值
    所有的變量都是左值
  • 將亡值
    將要被移動(dòng)的對(duì)象、T&&函數(shù)返回值、std::move返回值和轉(zhuǎn)換為T&&的類型的轉(zhuǎn)換函數(shù)的返回值等
  • 純右值
    1. 立即數(shù),比如
      int a = 5,
      其中的5就是右值
    2. 函數(shù)的拷貝返回值,比如
      int a = []{return 5;}
      a為lambda函數(shù)的返回值。返回的是5的匿名、拷貝的版本,真正的5在匿名函數(shù)返回前就銷毀了。
  1. 一種簡單的判斷是否是左值的辦法: 可以取地址就是左值。
  2. 左值引用:我們常見的引用如A &a就是左值引用。
  3. 右值引用常見形式:void func(A &&a)

移動(dòng)語義

1. 通俗解釋:“偷”(侯捷老師語)

等號(hào)左邊的對(duì)象可以steal等號(hào)右邊對(duì)象的資源,就構(gòu)成了移動(dòng)語義。這里可以聯(lián)想淺拷貝和深拷貝(實(shí)質(zhì)上移動(dòng)語義就是在語言層面上實(shí)現(xiàn)的深淺拷貝的實(shí)現(xiàn)方式)

2. 直觀印象

vector<string> a, b;
a = b; // 拷貝語義
a = std::move(b); // 典型的移動(dòng)語義

在拷貝語義中,會(huì)調(diào)用類的拷貝構(gòu)造函數(shù)賦值構(gòu)造函數(shù)(即operator =)
在移動(dòng)語義中,會(huì)自動(dòng)調(diào)用類的移動(dòng)構(gòu)造函數(shù)(move constructor),是C++11新加入的特性,其定義方法稍后說明。

  • 備注1:move函數(shù)并沒有神奇到可以自動(dòng)調(diào)用b的移動(dòng)構(gòu)造函數(shù)的地步,其作用只是將b從左值(lvalue)轉(zhuǎn)化為右值(rvalue)而已,可以將move函數(shù)理解為move_cast<rvalue>(lvalue v)。
  • 備注2:實(shí)質(zhì)上,右值引用可以實(shí)現(xiàn)兩個(gè)東西:
    - 生存周期變長(續(xù)命)
    - 在賦值時(shí)調(diào)用移動(dòng)構(gòu)造函數(shù)
    他們都是為了避免無謂的拷貝。
  • 備注3:std::move和std::forward都是編譯期行為,不產(chǎn)生任何運(yùn)行期的代碼。左值引用/右值引用的區(qū)別在于匯編層面。

2. 要解決的問題&典型使用場景

移動(dòng)語義與其配套的一些語言特性、關(guān)鍵字是為了解決C++語言中太多的、無謂的、匿名/臨時(shí)對(duì)象的拷貝。
所以,右值引用的典型使用場景就在于避免拷貝,而拷貝出現(xiàn)的最多的地方就在函數(shù)傳參返回對(duì)象時(shí)。
一個(gè)典型的對(duì)象拷貝例子:

vector<int> get_vi()
{
  vector<int> vt;
  //...
  return vt;
}

vector<int> v = get_vi();

在這個(gè)例子中,在get_vi()里,構(gòu)建了一個(gè)臨時(shí)變量vt,并將其拷貝一份(C++的函數(shù)返回值都是通過拷貝),將拷貝再復(fù)制給函數(shù)外的v,是非常低效的。如果返回一個(gè)右值引用如下:

vector<int> && get_vi()
{
    vector<int> vt;
    //...
    return move(vt);
}

則可以給vt“續(xù)命”,使它的存續(xù)周期超過get_vi()的生命周期,同時(shí)完成v和vt之間的淺拷貝(因?yàn)閟tl的vector是這么實(shí)現(xiàn)的,如果是自己開發(fā)的類,則一切都可以在移動(dòng)構(gòu)造函數(shù)中自定義)。

3. 完美轉(zhuǎn)發(fā)

要實(shí)現(xiàn)“完美”轉(zhuǎn)發(fā),首先要對(duì)“不完美”的轉(zhuǎn)發(fā)有了解。
在C++語言設(shè)計(jì)上,函數(shù)形參中的A &&a具有二義性:它不僅能接收左值,也可以接收右值??紤]以下代碼:

template <typename T>
void print(T && t)
{
    cout<<"rvalue"<<endl;
}

template <typename T>
void print(T& t)
{
    cout<<"lvalue"<<endl;
}

template <typename T>
void test_forward(T && t)
{
    print(t);
    print(std::forward<T>(t));
    print(std::move<T>(t)); 
}

int main()
{
    int i = 0;
    test_forward<int>(i);
    test_forward<int>(0);
    return 0;
}

以上代碼的運(yùn)行結(jié)果是:
lvalue
lvalue
rvalue
lvalue
rvalue
rvalue
解釋如下:

  1. 在傳入函數(shù)之后,所有的右值引用都會(huì)被解釋為左值引用,所以兩組輸出的第一個(gè)都是lvalue。
  2. std::forward的作用在于:把傳入函數(shù)內(nèi)部的引用(已經(jīng)被統(tǒng)一解釋為左值引用),恢復(fù)為它原本的樣子。它在傳入函數(shù)前是左值引用就是左值引用,在傳入函數(shù)前是右值引用就是右值引用。,所以第一組輸出的第二個(gè)是lvalue,第二組輸出的第二個(gè)是rvalue。
  3. std::move的作用在于把左值強(qiáng)轉(zhuǎn)為右值,所以兩組輸出的第三個(gè)都是rvalue。

總結(jié)

右值引用賦值給左值時(shí),會(huì)自動(dòng)調(diào)用移動(dòng)構(gòu)造函數(shù),形成移動(dòng)語義(如函數(shù)返回一個(gè)內(nèi)部值右值引用)。按個(gè)人理解,C++11引入右值引用和移動(dòng)語義的目的在于:在語言層面提供了原生的深/淺拷貝支持。
雖然C++為開發(fā)者提供了“瑞士軍刀”,但C++11的引入的大量特性,個(gè)人認(rèn)為是在為編程者提供一套“最佳實(shí)踐”。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容