1.普通的operator=函数的实现
给定以下的operator=的实现代码,我们可以对operator=重载的代码进行分析:
class Bitmap {
};
class Widget {
public:
Widget & operator=(const Widget& cw) {
delete pb; //删除当前对象
pb = new Bitmap(*cw.pb); //使用cw的元素构建新的Bitmap对象
return *this; //返回指向当前对象的引用
}
private
Bitmap * pb;
};
上述代码有没有问题呢?
在operator=函数中,当参数cw引用指向的对象和函数内的*this是同一个对象时,delete操作不仅销毁了当前对象的bitmap,也销毁了参数cw的bitmap,函数结尾,this指针就指向了一个已被删除的对象。
2.加入“自赋值检测”的operator=函数
为了避免因为自赋值导致的上述的状况,可以在赋值操作前进行自赋值检测,代码如下:
Widget & operator=(const Widget& cw) {
if (&cw != this) {
delete pb; //删除当前对象的bitmap指针
pb = new Bitmap(*cw.pb); //使用cw的元素构建新的Bitmap对象
}
return *this; //返回指向当前对象的引用
}
进行“自赋值”检测的代码避免了上述因自赋值带来的安全问题,但还是存在“异常安全问题”。
具体的“异常安全问题”体现在:在执行new Bitmap时,可能因为内存不够或调用Bitmap拷贝构造函数抛出异常。因此,Widget最终会持有一个指针指向一块被删除的Bitmap,这样的指针会带来太多的安全隐患:你无法安全删除它,甚至都读取不到它。剩下的唯一能做的事就是不断调试找到问题的根源。
3.加入“异常安全”的operator=函数
为了解决上述的异常安全问题,我们可保证在赋值pb所指东西前不要删除pb,具体可进行下面的操作:先将pb用变量保存,在pb被成功赋值后,再删除pb:
Widget & operator=(const Widget& cw) {
Bitmap* oriBp = pb; //使用变量oriBp事先保存pb
pb = new Bitmap(*cw.pb); //在pb被成功赋值后
delete oriBp; //再删除pb
return *this; //返回指向当前对象的引用
}
在这个解决方案中,如果“new Bitmap”操作中抛出异常,pb保持原状。
而且,这段代码也能处理自我赋值,因为对bitmap做了一份副本、删除原bitmap、然后指向新构造的那个副本。
4. “Copy and Swap”技术
为了提高上述既解决了“自我赋值”,又解决了“异常安全”问题的代码的效率,常用的“copy and Swap”技术就派上用场了,具体的实现代码如下:
class Bitmap {
};
class Widget {
public:
void swap(Widget& cw);
Widget & operator=(const Widget& cw) {
Widget temp(cw);
swap(temp); //将*this数据与上述复件的数据交换
return *this; //返回指向当前对象的引用
}
private:
Bitmap* pb;
};
copy-and-swap技术和“异常安全性”有着密切联系,在《effective C++ Item 29》中对“异常安全性”做了详细解说。
《剑指offer》第二版的面试题一“赋值运算符函数”对于String类的赋值运算符重载的实现就是利用了“copy-and-swap”技术。