Effective C++(11) 自我赋值(a=a)时会发生什么?

问题聚焦:
自我赋值看似有点愚蠢的行为,其实总会发生的
首先:它是合法的,
其次,它不一定是安全的,
再次,它有时候不是那么明显。

先看一个Demo

class Widget { ... };
Widget w;
...
 /** 最明显的自我赋值 **/
w = w;   

/** 不那么明显的自我赋值 **/
// 在某个地方实现了i = j或者相同作用的事情
a[i] = a[j]

/** 潜在的自我赋值 **/
*px = *py;

/** 更为隐蔽的自我赋值“别名” **/
class Base { ... };
class Derived : public Base { ... };
void doSomething( const Base& rb, Derived* pd);     // rb 和 *pd可能是同一对象


一般而言,如果某段代码操作指针和引用,而它们被用来指向多个相同类型的对象,就需要考虑这些对象是否为同一个了。

自我赋值可能带来的两种潜在的危险:
  • 自我赋值安全性
  • 异常安全
来看下面的例子:

class Bitmap { ... };
class Widget {
    ... 
private:
    Bitmap* pd;
};

/** operator=的代码实现 **/
Widget&
Widget::operator=(const Widget& rhs)
{
    delete pb;
    pb = new Bitmap(*rhs.pb);
    return *this;
}

自我赋值安全性:
若rhs和当前对象是同一对象,那么在销毁当前对象的pb时,把rhs的对象也销毁了。

改进:认同测试


/** operator=的代码实现 **/
Widget&
Widget::operator=(const Widget& rhs)
{
    if (this == &rhs) return *this;   // 认同测试

    delete pb;
    pb = new Bitmap(*rhs.pb);
    return *this;
}


上述改进后的代码避免了自我赋值安全性问题。
我们来关注一下第二个问题
异常安全性:
如果new Bitmap()发生异常,那么结果就是pb赋值直白,Widget最终持有一个指针指向一块被删除的Bitmap。

改进一:精心安排语句


/** operator=的代码实现 **/
Widget&
Widget::operator=(const Widget& rhs)
{
    Bitmap* pOrig  = pb;
    pb = new Bitmap(*rhs.pb);     // 如果new Bitmap抛出异常,pb保持原状
    delete pOrig;
    return *this;
}


改进方案二:copy and swap


class Widget {
...
void swap(Widget& rhs);
...
};

Widget& Widget::operator=(const Wdiget& rhs)
{
    Widget temp(rhs);
    swap(temp);
    return *this;
}

思想:
拷贝构造函数要声明为“按值传递”
按值传递会造成一份副本


小结:
确保当对象自我赋值时operator=有良好的行为。
包括:
对象来源
目标对象地址
语句顺序
确定任何函数如果操作一个以上的对象,而其中多个对象是同一个对象时,其行为仍然正确。



参考资料:
《Effective C++ 3rd》

 

posted @ 2013-12-30 20:07  suzhou  阅读(347)  评论(0编辑  收藏  举报