0%

C++构造函数不能声明为虚函数

引言:构造函数不能声明为虚函数,析构函数可以声明为虚函数,而且有时是必须声明为虚函数。

1.背景

C++中的虚函数的作用主要是实现了多态的机制。基类定义虚函数,子类可以重写该函数,当子类重新定义了父类的虚函数后,当父类的指针指向子类对象的地址时,父类指针根据赋给它的不同子类指针,动态的调用子类的该函数,而不是父类的函数,且这样的函数调用发生在运行阶段,而不是发生在编译阶段,称为动态联编。而函数的重载可以认为是多态,只不过是静态的。如果使用了virtual关键字,程序将根据引用或指针指向的对象类型来选择方法,否则使用引用类型或指针类型来选择方法。我们知道,在类的继承中,如果有基类指针指向派生类,那么用基类指针delete时,如果不定义成虚函数,派生类中派生的那部分无法析构。所以,基类的析构函数一定要定义成虚函数,那么构造函数为什么不能定义成虚函数呢?

2.虚函数的底层实现机制

要想搞清楚这个问题,首先要弄明白虚函数的底层是如何实现的。

编译器处理虚函数的方法是:首先为每个类对象添加一个隐藏成员,隐藏成员中保存了一个指向函数地址数组的指针。称为虚表指针(vptr),这种数组称为虚函数表,即每个类使用一个虚函数表,每个类对象有一个虚表指针。

举个例子:基类对象包含一个虚表指针,指向基类所有虚函数的地址表。派生类对象也将包含一个虚表指针,指向派生类虚函数表,看下面两种情况:

  • 如果派生类重写了基类的虚方法,该派生类虚函数表将保存重写的虚函数地址,而不是基类的虚函数地址。
  • 如果基类中的虚方法没有在派生类中重写,那么派生类将继承基类中的虚方法,而且派生类中的虚函数表将保存基类中未被重写的虚函数地址。注意,如果派生类中定义了新的虚方法,则该虚函数的地址也将被添加到派生类虚函数表中。

下面的图片体现了上述底层实现机制:

虚函数的底层实现.jpg

3.原因

搞明白虚函数的底层实现机制之后,这个问题就迎刃而解了。虚函数的执行依赖于虚表指针查找虚函数表的操作,而对象的虚表指针在构造函数中进行初始化,让它指向正确的虚函数地址,而在构造对象期间,虚表指针还没有被初始化,所以将无法执行查表操作。

虚函数的意思就是开启动态绑定,程序会根据对象的动态类型来选择要调用的方法。然而在构造函数运行的时候,这个对象的动态类型还不完整,没有办法确定它到底是什么类型,故构造函数不能动态绑定。