目录
Never call virtual functions during construction or destruction.
条款10:令 operator=返回一个reference to *this
Have assignment operators return a reference to *this.
Handle assignment to self in operator=.
条款09:绝不在构造和析构过程中调用 virtual函数
Never call virtual functions during construction or destruction.
看下面的代码:
构造BuyTransaction对象时会先调用Transaction的构造函数,而基类构造函数调用了虚函数,这时候不会实现多态,会调用父类的logTransation函数,原因很明显,要实现多态需要派生类对象有虚函数指针成员,而派生类对象还没构造,自然不会调用派生类的logTransation函数。也可以这么理解:在基类构造期间,派生类的类型是基类的类型。
同样的道理也适用于析构函数,一旦进入了基类的析构函数,派生类成员变量早就被析构了,变成为未定义的值,这时其类型就是基类类型。
应该怎么做?
在子类构造函数构造父类成分的时候,传递参数给基类的构造函数。基类构造函数传递参数给non-virtual函数调用:
总结:构造和析构期间不要调用虚函数,因为此时无法实现多态,可以用传递参数的方式调用非虚函数。
条款10:令 operator=返回一个reference to *this
Have assignment operators return a reference to *this.
赋值表达式可以写成连锁形式,比如:x=y=z=15;
这是因为式子会被解析成:x=(y=(z=15)));
对于对象而言也是一样的,注意要返回对象的引用类型,赋值运算符重载返回 * this。
比如:
class Widget{
public:
Widget& operator=(const Widget&w)
{
...
}
};
总结:让类的赋值相关的运算符,返回*this的引用。
条款11:在 operator=中处理“自我赋值”
Handle assignment to self in operator=.
看下面代码:
看似代码没有问题,其实是有安全问题的,因为当Widget对象进行自我赋值的时候,会将自己的pb指向的内容释放掉,然后持有一个指针指向一个已被删除的对象,正确的代码是:
也可以让代码具有异常安全性,就没有自我赋值的问题了,方法就是先拷贝再释放指针:
但注重效率问题的话,还是得进行自我赋值的判断,避免做没必要的拷贝工作。
一种简单的替代方案是使用copy and swap技术,直接看代码:
这种写法还能更简洁,就是将拷贝的工作直接交给参数:
总结:确保当对象自我赋值时不会发生问题,可以使用判断对象地址与this的关系、先拷贝再释放或copy and swap技术来实现。
条款12:复制对象时勿忘其每一个成分.
Copy all parts of an object.
看下面代码:
目前代码还正常,但是当新加入一个成员后:
这意味着需要修改所有构造和拷贝函数,来处理新加入的成员,因为用户自定义了函数,编译器不会帮我们拷贝新加入的成员。
一旦发生继承,可能会导致更严重的问题,比如下面代码:
派生类PriorityCustomer好像拷贝了所有成员,实际上它所继承的Customer成员变量都没有拷贝下来,因为在构造函数初始化列表没有提到过Customer,结果就是PriorityCustomer对象的Customer的成分被Customer的无参的构造函数初始化(即默认构造函数,没有就编译报错)。
正确的做法是让派生类的拷贝函数调用相应的基类构造函数:
另外注意不能让拷贝构造函数调用赋值函数,反之也不行,因为无意义。当发现拷贝构造函数和赋值函数有相近的代码,正确的做法通常是写一个private的init函数给两者调用,避免代码重复。
总结:
拷贝函数确保拷贝了所有的成员变量和所有的基类成分。
不要以一个拷贝函数实现另一个拷贝函数,应当提取公共代码写成第三个函数供它们调用。