Term11: Handle assignment to self in operator =
1. 在=操作符内自我赋值有风险
我们可能不太会写出类似a=a这样的表达式。但如果说对于同一个对象,其使用不同的指针、引用、容器等等指向它的时候,我们就很难保证一定不会自我赋值。自我赋值的实现里,如果仅仅是类似于a.x=b.x这样的赋值,可能除了浪费性能外并没有太大的影响,真正的危险在于new、delete之类的操作。例如:
class Bitmap;
class Widget
{
...
Bitmap* pb;
};
Widget& Widget::operator=(const Widget& rhs)
{
delete pb;
pb = new Bitmap(*rhs.pb);
return *this;
}
可以看出,如果rhs就是this指向的对象,那么会出现pb指向的对象被销毁,而赋值依然是这个被销毁对象的地址。后续对这个对象的访问势必导致内存访问错误。
下面介绍几种处理方法。
2. 证同测试
就是检测到自我赋值就直接退出而不做任何工作,如下:
Widget& Widget::operator=(const Widget& rhs)
{
if(this == &rhs) return *this;
delete pb;
pb = new Bitmap(*rhs.pb);
return *this;
}这种方法回避了自我赋值,但依然存在安全问题,如果pb = new Bitmap(*rhs.pb);开辟新空间没有成功,this->pb依然是无效的变量,访问它会导致内存错误。
3. 语序顺序控制
2中的代码是先delete再new,所以new的结果无法影响delete。如果先new,new失败了抛出异常,没有抛异常再delete,就可以避免这种情况。如下:
Widget& Widget::operator=(const Widget& rhs)
{
Bitmap* pOrig = pb;
pb = new Bitmap(*rhs.pb);
delete pOrig;
return *this;
}
4. Copy and swap
这种方法要求实现形如void Widget::swap(Widget& rhs);的方法,将rhs与*this的成员互换。代码如下:
Widget& Widget::operator=(const Widget& rhs)
{
Widget temp(rhs);
swap(rhs);
return *this;
}Widget& Widget::operator=(const Widget rhs)
{
swap(rhs);
return *this;
}这种方式将直接调用拷贝构造函数改为了隐式调用。上一段代码由于是传引用,因此需要显式调用;而这里由于是传值,参数压栈时会自动调用,代码显得更加精巧。当然,这种精巧使得代码更晦涩,不见得是好事。