Effective C++ 条款11:在operator=中处理自我赋值

Effective C++ 条款11:在operator=中处理自我赋值

潜在的自我赋值

// 1
a[i] = a[j];	// i = j

// 2
*px = *py;		// py和px指向同一对象

// 3
class Base{};
class Derived: public Base{};
void doSomething(const Base& rb, const Derived& pd);
// rb和pd可能引用的同一个对象

自我赋值一个bug:如果类里面有动态内存分配,那么在赋值的时候,需要先delete掉原来的,再new一个新的,最后赋值。但如果是自我赋值,那么在delete掉原来的内存的同时,需要赋的值也被delete了(因为都是同一块内存)。

解决这个问题的方法就是在前面加个判断

下面是个例子,假设我们有一个Bitmap类,一个Widget类。其中Widget有一个Bitmap的指针。

class Bitmap{};
class Widget {
public:
    Widget& operator=(const Widget& rhs);
private:
    Bitmap* pb;
};

// 防止自我赋值的
Widget& Widget::operator=(const Widget& rhs){
    if (this == *rhs) return *this;
    delete pb;
    pb = new Bitmap(*rhs.pb);
    return *this
}

除此之外还有个问题。如果赋值的时候,new一块新的空间失败了,那么pb会指向一块被delete掉的空间。这样的指针是有害的。

Widget& Widget::operator=(const Widget& rhs) {
    Bitmap *tmp = pb;
    pb = new Bitmap(*rhs.pb);
    delete tmp;
    return *this;
}

首先上面这个版本申请了一个临时变量保存原始的对象。然后new一个Bitmap并赋值。如果这里出错了,还没到delete,其他的所有东西都保持原样。如果没有出错,则再将原始的空间,通过这个临时变量delete。这就解决了上面的问题。

然后它还取消了自我赋值的检测。但是他依然可以处理自我检测问题,假如两个指针指向同一个对象,它也会先创建一个新的副本,赋值以后再删除原来的版本。

copy and swap技术

这个技术需要保证swap函数是异常安全的,具体后面的条款会解释,这里只做了解。

Widget& Widget::operator=(const Widget& rhs) {
    Widget tmp(rhs);
    swap(tmp);
    return this;
}

最后提一点的是,既然我们要自己创建一个rhs的副本,那为什么不直接以值传参,利用编译器为我们生成一个副本呢。

Widget& Widget::operator=(Widget rhs) {	// not reference
    swap(rhs);
    return this;
}

当然这写法不被提倡,因为降低清晰性,让系统变得复杂。

posted @ 2021-07-01 20:41  Destiny233  阅读(85)  评论(0)    收藏  举报