三規則
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
擁有的緩衝區。