Way to C++: 2. Move
在只有 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;
}