条款11:在operator=中处理"自我赋值"——83

文章目录


我们来看看下列代码:

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

这里自我赋值问题是,operator=函数内的* this(赋值的目的端)和rhs有可能是同一个对象。果真如此delete就不只是销毁当前对象的bitmap,它也销毁rhs的bitmap。
解决方案一 欲阻止这种错误,传统做法是藉由operator=最前面的一个“证同测试”达到“自我赋值”的检验目的:

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

这种做法似乎无懈可击,但仍然存在异常方面的麻烦。更明确地说,如果“new Bitmap”导致异常,Widget最终会持有一个指针指向一块被删除的Bitmap。这样的指针有害。你无法安全地删除它们,甚至无法安全地读取它们。
解决方案二 现在的做法焦点放在实现“异常安全性”上。条款29深度探讨了异常安全性,本条款只要你注意“许多时候一群精心安排的语句就可以到处异常安全(以及自我赋值安全)的代码”。例如如下代码,我们只需注意在复制pb所指东西之前别删除pb:

Widget&
Widget::operator=(const Widget& rhs){
    Bitmap* pOrig=pb; //记住原先的pb
    pb=new Bitmap(*rhs.pb); //令pb指向*pb的一个复件(副本)
    delete pOrig; //删除原先的pb
    return *this;
}

现在,如果“new Bitmap”抛出异常,pb保持原状。它或许不是处理“自我赋值”的最高效办法,但它行得通。
如果你很关心效率,可以把“证同测试”再次放回函数起始处。但这仍然会引入额外成本。
解决方案三 在operator=函数内手工排列语句的一个替代方案是,使用所谓的copy and swap技术。如下:

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

解决方案四 这个主题的另一个变奏曲乃利用一下事实:
(1)某class的copy assignment操作符可能被声明为“以by value方式接受实参”;
(2)以by value方式传递东西会造成一份复件/副本。

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

上面这个做法,为了伶俐巧妙的修补而牺牲了清晰性。然而将“copying动作”从函数本体内移至“函数参数构造阶段”却可令编译器有时生成更高效的代码。

总结——87

(1)确保当对象自我赋值时operator=有良好行为。其中技术包括比较“来源对象”和“目标对象”的地址、精心周到的语句顺序、以及copy-and-swap。
(2)确定任何函数如果操作一个以上的对象,而其中多个对象是同一个对象时,其行为仍然正确。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值