虚函数必须在基类中用virtual显式声明,否则不触发多态;多态仅通过指针或引用实现,对象传值或赋值会导致切片;子类重写须加override防误定义,析构函数需virtual防内存泄漏。

虚函数必须在基类中用 virtual 显式声明
不加 virtual,哪怕子类写了同名函数,调用时也不会走多态——编译器只看指针/引用的静态类型。这是最常踩的坑:以为“名字一样就自动多态”,结果 Base* p = new Derived(); p->func(); 调的还是 Base::func()。
实操建议:
-
virtual只需写在基类声明里,子类重写时加不加都行(c++11 起推荐加override) - 纯虚函数写成
virtual void func() = 0;,含纯虚函数的类就是抽象类,不能实例化 - 析构函数如果可能通过基类指针删除派生对象,必须声明为
virtual,否则派生部分内存泄漏
运行时多态只对指针和引用生效
直接用对象变量调用,比如 Base b = derived_obj;(发生切片),再调 b.func(),永远绑定到 Base::func()。多态不是靠函数名,而是靠“间接访问”机制——只有通过指针或引用,才能触发虚函数表查找。
常见错误现象:
立即学习“C++免费学习笔记(深入)”;
- 把派生类对象赋值给基类对象(非指针/引用),以为能多态,实际发生对象切片,派生成员全丢
- 函数参数传值(
void f(Base b)),进来就切片,内部调b.func()不会多态 - 正确做法是传
Base&或Base*
override 和 final 是 C++11 后必备的安全补丁
没加 override 时,子类函数签名稍有偏差(比如参数 const 性不同、返回类型协变没满足),就会变成新函数而非重写,虚函数表里根本没它——运行时静默调错函数,极难排查。
实操建议:
- 只要意图重写虚函数,就在子类声明后无条件加
override,编译器立刻报错提示签名不匹配 - 确定某个虚函数不再被进一步重写,就在基类或中间类里用
final(如virtual void f() final;),防止意外继承破坏契约 -
override和final都是标识符,不是关键字,但只在虚函数重写上下文有效
虚函数调用有轻微开销,但别过早优化
每次调用要查虚函数表(vtable),多一次间接寻址,比普通函数调用慢一点。但在绝大多数业务逻辑里,这点开销远小于 IO、内存分配或算法复杂度本身。
需要注意的点:
- 高频内层循环里反复调用虚函数(比如每帧上万次的图形渲染钩子),才值得考虑用模板或策略模式规避
- 开启编译器优化(
-O2)后,部分虚调用可能被 devirtualize(如编译期能确定唯一类型) - 真正影响性能的往往是虚函数背后做的事(比如锁、拷贝、动态分配),而不是虚调用本身
虚函数表布局、RTTI、多重继承下的虚函数行为,这些细节一旦涉及,问题会立刻从“语法是否写对”滑向“内存模型是否理解”。初学阶段盯住 virtual、override、指针/引用这三点,已经能覆盖 95% 的多态使用场景。