【C++】自我赋值问题
首先通过一个例子引出自我赋值可能会导致的问题,例子如下:
1 class Bitmap {...}; 2 class Widget { 3 public: 4 ... 5 Widget& operator=(const Widget& rhs); 6 ... 7 private: 8 Bitmap* pb; 9 }; 10 Widget& Widget::operator=(const Widget& rhs) //一份不安全的operator=实现版本 11 { 12 delete pb; 13 pb = new Bitmap(*rhs.pb); 14 return *this; 15 }
由于Widget类包含动态分配对象的指针,因而需要自定义拷贝构造函数和赋值符函数.
在上述operator=函数中,如果*this(赋值的目的端)和rhs是同一个对象,将会导致*this对象里的pb指针指向一个已被删除的对象.
通过在operator=中增加一个证同测试,可以阻止这种错误.代码实现如下:
1 Widget& Widget::operator=(const Widget& rhs) 2 { 3 if(this == &rhs) //证同测试 4 return *this; 5 delete pb; 6 pb = new Bitmap(*rhs.pb); 7 return *this; 8 }
虽然增加证同测试后,可以达到自我赋值的安全性,但不具备异常安全性.
如果"new Bitmap"导致异常,Widget最终会持有一个指针指向一块被删除的Bitmap.
这样的指针有害,我们无法安全地删除它们,甚至无法安全地读取它们.
通过合理安排语句,可以实现异常安全的代码.代码实现如下:
1 Widget& Widget::operator=(const Widget& rhs) 2 { 3 Bitmap* pOrig = pb; 4 pb = new Bitmap(*rhs.pb); 5 delete pOrig; 6 return *this; 7 }
如果"new Bitmap"抛出异常,pb会保持原状.
即使没有证测试,上述代码还是能够处理自我赋值.
此外,还有一种方法copy and swap技术可以实现operator=函数的异常安全和自我赋值安全.代码实现如下:
1 Widget& Widget::operator=(const Widget& rhs) 2 { 3 Widget temp(rhs); //为rhs数据制作一份复件 4 swap(temp); //将*this数据和上述复件的数据交换 5 return *this; 6 }
上述代码中,第3行代码创建临时变量temp,作为rhs数据的一份复件.
由于swap函数会交换*this和参数对象的数据,如果直接将rhs作为参数,则会改变rhs对象的值,与operator=的操作不符.
我们可以将operator=函数声明为"以by value方式接受实参",这样可减少临时变量temp的创建.
将"copying动作"从函数本体移至"函数参数构造阶段".代码如下:
1 Widget& Widget::operator=(Widget rhs) //pass by value,rhs是被传对象的一份复件 2 { 3 swap(this); //将*this的数据和复件的数据互换 4 return *this; 5 }