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

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

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

一個常量引用(const reference)既可以綁定臨時變量,也可以綁定非臨時變量。正是因為常量引用(const reference)的這個特性,當類的聲明中沒有移動構造函數時,即A(A&& rhs){},傳遞臨時變量,任然可以調用A(const A& rsh){}構造函數進行構造。

image.png

右值引用,避免深拷貝

一個深拷貝的例子

class A
{
public:
    A() : m_ptr(new int(0))
    {
        cout << "construct" << endl;
    }
    A(const A& rhs) : m_ptr(new int(*rhs.m_ptr)) // 拷貝構造函數
    {
        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);//調用拷貝構造函數
    return 0;
}

執行結果:
construct
construct
copy construct
destruct
destruct
destruct

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

改進深拷貝的例子

在class A中添加如下移動構造函數

A(A&& rhs) : m_ptr(rhs.m_ptr) //移動構造函數
{
  rhs.m_ptr = nullptr;
  cout << "move construct" << endl;
}

執行結果:
construct
construct
move construct
destruct
destruct
destruct

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


    image.png

    假設對象A中有一個指向堆內存的指針m_ptr,則在拷貝構造B時,B會重新申請一份和A相同的內存并拷貝A.m_ptr內存中的內容,然后釋放A.m_ptr。


    image.png

    若使用移動構造,則B只需要先將B.m_ptr=A.m_ptr,再將A.m_ptr設置為nullptr即可,這樣就避免了內存的重新申請、拷貝和釋放。

左值引用和右值引用

  • 右值引用就是對一個右值進行引用的類型,因為右值不具名,因此只能通過引用的方式找到它;
  • 通過右值引用的聲明,該右值又“重獲新生”,其聲明周期與右值引用類型變量的生命周期一樣;
  • 無論聲明為右值引用還是左值引用都必須立即初始化;

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

  1. 移動語義 (move semantic)
  2. 完美轉發 (perfect forwarding)

移動語義(move semantic)

問題: 移動語義是通過右值引用來匹配臨時值的,那么,普通的左值是否也能借助移動語義來優化性能呢?

  • C++11 為了解決上述問題,提供了std::move方法來將左值轉換為右值,從而方便應用移動語義;
  • move實際上并不能移動任何東西,他唯一的功能是將一個左值強制轉換為一個右值引用,使我們可以通過右值引用使用該值;
  • 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中移動構造函數的例子

  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;
};
  • 移動構造函數的處理流程:① 獲取臨時變量other中的指針、拷貝其他非指針成員,② 將臨時對象的指針設置為空,other.data = nullptr;。若不將other.data設置為空,other.data 將被釋放兩次,導致程序異常。下面是使用string的例子,當使用臨時變量構造時,調用移動構造函數。
string a(get_string());  // move constructor
string b(a);             // copy constructor
string c(std::move(b));  // move constructor

注意:若string中沒有移動構造函數string(string&& other),上述的string c(std::move(b));仍然將調用拷貝構造函數進行構造,因為string(const string& other)的參數是const reference。

模擬string中的賦值操作符

通過上面的分析,你肯定發現這種寫法也可以應用到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中,編譯器將會在類中自動添加移動構造函數和移動賦值操作符,因此C++11中,聲明一個類,編譯器會默認添加構造函數、拷貝構造函數、析構函數、移動構造函數和移動賦值操作,號稱類中的"5駕馬車",而不是C++11之前的“3駕馬車”。

若用戶自定義拷貝構造函數,編譯器將不會自動添加移動構造函數和移動賦值操作??梢栽谶@些函數之后添加 = delete,顯示的阻止編譯器自動添加對應的函數。

完美轉發 (perfect forwarding)

轉下一篇C++11 模板推導、引用折疊和完美轉發。

參考

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。