複製和交換

如果你正在編寫一個管理資源的類,則需要實現所有特殊成員函式(請參閱 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 保持不變。接下來,我們在*thisrhs 之間交換成員,然後 rhs 超出範圍。當 operator=,它隱含地清除了 this 的原始資源(通過解構函式,我們沒有必要複製)。自我賦值也有效 - 複製和交換效率較低(涉及額外的分配和釋放),但如果這是不太可能的情況,我們不會減慢典型用例來解釋它。

Version >= C++ 11

上述公式原樣用於移動分配。

p1 = std::move(p2);

在這裡,我們從 p2 移動構造 rhs,其餘的一切都是有效的。如果一個類是可移動但不可複製的,則不需要刪除複製賦值,因為由於刪除了複製建構函式,該賦值操作符將只是格式錯誤。