C++多态通过虚表(vtable)和虚表指针(vptr)实现:每个含虚函数的类有唯一vtable,对象内存首部存vptr指向所属类vtable;父类指针调用虚函数时,根据实际对象的vptr动态查表跳转,而非静态类型;非虚函数编译期绑定,不进vtable;对象切片会丢失vptr,故多态仅适用于指针或引用。
当用父类指针指向子类对象并调用虚函数时,C++不是靠类型声明决定调用哪个函数,而是靠对象实际内存中携带的虚表指针(vptr)在运行时动态查找——这就是多态的核心机制。
编译器为每个含虚函数的类生成一张虚函数表,表中按声明顺序存放该类所有虚函数的地址。
子类继承父类虚表后,会用自己的虚函数地址覆盖父类对应位置(若重写了该函数)。每个对象在内存头部隐式存储一个指向其所属类虚表的指针(vptr),构造对象时由构造函数自动初始化。
写 Base* p = new Derived(); p->func(); 时,编译器生成的指令不是直接跳转,而是三步查表:
整个过程不依赖 p 的静态类型 Base*,只依赖 p 实际指向的对象内存布局——所以哪怕 p 是 Base*,只要它指向的是 Derived 对象,就一定调用 Derived::func()。
非虚函数没有进入虚表,编译器在编译期就根据指针/引用的静态类型决定调用目标,生成直接 call 指令。例如 Base* p = new Derived(); p->non_virtual(); 会无条件调用 Base::non_virtual(),哪怕 Derived 里也定义了同名函数且参数一致——这叫隐藏(hiding),不是重写(overriding)。
多态只对指针和引用有效。如果写 Base b = Derived();,会发生对象切片——只复制 Base 部分,Derived 特有成员和 vptr 全部丢弃,b 的 vptr 指向 Base 的虚表,后续调用全是 Base 版本。这也是为什么多态接口必须用指针或引用传递,不能传值。
基本上就这些。理解 vptr 和 vtable 的存在与协作方式,比死记“父类指针可以调子类函数”更有价值——它让你看清 C++ 多态不是语法糖,而是有明确内存布局和运行时开销的确定机制。