通常我们在设计一个类并为它声明了赋值操作符时有一种状况总是需要处理——自赋值,如果用户在代码中注意避免自赋值当然可以但是那是很理想的状况,在一些复杂的情况下用户自己可能都不会意识到做了“自赋值”这个操作。例如:
*px=*py;//潜在的自我赋值
如果px和py恰巧指向同一个东西,这也是自我赋值。这些并不明显的自我赋值,是“别名”带来的结果。
我们来看一段代码:
class Bitmap {};
class Widget {
private: Bitmap* pb;
};
Widget& Widget::operator=(const Widget &rhs)
{
delete pb;
pb = new Bitmap(*rhs.pb);
return *this;
}
表面看上去好像合理,但是实际上并没有真正处理了自赋值而且也不存在异常安全性
问题在于如果*this指向的那个对象和rhs有可能是同一个对象。如果真是那样的话delete就不只是销毁对象的bitmap,它也把rhs的bitmap也销毁了(因为是同一个),这样我们的pb将会指向一个已经被删除的对象。
如何解决这个问题呢?
① 一种普遍的做法事“认同验证”,达到检验是否是同一个对象在进行赋值的目的:
Widget& Widget::operator=(const Widget&rhs)
{
if (this == &rhs)
return *this;
delete pb;
pb = new Bitmap(*rhs.pb);
return *this;
}
这样做的确处理了自赋值的问题,但是还有瑕疵。前一般operator=不仅不能正确处理自赋值,也不具备“异常安全性”,这个版本依然有可能存在异常方面的问题。更明确地说,如果”new Bitmap ”导致异常(不论是因为分配时内存不足或是因为Bitmap的拷贝构造函数抛出异常),Widget最终会持有一个指正指向一块被删除的Bitmap。这样的指针有害。你无法安全地删除它们,甚至无法安全地读取它们。唯一能对它们做的安全事情是付出许多调试能量找出错误的起源。
令人高兴的是,让operator=具备“异常安全性”往往自动获得“自我赋值安全”的回报。因此愈来愈多对“自我赋值”的处理态度是倾向不去管它,把焦点放在实现“异常安全性”上,这段代码只要改变一下顺序就能很好地处理这个问题,我们只要在
② 复制pb所指东西之前别删除pb:
Widget& Widget::operator=(const Widget& rhs)
{
Bitmap* porig = pb;
pb = new Bitmap(*rhs.pb);
delete porig;
return *this;
}
③ copy and swap(拷贝并交换)
略 待看完条款25回来写