前两天帮同事 review 一段 C++ 代码,继承体系里子类想“重写”父类的某个虚函数,结果测试时发现怎么调都是进父类版本。代码编译通过、运行不报错,甚至连 -Wall 都没给出像样的提示,纯粹是行为不符合预期。我扫了一眼函数声明就明白了——参数类型不一致,int 被写成了 double。这个场景特别典型:函数重载、函数签名、虚函数表(vtable)三个机制在同一个类层级里相遇时,任何一个环节的认知有偏差,都会出现这种“编译期风平浪静、运行期南辕北辙”的问题。
这篇文章想做的,是把虚函数表(vtable)、函数重载、函数签名这三件事放在同一张桌上讲清楚。它们分别负责什么、如何协作、又会在哪里互相干扰。无论你是刚接触 C++ 的初学者,还是在面试前想梳理多态底层的候选人,希望这篇能帮你把零散的知识点收拢成一个整体框架,而不是再靠背八股应付。
1. 一段“静默失效”代码引出的三角关系:重载、覆盖与虚表
1.1 问题代码复现
先看这段代码,我建议你自己跑一下,结果可能和直觉有出入:
cpp复制#include <iostream>
class Base {
public:
virtual void fun(int x) {
std::cout << "Base::fun(int) -> " << x << std::endl;
}
};
class Derived : public Base {
public:
// 注意:参数类型是 double,不是 int
void fun(double x) {
std::cout << "Derived::fun(double) -> " << x << std::endl;
}
};
int main() {
Derived d;
Base& ref = d;
ref.fun(10); // Base::fun(int) -> 10
d.fun(10); // Derived::fun(double) -> 10
return 0;
}
很多人第一眼会以为 ref.fun(10) 走动态绑定,ref 的实际类型是 `Derived
