读书笔记中涉及到的所有代码实例可通过https://github.com/LuanZheng/EffectiveCPlusPlus.git进行下载获得。
Item9 绝不在构造和析构函数中调用virtual函数
Base class构造函数的执行更早于derived class构造函数,因此,base class构造期间virtual函数绝不会下降到derived class阶层。或者说,在base class构造期间,virtual函数不是virtual函数。
相同道理也适用于析构函数。一旦derived class析构函数开始执行,对象内的derived class成员变量便呈现未定义值,所以C++视它们仿佛不再存在。
若在构造函数或析构函数中需要使用类似virtual带来的多态的便利性的话,一种做法是在base class将原来的虚函数(构造函数将调用的那个)改为非虚函数,然后要求派生类构造函数传递必要信息给基类构造函数,而后那个构造函数便可安全的调用此非虚函数。
小插曲:建立工程文件时,开始命名为Item8,之后修改为Item9之后,需要手动在Item9.vcxproj里面设置一下。然后在IDE里面移除工程,再重新加载。
例子见Item9
Item10 令operator=返回一个reference to *this
为了实现“连锁赋值”(x=y=z=15),赋值操作符必须返回一个reference指向操作符左侧实参。
#ifndef _COMPLEX_H_
#define _COMPLEX_H_
class Complex
{
public:
Complex();
Complex(const double real, const double image);
Complex& operator=(const Complex& rhs);
Complex& operator+=(const Complex& rhs);
void print();
private:
double m_RealPart;
double m_ImagePart;
};
#endif // !_COMPLEX_H_
#include "Complex.h"
int main()
{
Complex c1(1.7, 1.6);
c1.print();
Complex c2(0.9, 1.2);
c2.print();
Complex c3;
c3.print();
c3 += c2 += c1;
c1.print();
c2.print();
c3.print();
c1 = c2 = c3;
c1.print();
c2.print();
c3.print();
return 0;
}
例子见Item10
条款11 在operator=中处理“自我赋值”
在operator=中出现自我赋值时,需要进行先保存旧内存,分配新内存,copy,然后删除旧内存的顺序。因为如果先删除内存,一旦=两边指的是同一地址,则在删除内存时,就全部删除了。后面再赋值时,传入的数据已经变成了空指针。
#ifndef _WIDGET_H_
#define _WIDGET_H_
#include "Bitmap.h"
class Widget
{
public:
Widget();
~Widget();
Widget& operator=(const Widget& rhs);
private:
Bitmap *pb;
};
#endif // !_WIDGET_H_
#include "Widget.h"
Widget::Widget()
{
pb = new Bitmap();
}
Widget::~Widget()
{
if (0 != pb)
{
delete pb;
pb = 0;
}
}
Widget& Widget::operator=(const Widget& rhs)
{
//if (0 != pb)
//{
// delete pb; //这段代码存在问题,若rhs和this指的是同一个对象,则在delete pb时,同时也delete了rhs
// pb = 0; //后面copy构造函数运行就会报错。因为rhs.pb为空指针
//}
Bitmap* pOrig = pb; //修正后的代码,先保存一下this->pb,然后分配新内存,最后删除掉原内存
pb = new Bitmap(*rhs.pb); //运算符优先级,先运算.然后是*,相当于(*(rhs.pb))
//相当于利用Bitmap的copy构造函数,利用rhs.pb副本,使得this->pb指向这个副本
if (0 != pOrig)
{
delete pOrig;
pOrig = 0;
}
return *this;
}
#include "Widget.h"
int main()
{
Widget w1;
w1 = w1; //实际上在内存中做了一次交换,新分配内存空间存放W1,之后删除原来的W1
return 0;
}
例子见Item11
Item12: 复制对象时勿忘其每一个成分
如果你为class添加一个成员变量,你必须同时修改copying函数。(你也需要修改class的所有构造函数以及任何非标准形式的operator=)
任何时候只要你承担起“为派生类撰写拷贝构造函数”的重责大任,必须很小心的也复制其基类成分。那些成分往往是private,所以你无法直接访问它们,你应该让派生类的拷贝构造函数调用相应的基类函数。
注意:虽然派生类中无法访问基类的private成员,但基类成员变量的内存还是被派生类持有的,也就是说,当为基类传递一个派生类对象时,在基类中,就可以使用这个成员变量。
如果你发现你的拷贝构造函数和拷贝复制操作符有相近的代码,消除重复代码的做法是,建立一个新的成员函数给两者调用。这样的函数通常为private的,且通常被命名为init.
#include "Derived.h"
Derived::Derived()
{
}
void Derived::setY(int y)
{
m_Y = y;
}
int Derived::getY()
{
return m_Y;
}
Derived::Derived(const Derived& derived) : Base(derived), m_Y(derived.m_Y)
{
}
例子见Item11