三規則
Version <= C++ 03
Rule of Three 宣告如果某個型別需要具有使用者定義的複製建構函式,複製賦值運算子或解構函式,那麼它必須具有所有三個。
規則的原因是需要三個中的任何一個的類管理一些資源(檔案控制代碼,動態分配的記憶體等),並且需要所有三個來一致地管理該資源。複製函式處理資源如何在物件之間複製,解構函式將根據 RAII 原則銷燬資源。
考慮管理字串資源的型別:
class Person
{
char* name;
int age;
public:
Person(char const* new_name, int new_age)
: name(new char[std::strlen(new_name) + 1])
, age(new_age)
{
std::strcpy(name, new_name);
}
~Person() {
delete [] name;
}
};
由於 name 是在建構函式中分配的,解構函式會釋放它以避免洩漏記憶體。但是如果複製了這樣的物件會發生什麼?
int main()
{
Person p1("foo", 11);
Person p2 = p1;
}
首先,將建造 p1。然後 p2 將從 p1 複製。但是,C++生成的複製建構函式將按原樣複製該型別的每個元件。這意味著 p1.name 和 p2.name 都指向相同的字串。
當 main 結束時,將呼叫解構函式。第一個 p2 的解構函式將被呼叫; 它會刪除字串。然後將呼叫 p1 的解構函式。但是,該字串已被刪除。在已刪除的記憶體上呼叫 delete 會產生未定義的行為。
為避免這種情況,有必要提供合適的拷貝建構函式。一種方法是實現引用計數系統,其中不同的 Person 例項共享相同的字串資料。每次執行復制時,共享引用計數都會遞增。解構函式然後遞減引用計數,僅在計數為零時釋放記憶體。
或者我們可以實現值語義和深度複製行為 :
Person(Person const& other)
: name(new char[std::strlen(other.name) + 1])
, age(other.age)
{
std::strcpy(name, other.name);
}
Person &operator=(Person const& other)
{
// Use copy and swap idiom to implement assignment
Person copy(other);
swap(copy); // assume swap() exchanges contents of *this and copy
return *this;
}
由於需要釋放現有緩衝區,因此複製賦值運算子的實現變得複雜。複製和交換技術建立一個包含新緩衝區的臨時物件。交換*this 和 copy 的內容將獲得原始緩衝區的 copy 的所有權。當函式返回時,copy 的銷燬會釋放先前由*this 擁有的緩衝區。