问题聚焦:
自我赋值看似有点愚蠢的行为,其实总会发生的
首先:它是合法的,
其次,它不必定是安全的,
再次,它有时候不是那么明显。
先看一个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》