C++中的多态是如何分类的?(函数重载的静态多态与虚函数的动态多态)

13次阅读

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

C++中的多态是如何分类的?(函数重载的静态多态与虚函数的动态多态)

静态多态靠编译期绑定,本质是函数名相同但签名不同

函数重载(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 或静默调用错误函数。

text=ZqhQzanResources