前言
最近在读《Effective C++》,对里面的思想和代码深有感触,因此在此做点记录并加以自己的理解,方便以后查看。
本文内容来自条款11:在operator=中处理"自我赋值"(Handle assignment to self in operator=.)
。
何谓自我赋值
考虑如下代码:
class A{...}
A a;
a = a;//赋值给自己
上述代码看起来不太可能出现,却是合法的,但这只是明显的自我赋值。潜在的自我赋值如下:
当i和j有相同的值时:a[i] = a[j];
当px和py指向同一个东西时:*px = *py;
以及函数参数中的多个对象是同一个的情况:
class A{...};
class B : public A{...};
void doSomething(const A& ra,B* pb);//rb和*pd有可能是同一个对象
另外,考虑如下代码:
class Bitmap{...};
class Widget{
...
public:
Widget& operator=(const Widget&);
private:
Bitmap* pb;//指针,指向一个从heap分配来的对象
};
Widget& Widget::operator=(const Widget& rhs){
delete pb;//停止使用当前的bitmap
pb = new Bitmap(*rhs.pb);//通过拷贝构造函数来拷贝rhs的*pb对象
return *this;
}
此处存在问题,如果rhs和*this是同一个对象,那么就会出现问题,在new Bitmap时,用到的rhs.pb指针指向的内容已经被销毁,因此这是隐含有安全问题的,也就是没有考虑自我赋值的影响。
这段代码隐含的问题是不具备自我赋值安全性和异常安全性。
解决方案之证同测试
传统的解决方案是做证同测试来达到自我赋值的检验目的:
Widget& Widget::operator=(const Widget& rhs){
if(this == &rhs) return *this;//证同测试,如果是自我赋值,就什么也不做
delete pb;
pb = new Bitmap(*rhs.pb);
return *this;
}
但这个方案只解决了自我赋值安全性,而仍然存在异常安全性问题:如果"new Bitmap"时抛出异常,pb最终会指向一块被删除的空间,很明显这样是有问题的。
解决方案之考虑异常安全性
让operator=具备"异常安全性"时,往往会自动获得"自我赋值安全性"。因此越来越多人对"自我赋值"的处理态度是不处理,而把焦点放到实现"异常安全性"上。因此,只需要注意在复制pb所指向的东西之前别删除pb:
Widget& Widget::operator=(const Widget& rhs){
Bitmap *pOrig = pb;//记住原先的pb值
pb = new Bitmap(*rhs.pb);
delete pOrig;//删除原先的pb
return *this;
}
这样或许不是处理"自我赋值"最高效的办法,但它同时兼顾了自我赋值安全性和异常安全性。
解决方案之"copy and swap"技术
跟上面的方法是一个道理,但这种方法与"异常安全性”密切相关,因此值得看看其实现手法。
- 方式一:基于pass-by-reference的实现
class Widget{ ... void swap(Widget& rhs);//交换*this和rhs的数据 ... } Widget& Widget::operator=(const Widget& rhs){ Widget temp(rhs);//为rhs创建一份副本 swap(temp);//将*this的数据与temp的数据互换 return *this; }
- 方式二:基于pass-by-value的实现
class Widget{ ... void swap(Widget& rhs);//交换*this和rhs的数据 ... } Widget& Widget::operator=(const Widget rhs){ //pass-by-value传参,本身就是一份副本 swap(temp);//将*this的数据与rhs的数据互换 return *this; }
总结
- 确保当对象自我赋值时operator=有良好的表现。
- 确保任何一个函数如果操作一个以上的对象,而其中多个对象是同一个对象时,其行为仍然正确。
参考文献
《Effective C++(第三版)》