effective C++ 笔记:条款11 在operator=中处理“自我赋值”

直接上代码~

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;
}

总结:确保对象如果出现自我赋值时不会有不良的行为,并且确定任何函数如果操作一个以上的对象,而且这些对象是同一个对象时,也不会出现不良的行为。

©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容

  • 2 构造/析构/赋值运算(续) 条款09:绝不在构造和析构过程中调用 virtual 函数 如果执行BuyTran...
    暗夜望月阅读 374评论 0 0
  • 再读高效c++,颇有收获,现将高效c++中的经典分享如下,希望对你有所帮助。 1、尽量以const \enum\i...
    橙小汁阅读 1,258评论 0 1
  • Swift1> Swift和OC的区别1.1> Swift没有地址/指针的概念1.2> 泛型1.3> 类型严谨 对...
    cosWriter阅读 11,165评论 1 32
  • 春明子阅读 256评论 0 1
  • 2018年7月30日 星期一 天气晴 亲子日记第83篇 昨天抱着小宝坐了半天车赶回东营,又接着参加老姥爷寿...
    最爱奇毅阅读 103评论 0 0