条款 11:在 operator= 中处理“自我赋值”

Handle assignment to self in operator=.

什么是自我赋值

“自我赋值”发生在对象赋值给自己时

  1. class Widget { ... };
  2. Widget w;
  3. ...
  4. w = w; // 赋值给自己
  5. // 潜在的自我赋值
  6. a[i] = a[j];
  7. // 潜在的自我赋值
  8. *px = *py;
  9. // 潜在的自我赋值
  10. class Base { ... };
  11. class Derived : public Base { ... };
  12. void doSomething (const Base* rb, Derived* pd); // rb 和 pd 可能指向同一对象

一份 operator= 错误实现示例

  1. // 假设 Widget 存储了一块动态分配的位图(bitmap)
  2. Widget& Widget::operator=(const Widget& rhs)
  3. {
  4. delete pb;
  5. pb = new Bitmap(*rhs.pb);
  6. return *this;
  7. }

问题一 自我赋值安全性

源和目的可能是同一对象,此时 delete pb 将会销毁当前对象的 bitmap,导致返回后该指针未定义,一种修改方式如下

  1. Widget& Widget::operator=(const Widget& rhs)
  2. {
  3. if (rhs == *this) return *this; // 证同测试
  4. delete pb;
  5. pb = new Bitmap(*rhs.pb);
  6. return *this;
  7. }
  8. 添加“证同测试”使得其具备“自我赋值”安全性,但是仍然存在问题二。

问题二 异常安全性

即使按照问题一的修改方式,仍可能存在问题,如果 new Bitmap 时发生异常,将会导致 pb 失效,一种简单的修改方式如下

  1. Widget& Widget::operator=(const Widget& rhs)
  2. {
  3. Bitmap* pOrig = pb;
  4. pb = new Bitmap(*rhs.pb);
  5. delete pOrg;
  6. return *this;
  7. }

即使没有“证同测试”,这种修改方式也已经同事解决了问题一。如果比较关心效率,可以在加上“证同测试”,此时需要从效率上衡量必要性
另一种修改方式是 copy and swap 技术

  1. class Widget
  2. {
  3. ...
  4. void swap(Widget* rhs);
  5. ...
  6. };
  7. Widget& Widget::operator=(const Widget& rhs)
  8. {
  9. Widget temp(rhs); // 为 rhs 制作一份副本
  10. swap(temp); // 将 *this 与上述副本交换
  11. return *this;
  12. }

可以使用 pass by value 技巧实现一种巧妙而略失清晰性的方式

  1. Widget& Widget::operator=(Widget rhs) // pass by value
  2. {
  3. swap(rhs); // 将 *this 与副本交换
  4. return *this;
  5. }