静态多态靠编译期绑定,本质是函数重载;动态多态需虚函数、继承和指针 / 引用调用三要素,通过 vtable 实现运行时分发,override/final 可避免误用。

静态多态靠编译期绑定,本质是函数名相同但签名不同
函数重载(overload)不是真正意义上的“多态”语义,而是编译器根据实参类型在编译时选择具体函数版本。它不涉及继承或运行时决策,只是名字空间内多个同名函数的共存。
常见错误现象:void foo(int) 和 void foo(double) 在调用 foo(5) 时选 int 版本,但若只声明了 foo(double),foo(5) 会 隐式转换 并调用它——这容易掩盖预期缺失的重载。
- 重载解析发生在编译期,与对象动态类型无关
- 参数个数、类型、const 限定符都会影响重载决议
- 基类和派生类中的重载不构成覆盖关系;派生类中新增重载不会自动继承基类重载,需用
using Base::func;显式引入
动态多态必须有虚函数、继承和指针 / 引用调用三要素
只有当三个条件同时满足时,virtual 才能触发运行时多态:基类声明虚函数 、 派生类重写(override)该函数 、 通过基类指针或引用调用。缺一不可。
典型错误:用值传递对象(如 void call(Base b)),导致对象被切片(slicing),虚函数表丢失,退化为静态绑定。
立即学习“C++ 免费学习笔记(深入)”;
-
virtual关键字必须出现在基类函数声明中,派生类重写时可省略(但建议加上override) - 析构函数若可能被多态删除(如
delete ptr),必须声明为virtual - 构造函数不能是虚函数;静态成员函数也不能是虚函数
虚函数表(vtable)是实现动态多态的底层机制
每个含虚函数的类(或其子类)在编译后都有一个对应的虚函数表,表中按声明顺序存放函数指针。对象实例头部隐式存储一个指向 vtable 的指针(vptr)。调用虚函数时,实际执行的是 obj->vptr[索引]()。
性能影响:虚函数调用比普通函数多一次内存读取(查 vtable)和一次间接跳转,现代 CPU 分支预测通常能缓解,但高频小函数仍可能成为瓶颈。
- 纯虚函数(
= 0)使类变为抽象类,vtable 中对应项为 nullptr,强制派生类实现 - 虚函数内联几乎不可能,编译器无法在编译期确定目标地址
- 多重继承下 vptr 可能不止一个,布局更复杂,但标准未规定细节,依赖具体 ABI
override 和 final 是 C++11 后避免多态误用的关键 工具
没加 override 的派生类函数,即使签名看似匹配,也可能因 const / 引用限定符或参数类型细微差异(如 int vs int&)而变成新重载,而非重写——此时虚调用仍走基类实现,bug 隐蔽。
class Base {public: virtual void func(int x) const; }; class Derived : public Base {public: void func(int x) override; // ✅ 正确:签名完全匹配 void func(int& x) override; // ❌ 编译错误:不能重写,因为参数类型不同 };
-
override让编译器检查是否真正在重写虚函数,否则报错 -
final可用于类(禁止继承)或虚函数(禁止进一步重写),防止意外覆盖 - 虚函数默认不继承:派生类若不重写,调用时仍沿用基类实现(包括基类的虚函数体)
虚函数的动态分发依赖于对象内存布局的一致性,一旦涉及内存操作(如 memcpy、序列化反序列化)、跨 DLL 边界或手动管理 vptr,就极易破坏多态行为——这些地方没有编译器保护,出错时往往表现为随机 crash 或静默调用错误函数。