首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> C語言 >> C++ >> C++ 入門

Effective C++ 條款11:處理自我賦值——身份檢查、異常安全與 Copy-and-Swap 惯用法

     編輯:C++ 入門

自我賦值無處不在

看似愚蠢的 a = a 實際常發生:a[i] = a[j]、*p = *q(p,q 可能指向同一對象)、基類引用與派生指針別名等。編譯器不會阻止,必須在代碼中防禦。

危害一:自我賦值安全性


class Widget {
    Bitmap* pb;
public:
    Widget& operator=(const Widget& rhs) {
        delete pb;               // 1. 釋放自身資源
        pb = new Bitmap(*rhs.pb); // 2. 複製 rhs 資源
        return *this;
    }
};

若 rhs == *this:步驟 1 刪除 pb 導致 rhs.pb 懸空,步驟 2 複製已釋放內存 → **未定義行為**。

修復:身份檢查


Widget& operator=(const Widget& rhs) {
    if (this == &rhs) return *this; // 早期返回
    delete pb;
    pb = new Bitmap(*rhs.pb);
    return *this;
}

危害二:異常安全性

若 new Bitmap 拋出異常(如內存不足),pb 已被 delete,對象處於**資源丟失、指針懸空**的破壞狀態。

修復一:調整語句順序(先分配後釋放)


Widget& operator=(const Widget& rhs) {
    Bitmap* newPb = new Bitmap(*rhs.pb); // 可能拋出,但 pb 未動
    delete pb;
    pb = newPb;
    return *this;
}

修復二:Copy-and-Swap 惯用法(強烈推薦)


class Widget {
    // ...
    void swap(Widget& rhs) noexcept { // 交換所有成員,不拋異常
        using std::swap;
        swap(pb, rhs.pb);
        // swap 其他成員...
    }
public:
    Widget& operator=(Widget rhs) { // 參數按值傳遞 → 自動調用拷貝構造
        swap(rhs);                   // 拿構造好的副本與自身交換
        return *this;                // rhs 析構時釋放舊資源
    }
};

優勢:

  1. 自動處理自我賦值(拷貝構造副本,交換無害)。
  2. 強異常安全:拷貝構造失敗則原對象不變;交換不拋異常。
  3. 代碼簡潔,復用拷貝構造邏輯。

Copy-and-Swap 核心要點

  • 參數**按值傳遞**(而非 const 引用),編譯器在調用點生成副本(C++17 強制拷貝省略優化)。
  • 提供 noexcept swap 成員函數交換所有資源。
  • 析構函數負責釋放舊資源(在參數 rhs 析構時自動發生)。

總結清單

  1. 拷貝賦值運算符**必須**正確處理自我賦值。
  2. 優先使用 **Copy-and-Swap**,同時解決自我賦值與異常安全。
  3. 若不使用 Swap,至少做**身份檢查**並**先分配後釋放**。
  4. 任何操作多個可能同源對象的函數(如 void f(T&, T&)),皆需考慮別名情況。
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved