1. 程式人生 > 實用技巧 >c++ 右值引用&std::move()&移動建構函式&移動賦值函式

c++ 右值引用&std::move()&移動建構函式&移動賦值函式

右值引用

所謂右值引用就是必須繫結到右值的引用,我們通過 && 而不是 & 來獲得右值引用。

右值引用有一個很重要的性質--只能繫結到一個將要銷燬的物件,即左值持久,右值短暫。

  int i = 42;
  int& r = i; // 正確,r 引用 i
  int&& rr = i; // 錯誤,不能將一個右值引用繫結到一個左值上
  int& r2 = i * 42; // 錯誤,i * 42 是一個右值
  const int& r3 = i * 42; // 正確,可以將一個 const 的引用繫結到一個右值上
  int&& rr2 = i * 42
; // 正確,將 rr2 繫結到乘法結果上

標準庫 move 函式

雖然不能將一個右值引用直接繫結到一個左值上,但我們可以顯式地將一個左值轉換為對應的右值引用型別。我們還可以通過呼叫一個名為 move 的新標準函式來獲得繫結到左值的右值引用。

 int&& rr3 = std::move(rr1); // ok

move 呼叫告訴編譯器:我們有一個左值,但我們希望像一個右值一樣處理它,我們必須認識到,呼叫 move 就意味著:除了對 rr1 賦值或銷燬它以外,我們將不再使用它,在呼叫 move 之後, 我們不能對移後源物件的值做任何假設。

移動建構函式&移動賦值運算子

移動建構函式和移動賦值運算子“竊取”資源,它們通常不分配任何資源。因此這兩類函式通常不會丟擲任何異常,當不丟擲異常時必須宣告為 noexcept。

  Foo(Foo&& v) noexcept { // 移動建構函式
      ; // TODO
  }
  Foo& operator = (Foo&& a) noexcept { // 移動操作運算子
      ; // TODO
      return *this;
  }

用拷貝建構函式代替移動建構函式幾乎肯定是安全的,用賦值運算子代替移動賦值運算子也幾乎肯定是安全的。

  Foo x;
  Foo y(x); 
// 拷貝建構函式,x是一個左值 Foo z(std::move(x)); // 拷貝建構函式,因為未定義移動建構函式

在對 z 進行初始化時,我們呼叫了 move(x),它返回一個繫結到 x 的 Foo&&。Foo 拷貝建構函式是可行的,因為我們可以將一個 Foo&& 轉換為一個 const Foo&。因此 z 的初始化將使用 Foo 的拷貝建構函式。

引用限定符

用來指出一個非 static 成員函式可以用於左值或右值的符號。限定符 & 和 && 應該放在引數列表之後或 const 之後(如果有的話)。被 & 限定的函式只能用於左值,被 && 限定的函式只能用於右值。

  Foo x;
  Foo y(x); // 拷貝建構函式,x是一個左值
  Foo z(std::move(x)); // 拷貝建構函式,因為未定義移動建構函式

程式碼示例

  class A {
  public:
      int val = 0;
      A() {
          std::cout << "A()" << std::endl;
      }
      A(int v) {
          std::cout << "A(int v)" << std::endl;
      }
      A& operator = (const A& a) {
          std::cout << "A& operator = (const A& a)" << std::endl;
          return *this;
      }
      A(const A& a) {
          std::cout << "A(const A& a)" << std::endl;
      }
      A& operator = (A&& a) {
          std::cout << "A& operator = (A&& a)" << std::endl;
          return *this;
      }
      A(A&& v) {
          std::cout << "A(A&& v)" << std::endl;
      }
      virtual ~A() {
          std::cout << "~A()" << std::endl;
      }
      void print(int a) && { // 如果只有此函式,c.print(3);編譯報錯
          std::cout << "print(int a) &&" << std::endl;
      }
      void print(int a) & { // 如果只有此函式,std::move(d).print(2);編譯報錯
          std::cout << "print(int a) &" << std::endl;
      }
  };
  ​
  int main()
  {
      A b; // A()
      A c; // A()
      A d; // A()
  ​
      c = b; // A& operator = (const A& a)
      d = std::move(b); // A& operator = (A&& a)
  ​
     std::move(d).print(2); // print(int a) &&
     c.print(3); // print(int a) &
// ~A()
      // ~A()
      // ~A()
      return 0;
  }