一、虚函数和构造函数
当创建一个含有虚函数的对像是,必须初始化它的VPTR以指向相应的VTABLE,这必须在对虚函数进行任何调用之前完成,而设置VPTR这项工作是由构造函数来完成。编译器在构造函数的开头部分秘密地插入能初始化VPTR的代码,如果我们没有为一个类显式创建构造函数,则编译器会为我们生成构造函数。如果该类含有虚函数,则生成的构造函数将会包含相应的VPTR初始化代码。
所有基类构造函数总是在继承类构造函数中被调用,这个是有意义的,因为构造函数有一项专门的工作:确保对象被正确地建立。派生类只访问它自己的成员,而不访问基类的成员,只有基类构造函数能正确地初始化它自己的成员。因此,确保所有的构造函数被调用是很关键的,否则整个对象不会使当地被构造,这就是为什么编译器强制为派生类的每个部分调用构造函数的原因,如果不在构造函数初始化列表中显示地调用基类构造函数,它就调用默认构造函数,如果没有默认构造函数,编译器将报告出错。
二、构造函数不能为虚函数
当继承时,必须知道基类的全部成员并能访问基类的任何Public和Protected成员。这意味着,当在派生类中时,必须能肯定基类的所有成员都是有效的。在构造函数中,为了保证对象的所有成员已经建立,唯一的方法就是让基类构造函数首先被调用。这样,当在派生类构造函数中,在基类中能访问的所有成员都已经被初始化,这也是下面做法的原因:只要可能,我们应当在这个构造函数初始化表达式所有的成员对象(即对象通过组合被置于类中)。
为什么构造函数不能是虚函数?因为对于在构造函数中调用一个虚函数,被调用的知识这个函数的本地版本,也就是说,虚机制在构造函数中不工作。
这种行为有以下两种理由:
第一: 概念上来说,在任何构造函数中,我们是先让基类构造函数首先被调用,然后调用成员对象构造函数。然而,虚函数可以调用在派生类中函数,如果我们的构造函数也这么做,那么我们所调用的函数可能操作还没有被初始化的成员,这将导致灾难的产生。
第二:从机制上来说,当一个构造函数被调用时,它做的首要的事情就是初始化它的VPTR。然而,他只能知道它属于“当前类”——即构造函数所在的类。所以,当编译器为这个构造函数产生代码时,它是为这个类的构造函数产生代码——既不是为基类,也不是委它的派生类,所以它使用的VPTR必须是对于这个类的VTABLE。因此,只要它不是最后的构造函数调用,那么在这个对象的生命期,VPTR将保持被初始化为指向这个VTABLE,依此类推,直到最后的构造函数结束。VPTR的状态是由被最后的构造函数确定的,这也就是为什么构造函数调用是按照从基类到最后派生的类的顺序的另一个理由。
但是,当这一系列构造函数调用正发生时,每个构造函数都已经设置VPTR指向它自己的VTABLE,如果函数使用虚机制,它将只产生通过它自己的VTABLE的调用,而不是最后派生的VTABLE(所有构造函数被调用后才会有最后派生的VTABLE)。
综合以上两点得出: 构造函数是不能为虚函数的。
三、析构函数能够且常常必须是虚的
与构造函数调用的顺序完全相反,析构函数从最晚派生的类开始,并向上到基类。这是安全且合理的:当期的析构函数一直知道基类成员任然是有效的。如果需要在析构函数调用某一基类的成员函数,进行这样的操作是安全的。因此,析构函数能够对其自身进行清除,然后调用下一个析构函数,该析构函数又将执行它的清除工作,依次类推。
应当记住,构造函数和析构函数是类层次进行调用的唯一地方(因此,编译器自动地生成适当的类层次)。在所有的其它函数中,只有这个函数会被调用(非基类版本),而无论是虚的还是非虚的。同一个函数的基类版本在普通函数中北调用(无论虚函数还是非虚函数)的唯一方法就是显式地调用这个函数。
如果将析构函数设置为非虚函数,那么我们通过指向基类的指针操纵派生类对象的析构函数时,编译器只能知道调用这个析构函数的基类版本,派生类自己的析构函数部分不会被调用,这样会引入“内存泄露”。
因此,析构函数可以且常常必须是虚函数。
四、纯虚析构函数
经过纯虚析构函数在标准C++中是合法的,但是使用时有个额外的限制:必须为纯虚析构函数提供一个函数体。这可能听起来有点违反常规,因为通常的纯虚函数没有函数体的,但是因为在一个类层次中总是会调用所有的析构函数,如果不对一个纯虚析构函数进行定义,在析构期间将不知道该调用什么函数体。因此,编译器和链接程序强迫纯虚析构函数一定要有一个函数体,这是十分必要的。
然而,当从某个含有纯虚析构函数的类中继承出一个类,就跟其它的纯虚函数有所不同,我们不要求在派生类中提供纯虚函数的定义。此外,通常如果在派生类中基类的纯虚函数没有重新定义,则派生类将会成为抽象类,但是,对于纯析构函数而言,并不是这样。
// Pure virtual destructors ,seem to behave strangely
class AbstractBase {
public:
virtual~AbstractBase() = 0;
};
AbstractBase::~AbstractBase() {}
class Derived : public AbstractBase {};
// No overriding of destructor necessary?
int main() { Derived d; }///:~