條款 11:在 operator= 中處理“自我賦值”
Handle assignment to self in operator=.
什麼是自我賦值
“自我賦值”發生在對象賦值給自己時
class Widget { ... };
Widget w;
...
w = w; // 賦值給自己
// 潛在的自我賦值
a[i] = a[j];
// 潛在的自我賦值
*px = *py;
// 潛在的自我賦值
class Base { ... };
class Derived : public Base { ... };
void doSomething (const Base* rb, Derived* pd); // rb 和 pd 可能指向同一對象
一份 operator= 錯誤實現示例
// 假設 Widget 存儲了一塊動態分配的位圖(bitmap)
Widget& Widget::operator=(const Widget& rhs)
{
delete pb;
pb = new Bitmap(*rhs.pb);
return *this;
}
問題一 自我賦值安全性
源和目的可能是同一對象,此時 delete pb 將會銷燬當前對象的 bitmap,導致返回後該指針未定義,一種修改方式如下
Widget& Widget::operator=(const Widget& rhs)
{
if (rhs == *this) return *this; // 證同測試
delete pb;
pb = new Bitmap(*rhs.pb);
return *this;
}
添加“證同測試”使得其具備“自我賦值”安全性,但是仍然存在問題二。
問題二 異常安全性
即使按照問題一的修改方式,仍可能存在問題,如果 new Bitmap 時發生異常,將會導致 pb 失效,一種簡單的修改方式如下
Widget& Widget::operator=(const Widget& rhs)
{
Bitmap* pOrig = pb;
pb = new Bitmap(*rhs.pb);
delete pOrg;
return *this;
}
即使沒有“證同測試”,這種修改方式也已經同事解決了問題一。如果比較關心效率,可以在加上“證同測試”,此時需要從效率上衡量必要性
另一種修改方式是 copy and swap 技術
class Widget
{
...
void swap(Widget* rhs);
...
};
Widget& Widget::operator=(const Widget& rhs)
{
Widget temp(rhs); // 爲 rhs 製作一份副本
swap(temp); // 將 *this 與上述副本交換
return *this;
}
可以使用 pass by value 技巧實現一種巧妙而略失清晰性的方式
Widget& Widget::operator=(Widget rhs) // pass by value
{
swap(rhs); // 將 *this 與副本交換
return *this;
}