最近开始看《Effective C++》,为了方便以后回顾,特意做了笔记。若本人对书中的知识点理解有误的话,望请指正!!!
“自我赋值”的意思是自己给自己赋值,如:
class Widget{...};
Widget w;
w = w; //自己给自己赋值
这看起来很蠢,但它合法,所以不要认为它不会发生,此外,赋值动作并不会被你一眼看出来(如w = w;
),例如:
Widget arr[10] = {...};
arr[i] = arr[j]; //潜在的自我赋值,arr[i]和arr[j]存储同一个对象或 i == j
Widget w;
Widget* px = &w;
Widget* py = &w;
*px = *py; //潜在的自我赋值,指针px和py指向同一个对象
这些不明显的自我赋值是由别名造成的,别名就是有一个以上的方法指称某对象。若两个对象处来自同一个继承体系,它们不需要声明为相同类型就可能造成别名,因为基类的引用和指针可以指向一个派生类对象。
class Base {...};
class Derived:public Base {...};
void doSomething(const Base& rb,Derived* pd);//rb和*pd有可能其实是同一对象
如果你打算自行管理资源,可能会掉进“在停止使用资源前意外释放了它”的陷阱。假设你建立一个class用来保存一个指向一块动态分配的位图的指针:
class Bitmap{...}
class Widget{
...
private:
Bitmap *bp;
};
Widget& Widget::operator=(const Widget& rhs){
delete pb; //释放当前的bitmap
pb = new Bitmap(*rhs.bp); //使用位图rhs拷贝新的一份位图
return *this; //返回自身引用
}
这里自我赋值的问题是 operator=
函数内的 *this
(赋值的目的端)和 rhs
有可能是同一个对象。若真的是同一个对象,则 delete
就不只是释放了当前对象的位图,也释放了 rhs
对象的位图。函数最后的结果是当前对象的位图被释放了,原本期待的结果是不会发生任何变化。
解决方法:
-
检查传入的参数是不是 *this
Widget& Widget::operator=(const Widget& rhs){ // 先做一个身份校验 if(this == &rhs) return *this; // 如果不是自己,再执行如下操作 delete pb; pb = new Bitmap(*rhs.pb); return *this; }
这个方法解决了自身赋值的问题,但它不是异常安全的。如果
new Bitmap
导致异常(不论是因为分配时内存不足或是因为Bitmap
的拷贝构造函数抛出异常),Widget
最终会有一个指向一块被删除的Bitmap
的指针。 -
复制位图前别删除
Widget& Widget::operator=(const Widget& rhs){ Bitmap* pOrig = pb; //暂存原先的pb pb = new Bitmap(*rhs.pb); //令pb指向*rhs.pb的一个拷贝 delete pOrig; //删除原先的pb return *this; }
如果
new Bitmap
抛出异常,pb
(及其栖身的哪个Widget
)保持原状。即使没有身份校验,它也能够处理自我赋值,因为我们队原位图做了一份拷贝,然后删除原位图,最后指向拷贝的那份位图。 -
先拷贝再交换(copy and swap)
class Widget{ ... private: Bitmap *bp; void swap(Widget& rhs); //把*this的数据和rhs的数据互换 }; void Widget::swap(Widget& rhs){ *bp = rhs.bp; } //引用传递方式 Widget& Widget::operator=(const Widget& rhs){ Widget temp(rhs); //为rhs数据拷贝一份副本,防止自我赋值 swap(temp); //把*this的数据和rhs的数据互换 return *this; } //值传递方式:参数rhs已经是一个副本 Widget& Widget::operator=(Widget rhs){ swap(temp); //把*this的数据和rhs的数据互换 return *this; }
Note:
- 处理好operator=函数中的自我赋值问题,其中的方法有“检查传入的参数是不是 *this”、“复制位图前别删除”和“先拷贝再交换”
- 同一个函数中使用多个参数,也需要保证函数再这些参数同时指向同一个对象时可以正常工作