複製和交換
如果你正在編寫一個管理資源的類,則需要實現所有特殊成員函式(請參閱 Rule of Three / Five / Zero )。編寫複製建構函式和賦值運算子的最直接方法是:
person(const person &other)
: name(new char[std::strlen(other.name) + 1])
, age(other.age)
{
std::strcpy(name, other.name);
}
person& operator=(person const& rhs) {
if (this != &other) {
delete [] name;
name = new char[std::strlen(other.name) + 1];
std::strcpy(name, other.name);
age = other.age;
}
return *this;
}
但這種方法存在一些問題。它沒有強大的異常保證 - 如果 new[]
丟擲,我們已經清除了 this
所擁有的資源並且無法恢復。我們在複製賦值中複製了許多複製構造的邏輯。我們必須記住自我分配檢查,這通常只會增加複製操作的開銷,但仍然很關鍵。
為了滿足強大的異常保證並避免程式碼重複(使用後續的移動賦值運算子加倍),我們可以使用複製和交換習慣用法:
class person {
char* name;
int age;
public:
/* all the other functions ... */
friend void swap(person& lhs, person& rhs) {
using std::swap; // enable ADL
swap(lhs.name, rhs.name);
swap(lhs.age, rhs.age);
}
person& operator=(person rhs) {
swap(*this, rhs);
return *this;
}
};
為什麼這樣做?考慮一下我們遇到的情況
person p1 = ...;
person p2 = ...;
p1 = p2;
首先,我們從 p2
複製構造 rhs
(我們不必在這裡複製)。如果該操作丟擲,我們在 operator=
中沒有做任何事情,並且 p1
保持不變。接下來,我們在*this
和 rhs
之間交換成員,然後 rhs
超出範圍。當 operator=
,它隱含地清除了 this
的原始資源(通過解構函式,我們沒有必要複製)。自我賦值也有效 - 複製和交換效率較低(涉及額外的分配和釋放),但如果這是不太可能的情況,我們不會減慢典型用例來解釋它。
Version >= C++ 11
上述公式原樣用於移動分配。
p1 = std::move(p2);
在這裡,我們從 p2
移動構造 rhs
,其餘的一切都是有效的。如果一個類是可移動但不可複製的,則不需要刪除複製賦值,因為由於刪除了複製建構函式,該賦值操作符將只是格式錯誤。