发布于2026-07-24 阅读(0)
扫一扫,手机访问
谈到C++虚函数表的初始化,很多人觉得玄乎,其实拆开看就两个阶段:一个是编译/链接期干的事,一个是运行期干的事。分工非常明确,搞清楚了,多态那点底子也就透了。

当你写一个带虚函数的类,比如 class A { virtual void f(); },然后编译它——编译器会做什么?它会为每个包含虚函数的类(或者从虚函数类继承下来的子类)生成一份固定的 vtable 数据结构。说白了,就是一个函数指针数组,里面填好了各个虚函数的地址,比如 A::f 的地址。
这份 vtable 会被编译到程序的只读数据段(.rodata)里,跟代码段一样,属于程序启动之前就已经确定好的“静态数据”。也就是说,类的虚函数表本身不是运行时捏出来的,而是编译期就焊死在二进制里的。
每个包含虚函数的对象,内部都藏着一个隐藏的 vptr 成员,它的作用就是指向所属类的那个 vtable。这个指针什么时候初始化?
vptr 在构造函数执行的最开始阶段完成初始化——注意,早于构造函数的函数体。也就是说,构造函数体还没跑,vptr 已经指向了正确的 vtable。子类构造时,会先初始化父类的 vptr,再切换到子类自己的 vptr。vptr 在程序启动后的全局初始化阶段(main 函数执行前)完成。vptr 在 operator new 分配内存之后、构造函数执行之前初始化。一句话总结:vtable 是静态的,vptr 才是动态的,真正实现多态靠的是指针的切换。
#includeusing namespace std; class Base { public: Base() { // 此时 vptr 已经指向 Base 的 vtable,调用虚函数会走 Base 版本 func(); // 输出 "Base::func" } virtual void func() { cout << "Base::func" << endl; } }; class Derived : public Base { public: Derived() { // 此时 vptr 已经切换到 Derived 的 vtable func(); // 输出 "Derived::func" } void func() override { cout << "Derived::func" << endl; } }; int main() { Derived d; // 构造过程: // 1. 初始化 Base 部分 → vptr 指向 Base::vtable → 调用 Base::func // 2. 初始化 Derived 部分 → vptr 切换到 Derived::vtable → 调用 Derived::func return 0; }
输出结果:
Base::func
Derived::func
代码跑出来很清楚:vptr 在构造函数执行时已经就位,而且子类构造时会覆盖父类的 vptr,所以同一个对象在不同构造阶段调用虚函数,走的是不同的版本。
vptr 都指向同一张表,不会重复创建。vptr 初始化是在构造函数的“初始化列表阶段”(编译器自动插入的),早于函数体。所以构造函数体里调虚函数,已经能正确分发。vptr 指向它,表本身不动。vptr。
售后无忧
立即购买>office旗舰店
售后无忧
立即购买>office旗舰店
售后无忧
立即购买>office旗舰店
售后无忧
立即购买>office旗舰店
正版软件
正版软件
正版软件
正版软件
正版软件
1
2
3
7
8