看似愚蠢的 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;
}
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 析構時釋放舊資源
}
};
優勢:
noexcept swap 成員函數交換所有資源。rhs 析構時自動發生)。void f(T&, T&)),皆需考慮別名情況。