Effective C++(11) 自我赋值(a=a)时会发生什么?
问题聚焦:
先看一个Demo自我赋值看似有点愚蠢的行为,其实总会发生的首先:它是合法的,其次,它不一定是安全的,再次,它有时候不是那么明显。
class Widget { ... }; Widget w; ... /** 最明显的自我赋值 **/ w = w; /** 不那么明显的自我赋值 **/ // 在某个地方实现了i = j或者相同作用的事情 a[i] = a[j] /** 潜在的自我赋值 **/ *px = *py; /** 更为隐蔽的自我赋值“别名” **/ class Base { ... }; class Derived : public Base { ... }; void doSomething( const Base& rb, Derived* pd); // rb 和 *pd可能是同一对象
一般而言,如果某段代码操作指针和引用,而它们被用来指向多个相同类型的对象,就需要考虑这些对象是否为同一个了。
自我赋值可能带来的两种潜在的危险:
- 自我赋值安全性
- 异常安全
来看下面的例子:
class Bitmap { ... }; class Widget { ... private: Bitmap* pd; }; /** operator=的代码实现 **/ Widget& Widget::operator=(const Widget& rhs) { delete pb; pb = new Bitmap(*rhs.pb); return *this; }
自我赋值安全性:
若rhs和当前对象是同一对象,那么在销毁当前对象的pb时,把rhs的对象也销毁了。
改进:认同测试
/** operator=的代码实现 **/ Widget& Widget::operator=(const Widget& rhs) { if (this == &rhs) return *this; // 认同测试 delete pb; pb = new Bitmap(*rhs.pb); return *this; }
上述改进后的代码避免了自我赋值安全性问题。
我们来关注一下第二个问题
异常安全性:
改进一:精心安排语句如果new Bitmap()发生异常,那么结果就是pb赋值直白,Widget最终持有一个指针指向一块被删除的Bitmap。
/** operator=的代码实现 **/ Widget& Widget::operator=(const Widget& rhs) { Bitmap* pOrig = pb; pb = new Bitmap(*rhs.pb); // 如果new Bitmap抛出异常,pb保持原状 delete pOrig; return *this; }
改进方案二:copy and swap
class Widget { ... void swap(Widget& rhs); ... }; Widget& Widget::operator=(const Wdiget& rhs) { Widget temp(rhs); swap(temp); return *this; }
思想:
拷贝构造函数要声明为“按值传递”按值传递会造成一份副本
小结:
确保当对象自我赋值时operator=有良好的行为。
包括:
对象来源目标对象地址语句顺序确定任何函数如果操作一个以上的对象,而其中多个对象是同一个对象时,其行为仍然正确。
参考资料:
《Effective C++ 3rd》