在 C++ 11 之前有所謂 the rule of three,也就是自訂類別有寫自己的複製建構子、複製賦值運算子或解構子其中之一,應該將其他兩個也補上,因為通常編譯器自動產生的不會符合你需要的,不然你也不用自己寫了對吧。
在 C++ 11 後,因為多了移動語意,所以還要加上兩個特別的函式,也就是移動建構子和移動賦值運算子。
那要怎麼寫呢?假設有一個自訂類別 Foo,有兩個成員變數,一個是內建型別 int,另一個是自訂型別 std::vector
class Foo {
private:
int id_;
std::vector ticks_;
};
以下示範正確的宣告及實作方法。
首先是建構子,需不需要傳參數是根據此類別的需求而定,請至少寫一種,正確的寫法是使用 initializer list,也就是冒號 (:) 後面接成員變數的初始化,如果是在 body 內用賦值的,其實效率會慢,因為實際行為是先用 defualt 建構子建構出成員變數,再用複製賦值運算子拷貝一次:
class Foo {
public:
// constructor
Foo() : id_(0), ticks_() {}
// constructor
Foo(int id, const std::vector& ticks) : id_(id), ticks_(ticks) {}
// constructor 不好的寫法,先用預設建構子建構出 int 及 std::vector,再用 = 賦值一次
Foo(int id, const std::vector& ticks) {
id_ = id;
ticks_ = ticks;
}
private:
int id_;
std::vector ticks_;
};
第二個是解構子,解構子很簡單,首先考慮此函式有沒有指標需要 delete,有的話建議改用 smart point,再來考慮此類別有沒有虛擬函式,有的話就加 virtual,沒有的話就不用加:
class Foo {
public:
// destructor 沒有其他虛擬函式不用加上 virtual
~Foo() {}
// 或
// destructor 有其他虛擬函式就加上 virtual
virtual ~Foo() {}
private:
int id_;
std::vector ticks_;
};
第三個是複製建構子和移動建構子,一樣要使用 initializer list 的寫法,要注意的是移動建構子,如果有成員變數不是內建型別,請使用 std::move 包起來,這樣才可以轉成 rvalue,因為具名的右值參考 (named rvalue reference) 其實是左值 (rvalue),也就是程式中的 Foo&& rhs:
class Foo {
public:
// copy constructor
Foo(const Foo& rhs) : id_(rhs.id_), ticks_(rhs.ticks_) {}
// move constructor
Foo(Foo&& rhs) : id_(rhs.id_), ticks_(std::move(rhs.ticks_)) {} // 雖然 rhs 型別是 Foo&&,但其實是左值,因此要用 std::move 強制轉換成右值
private:
int id_;
std::vector ticks_;
};
最後是複製賦值運算子及移動賦值運算子,要注意的是需要先檢查是不是自我賦值,也就是撇除程式中 x = x 或 x = std::move(x) 的寫法,再來移動賦值運算子一樣非內建型別要用 std::move 包起來:
class Foo {
public:
// copy assignment
Foo& operator=(const Foo& rhs) {
if (this != &rhs) { // 檢查自我賦值
id_ = rhs.id_;
ticks_ = rhs.ticks_;
}
return *this;
}
// move assignment
Foo& operator=(Foo&& rhs) {
if (this != &rhs) { // 檢查自我賦值
id_ = rhs.id_;
ticks_ = std::move(rhs.ticks_); // 非內建型別用 std::move 包起來
}
return *this;
}
private:
int id_;
std::vector ticks_;
};
注意 assignment 正確寫法要傳回自己的參考
Foo& operator=(const Foo&);
Foo& operator=(Foo&&);
不然沒辦法這樣賦值
foo1 = foo2 = foo3;
而跟內建型別的賦值行為不同,對使用你自訂類別的使用者來說不是好的使用體驗。
最後,新寫一個類別時不要怕麻煩,養成好習慣一開始寫好就可以避免未來很多 debug 的時間。
留言
張貼留言