條款 11:在 operator= 中處理“自我賦值”

條款 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;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章