三:构造、析构、赋值运算二
条款九:绝不在构造和析构过程中调用虚函数
对于有继承体系的类来说,在派生类对象的基类构造期间,对象的类型是基类而不是派生类,这样的话,基类构造函数中若调用了虚函数,那么调用的虚函数一定是基类的,因为此时类型是基类啊,
到这里就gg了,因为你想要的是调用派生类的虚函数,这个行为不是你想要的
小结:
在构造和析构期间不要调用虚函数,因为这类调用从不下降至派生类
条款十:令operator返回一个指向自己的引用
很简单,为了实现连续赋值
int x, y, z;
x=y=z=15; //都是15
条款十一:在operator=中处理自我赋值
我先来写一份代码处理自我赋值,你看看对不:
//准备工作
class Bitmap{...};
class Widget{
pulic:
Widget& operator=(const Widget& rhs);
private:
Bitmap* pb;
};
//具体实现:
Widget& Widget::operator=(const Widget& rhs){
if(this == &rhs) return *this; //证同测试,比较是不是同一地址
delete pb;
pb = new Bitmap(*rhs.pb);
return *this;
}
这里的问题是:如果new失败了,pb就指向被删除的内存了,更好的代码如下,我们只要对上述代码做一点点改动即可,而且可以丢掉证同测试:
Widget& Widget::operator=(const Widget& rhs){
Bitmap* pOrig = pb;
pb = new Bitmap(*rhs.pb);
delete pOrig;
return *this;
}
丢弃看起来美好的证同测试是为了效率;
与前面代码的区别是new成功了再通过副本pOrig来delete;如果new抛出异常,那么pb还是指向原对象,delete不会被执行,所以不用担心原对象被删除。
还有一种通用的方式叫copy and swap技术,我们可以拷贝一份rhs,再把拷贝的rhs与this所指对象交换,这样也就排除了自我赋值的情况:
Widget& Widget::operator=(const Widget& rhs){
Widget temp(rhs);
swap(temp);
return *this;
}
//或者,通过值传递,本身形参rhs就是实参的副本
Widget& Widget::operator=(Widget rhs){
swap(rhs);
return *this;
}
小结:
- 确保自我赋值有良好的行为。技术包括证同测试,先new成功再通过副本delete、以及copy and swap
条款十二:复制对象时勿忘其每一个成分
我们先来看一下傲娇的编译器:如果你自己实现了拷贝函数(拷贝构造函数和拷贝赋值运算符),编译器在你出错时不会告诉你。
- 你新增了一个成员变量,如果你没有在拷贝函数中去拷贝它,编译器应该报错,它却不报错,所以你得自己小心
- 为派生类写拷贝函数要自己调用基类的拷贝函数,编译器不会像构造析构那样帮你调基类函数
小结
拷贝函数应该确保复制对象内所有成员变量以及所有基类成分