条款11:在operator=中处理“自我赋值”
此条款,同上一条一样,也是针对operator= 的,同样套用两步走说完:
- 问题现象
- 怎么解决
一、问题现象
原书中的先是提出了一个现象:
假设建立一个class 用来保存一个指针指向一块动态分配的位图。
class Bitmap {......};
class Widget{
...
private:
Bitmap* pb ;
};
Widget& Widget::operator= (const Widget& rhs)
{
delete pb;
pb = new Bitmap(*rhs.pb);
return *this;
}
上面是一份不安全的 operator= 实现版本,因为 operator= 函数内的*this 和 rhs 有可能是同一个对象。
二、怎么解决
1、传统方法
Widget& Widget::operator= (const Widget& rhs)
{
if( this == rhs )
return *this;
delete pb;
pb = new Bitmap(*rhs.pb);
return *this;
}
传统方法,也是最简单的方法,是在重载赋值方法的第一步先检验一下传入对象是否为自己。
但这种方法并不是“异常安全的”。
例如在new操作符执行时跑出了异常(内存不足或因为Bitmap类的拷贝构造函数抛出异常),最终Widget对象会只有一个一块已被删除的Bitmap,因此代码是不安全的。
2、升级版
Widget& Widget::operator= (const Widget& rhs)
{
Bitmap* pOrig = pb ;
pb = new Bitmap(*rhs.pb);
delete pOrig ;
return *this;
}
这段代码可以来处理异常:如果new时抛出了异常,此时我们的pb对象还没有删除
这段代码还可以来处理“自我赋值”:我们对原bitmap做了一份复制、删除原bitmap,然后将pb再指向于复制的那一份。这个虽然不是处理“自我赋值”最高效的办法,但是行得通。
3、copy and swap 技术
替换上面的所有办法,我们可以使用“copy and swap”技术来解决“自我赋值”以及“异常处理”copy and swap技术和“异常安全性”有密切关系,会在条款29详细讲述
Widget& Widget::operator= (const Widget& rhs)
{
Widget temp( rhs ) ;
swap(temp);
return *this;
}
三、总结
确保当前对象自我赋值时operator=有良好行为。其中技术包括比较“来源对象”和“目标对象”的地址、精心周到的语句顺序、以及copy=and-swap
确定任何函数如果操作一个以上的对象,而其中多个对象是同一个对象时,其行为仍然正确