条款 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;}
