您的位置:首页 > 编程语言 > C语言/C++

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

2014-06-09 16:28 429 查看
(一)注意自我赋值,因为有“别名”的存在。

实际上两个对象只要来自于同一个继承体系,他们甚至不需要声明为相同类型就可能造成“别名”,因为一个base class的reference或pointer可以指向一个derived class对象(多态)。

class Base {...};
class Derived : public Base {...};
void doSomething(const Base& rb, Derived* pd);

以上这种情况,rb和*pd有可能其实是同一个对象。

(二)

先看下面这段代码(这段代码不仅不具备“自我赋值安全性”也不具备“异常安全性”。):

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

Widget& Widget::operator=(const Widget& rhs) {   //一份不安全的operator=实现版本
delete pb;
pb = new Bitmap(*rhs.pb);
return *this;
}

如果*this跟rhs是同一个对象的话,那么delete pb;就不只是销毁当前对象的bitmap,同时也销毁rhs的bitmap。这样的话,在函数末尾,该对象会发现自己持有的是一个指针指向一个已被删除的对象!

阻止这种错误的传统方法(在赋值函数最前面加一个“证同测试”):

Widget& Widget::operator=(const Widget& rhs) {   //一份安全的operator=实现版本
if(this == &rhs) return *this;
delete pb;
pb = new Bitmap(*rhs.pb);
return *this;
}

但是这种方法只具备“自我赋值安全性”却不具备“异常安全性”。如果"new Bitmap"导致异常(无论是new内存或者copy)Widget最终会持有一个指针指向一块被删除的Bitmap!!!

解决这种错误的方法(在处理了“异常安全性”的同时也处理了“自我赋值安全性”):

我们只需注意在复制pb所指东西之前别删除pb。代码如下:

Widget& Widget::operator=(const Widget& rhs) {   //一份很安全的operator=实现版本
Bitmap* pOrig = pb;
pb = new Bitmap(*rhs.pb);
delete pOrig;
return *this;
}

这样的话,如果Bitmap出现异常,pb以及他所在那个对象都会保持原状!

这样的话,也可以处理自我赋值!当自我赋值的时候,pOrig指向原对象,pb指向原对象的一个副本。删除原bitmap。返回原对象的副本!同样处理了自我赋值。

(三)还有一个很好的方法使得我们的operator=函数不仅具有“自我赋值安全性”并且具有“异常安全性”!这就是copy and swap技术。

class Widget {
...;
void swap(Widget& rhs);    //交换*this和rhs的数据。
...;
};
Widget& Widget::operator=(const Widget& rhs) {
Widget temp(rhs);  //为rhs数据制作一份副本
swap(temp);        //将*this数据和上述复件的数据交换。
return *this;
}

这里的operator=函数是以传递const reference为参数的。还可以以pass by value的方式:

Widget& Widget::operator=(Widget rhs) {
swap(rhs);        //将*this数据和上述复件的数据交换。
return *this;
}

这样的方法虽然减低了可读性,但是它将“copying动作”从函数本体内移至“函数参数构造阶段”却可令编译器有时生成更高效的代码!

请记住:

(1)确保当对象自我赋值时operator=有良好行为。其中技术包括比较“来源对象”和“目标对象”的地址、精心周到的语句顺序、以及copy and swap。

(2)确定任何函数如果操作一个以上的对象,而其中多个对象是同一个对象时,其行为仍然正确。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: