直接上代码~
class Widget{...};
Widget w;
w = w;
上述代码中有一个自我赋值的操作,这种自我赋值非常明显,但是有些自我赋值就不一定那么明显了,比如
a[i] = a[j]; // i 和 j 值相等
*px = *py; // px py 指向相同
class base{...};
class derived : public base{...};
void doSomething(const base& rb, derived* pd); //实际上,rb 和pd可能指向同一对象
那我们接着来看一下对象的自我赋值可能会发生什么事情。
下面是一个类内的operator=实现代码
class Bitmap{...};
class Widget{
...
private:
Bitmap* pb;
};
Widget& Widget::operator=(const Widget& rhs){
delete pb;
pb = new Bitmap(*rhs.pb);
return *this;
}
此时我们假设传入的rhs和=左边的(也就是*this)是同一个东西,那么在new pb的时候,其实rhs.pb其实已经被delete!,那么此时return的 *this,其实里面的pb指向的是一个已经被删除的Bitmap对象。
对于这种情况,有一种简便方法就是在这段代码前面加一个证同测试(identity test):
Widget& Widget::operator=(const Widget& rhs){
if(this == &rhs)
return *this;
delete pb;
pb = new Bitmap(*rhs.pb);
return *this;
}
这样的代码看似没有问题,但是我们还少考虑了“异常状况”,比如
pb = new Bitmap(*rhs.pb);
在这段分配空间的操作中如果导致了异常(内存不足或者Bitmap的构造函数抛出异常),这时候返回的*this,其中的pb仍然是指向一个已经被删除的Bitmap。
鉴于此有了新的改进代码
Widget& Widget::operator=(const Widget& rhs){
Bitmap* tmp = pb;
pb = new Bitmap(*rhs.pb);
delete tmp;
return *this;
}
这样,在new操作之前就没有delete操作,就不会产生指向已删除对象的指针,此时就算报出异常,pb还是原来的值,并不会那么地危险。而且就算传入的rhs和*this是同一个东西,这段代码就相当于复制了一个和原来一样的Bitmap给pb。
以上思想还有两种写法:
Widget& Widget::operator=(const Widget& rhs){
Widget tmp(rhs);
swap(tmp);
return *this;
}
Widget& Widget::operator=(const Widget rhs){ //这里不是引用 是值传递
swap(tmp);
return *this;
}
总结:确保对象如果出现自我赋值时不会有不良的行为,并且确定任何函数如果操作一个以上的对象,而且这些对象是同一个对象时,也不会出现不良的行为。
网友评论