Way to C++: 2. Move

Mar 18, 2020·

2 min read

上一篇

在只有 copy 行為的情況下,會有不少的效能問題,例如一個最常見的用法:

std::vector<int> getPrimes(int n); // return p\_1, ..., p\_n primes
auto primes = getPrimes(100000); // copy??

在只有 copy 語意的情況下, 第二行會需要複製大量的陣列元素,可是這是不太需要的。

所以 c++11 引進了所謂 r-value reference 的概念。

R-value

r-value 可以直接理解為「只可以擺在 assignment 右邊的值」,與之相對的則是 l-value 「可以擺在左邊的值」。

一些直接的例子:

int b;  
int foo(int a) {  
  return a;  
}  
int& bar() {  
  return b;  
}

// b: l-value  
// foo(): r-value  
// bar(): l-value  
// (b+1): r-value  
// 99: r-value

而最上面的 getPrimes(100000) 也是 r-value。

而 r-value reference 就是 type&&

一個用 code 來分辨的方式可以這樣寫:

void foo(const int& a) {  
  puts("l-value");  
}  
void foo(int&& a) {  
  puts("r-value");  
}  
int main() {  
  int a = 1;  
  foo(a); // l-value  
  foo(a + 1); // r-value  
  return 0;  
}

搬移

class 裡面有兩個搬移函數,分別是:

  • move constructor
  • move assignment
C::C(C&& c);  
C& C::operator=(C&& c);

要自行實作搬移函數,除了把指標位置複製過進來,還要把被搬移的東西設置成「空」。

舉上一篇的 IntBuff 來說,我們可以這樣寫:

IntBuff::IntBuff(IntBuff&& ib) {  
  buf = ib.buf;  
  ib.buf = nullptr;  
  sz = ib.sz;  
  ib.sz = 0;  
}

IntBuff& IntBuff::operator=(IntBuff&& ib) {  
  delete buf; // delete nullptr is ok  
  buf = ib.buf;  
  ib.buf = nullptr;  
  sz = ib.sz;  
  ib.sz = 0;  
}

因為在我們在「搬移」的同時,仍然注意原本被搬移的對象的指標,讓他的指標不會和自己的指標指向同一區塊,刪除時也不會出問題。

去看 cppreference 可以發現, std::vector 確實有實作 vector(vector&&),所以

auto primes = getPrimes(100000);

確實可以觸發 move constructor,而不會觸發 copy constructor。

std::move

一些可以直接是 r-value reference 的確實可以直接觸發搬移,可是假如希望一個變數觸發搬移呢?

vector<int> a, b(10, 0);  
a = b; // copy

std::move 可以把傳進去的轉換成他的 r-value reference 型別。

void foo(const int& a) {  
  puts("l-value");  
}  
void foo(int&& a) {  
  puts("r-value");  
}  
int main() {  
  int a = 1;  
  foo(a); // l-value  
  foo(std::move(a)); // r-value  
  return 0;  
}

IntBuff version3

附上 IntBuff 有實作搬移的版本:

#include <algorithm>
#include <cstdio>
#include <memory>

class IntBuff {
public:
    IntBuff() = default;
    explicit IntBuff(size_t sz) : sz(sz) {
        if (sz) {
            arr = new int[sz];
        }
    };

    IntBuff(const IntBuff& ib) {
        sz = ib.sz;
        arr = new int[sz];
        for (int lx = 0; lx < sz; lx++) {
            arr[lx] = ib.arr[lx];
        }
    }

    IntBuff& operator=(const IntBuff& ib) {
        IntBuff tmp(ib);  // copy-and-swap
        std::swap(tmp.arr, arr);
        std::swap(tmp.sz, sz);
        return *this;
    }

    IntBuff(IntBuff&& ib) {
        arr = ib.arr;
        ib.arr = nullptr;
        sz = ib.sz;
        ib.sz = 0;
    }

    IntBuff& operator=(IntBuff&& ib) {
        delete arr;
        arr = ib.arr;
        ib.arr = nullptr;
        sz = ib.sz;
        ib.sz = 0;
        return *this;
    }

    ~IntBuff() {
        printf("%p\n", arr);
        delete[] arr;
    }

private:
    int* arr = nullptr;
    size_t sz = 0;
};

int main() {
    {
        IntBuff a1(size_t(10)), b1;
        b1 = std::move(a1);
    }
    return 0;
}