三規則

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.namep2.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;
}

由於需要釋放現有緩衝區,因此複製賦值運算子的實現變得複雜。複製和交換技術建立一個包含新緩衝區的臨時物件。交換*thiscopy 的內容將獲得原始緩衝區的 copy 的所有權。當函式返回時,copy 的銷燬會釋放先前由*this 擁有的緩衝區。