文章目录
我们来看看下列代码:
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)确定任何函数如果操作一个以上的对象,而其中多个对象是同一个对象时,其行为仍然正确。