一般我们学的八股

构造函数可以是虚函数吗?

这个问题已经算的上周所周知、耳熟能详。答案是构造函数不能是虚函数。了解过的小伙伴应该也知道原因,虚函数是C++多态的基础,我们在一个类中声明了虚函数后,可以在子类中对它进行重载,重载后就能够继承父类的特性同时实现子类的新功能。构造函数用来完成类对象的初始化操作,并且会初始化虚函数的虚表,这个虚表里面填了虚指针最终指向这个虚函数的具体实现,也就是倘若没有构造函数,虚表就建立不起来,虚函数也没法初始化,那如果构造函数是虚函数就出问题了,形成一种类似“被锁住”的形态,虚函数等构造函数构造,构造函数被虚函数困住也要等构造。

那么现在来说今天的主题……

虚函数可以被构造函数调用吗?
先给大家分个类:

【构造函数是外部的非派生类中的构造函数】

这个就属于普通的类外访问,只要虚函数的访问权限是public就可以正常被类外访问。但是注意访问之前需要建立虚函数所在类的对象,利用对象访问非静态成员函数;如果是protected权限,只能被子类、友元和自己访问;如果是private权限就不能访问。

权限 谁可以访问
public 所有其他类都可以访问
protected 只有本类、友元、子类可以访问
private 只有本类和友元可以访问

【构造函数是外部的派生类中的构造函数】

如果是派生类,构造函数包含基类的虚函数大家应该也能想到是可以这么做的,毕竟基类已经先初始化完成了,虚函数所需要的虚表似乎已经建立。答案的确是可以的,不过是不是大家所想的那样这里先不阐述。

【构造函数是当前虚函数类内的构造函数】

而对于本类的构造函数,如果包含了本类的虚函数,那么还能正常的构造出来并调用吗?你可能会想,构造函数中调用了虚函数,构造函数调用虚函数的时候,虚函数是否已经先初始化完成了?
在构造函数或析构函数中可以调用虚函数,不过这不会产生多态行为,即不会调用派生类的重写版本,而是调用当前构造阶段所属类中的版本

总结答案:

如果构造函数是外部的非派生类中的构造函数,属于普通的类外访问,要看虚函数的访问权限;如果构造函数是外部的派生类中的构造函数或是当前虚函数类内的构造函数,可以调用虚函数。

🔍 原因解析

现在不得不说一个问题虚函数表(vtable)是什么时候产生的
🔹1. 编译期就生成虚函数表的结构
在编译阶段,编译器会根据类中声明的 virtual 函数,为每个含虚函数的类生成一个虚函数表(vtable)的结构。这个表中记录了该类的虚函数地址(指针),包括继承情况下的覆盖重定向。
👉 所以:vtable 是编译器生成的静态数据结构,存在于程序的常量区(只读存储区)或数据区中。
🔹2. 对象构造时设置虚表指针(vptr)
虽然虚表(vtable)在编译时生成好了,但每个对象内部都有一个“隐藏的指针”叫 vptr(虚表指针):这个 vptr 会在对象构造阶段被自动初始化,指向对应类的 vtable。vptr 是由编译器在构造函数的机器码里插入的代码负责设置的。

现在深挖一下构造函数:
C++ 中,对象的构造是分阶段进行的。如果有继承关系,构造顺序是:基类构造函数 → 成员对象构造 → 派生类构造函数
在构造过程中:
· 虚函数机制依赖于虚表(vtable)
· 在基类构造函数中,vptr(虚函数表指针)指向的是基类的 vtable
· 直到派生类的构造函数运行,才会把 vptr 设置为派生类的 vtable
所以:
在构造函数中调用虚函数时,vptr 还没指向最终派生类的虚表,所以会调用当前构造阶段类的虚函数实现。

综上

构造函数调用的时候虚表其实已经生成,只不过里面的指针还没有确定,如果在构造函数里面调用了虚函数,不会影响什么,这时候指针指向的还是本类,不能指向外部派生类,也就是前面说的不会产生多态行为

下面是具体进行测试的代码,可以运行一下看。

#include<bits/stdc++.h>
using namespace std;

class A{
public:
    A(){
        B();
    }
    virtual void B(){
        cout << "虚函数能被构造函数调用吗?" <<endl;
    };
};

class A_son:public A{
public:
    A_son(){
        B();
    }
};

class C{
public:
    C(){
        A a;
        a.B();
    }
};

int main(){
    A aa;
    A_son a_son;
    C c;
    return 0;
}