自我賦值發生在對象被賦值給自己時,例如:
Widget w;
w = w; // 顯式自我賦值
a[i] = a[j]; // 若 i == j,隱式自我賦值
*px = *py; // 若 px == py,隱式自我賦值
實現「異常安全」的 operator= 往往自動獲得「自我賦值安全」。現代 C++ 實踐傾向於專注於異常安全性(強異常安全保證:操作失敗時對象狀態不變),而非顯式檢測自我賦值。
// 危險:既非異常安全也非自我賦值安全
Widget& Widget::operator=(const Widget& rhs) {
delete pImpl_; // 1. 釋放舊資源
pImpl_ = new Impl(*rhs.pImpl_); // 2. 分配新資源(可能拋出異常)
return *this;
}
// 若 rhs 為 *this:步驟 1 刪除了 pImpl_,步驟 2 嘗試從已刪除內存拷貝 → 未定義行為
// 若步驟 2 拋出異常:pImpl_ 已被刪除,對象處於無效狀態 → 違反強異常安全
class Widget {
public:
Widget& operator=(const Widget& rhs) {
Widget temp(rhs); // 1. 複製構造(可能拋出異常,但原對象未修改)
swap(temp); // 2. 交換內部狀態(不拋出異常)
return *this; // 3. temp 析構時釋放舊資源
}
void swap(Widget& rhs) noexcept { /* 交換所有成員 */ }
};
Widget& Widget::operator=(Widget rhs) { // 參數按值傳遞,調用者隱式構造副本
swap(rhs); // 交換狀態
return *this; // rhs 析構釋放舊資源
}
優勢:將拷貝操作移至參數構造階段,編譯器可優化(如拷貝消除、移動構造),代碼更簡潔。
| 場景 | 傳統實現 | Copy-and-Swap |
|---|---|---|
| 自我賦值 | 需顯式檢測 if (this == &rhs) | 無需檢測:複製構造創建獨立副本,交換後原對象獲取副本資源,舊資源隨 temp 銷毀 |
| 異常安全(強保證) | 難以保證:資源釋放後再分配可能拋出異常 | 天然滿足:拷貝構造失敗時原對象未修改;交換不拋出異常 |
| 代碼復雜度 | 高:需手動管理資源釋放/分配順序 | 低:委託給複製構造函數和 swap |
swap 成員函數(或非成員 swap),且標記 noexcept不再手寫自我賦值檢測。
使用 Copy-and-Swap 惯用法實現 operator=,
同時獲得 自我賦值安全 與 強異常安全保證,
代碼更簡潔、更健壯、更易維護。