60 秒回答模板

C++ 虚函数用于实现运行时多态。基类函数声明为 virtual 后,通过基类指针或引用调用该函数时,实际调用哪个实现由对象的动态类型决定。常见实现是对象里有隐藏的 vptr 指向所属类型的虚函数表 vtable,表中存放虚函数入口地址,调用时先取 vptr 再查表跳转。它的成本是对象多一个 vptr、调用多一次间接跳转并可能影响内联。使用时要注意基类指针删除派生对象时析构函数应为 virtual,构造和析构期间虚调用不会分派到尚未构造或已经析构的派生部分。

考点 核心机制与工程取舍
难度 中高频面试题
回答目标 按定义、机制、场景讲清楚

深入解析

01

解决运行时多态

虚函数让同一个基类接口在运行时根据真实对象类型调用不同实现。只有通过基类指针或引用调用 virtual 函数时,动态绑定才有意义;普通对象直接调用或非虚函数通常是静态绑定。

02

vptr 和 vtable

主流编译器通常在含虚函数的对象中放一个隐藏 vptr,指向该类的 vtable。vtable 是类级别结构,保存各虚函数最终实现的地址。派生类重写虚函数时,对应表项指向派生类实现。

03

调用过程

执行 `base->foo()` 时,程序先从对象中取 vptr,再在虚表中找到 foo 对应槽位,最后间接跳转到函数地址。这个过程让编译期只知道基类类型的代码,运行时仍能调用派生实现。

04

虚析构很重要

如果用基类指针 delete 派生对象,基类析构函数不是 virtual 时,只会按静态类型析构,派生类资源可能泄漏。作为多态基类时,析构函数通常应声明为 virtual。

05

构造析构阶段有边界

构造基类部分时,派生类部分尚未构造完成;析构基类部分时,派生类部分已经析构。因此构造和析构函数内调用虚函数,不会按完整派生对象那样分派,容易产生误解和 bug。

易错点

  • 把函数重载当成虚函数多态,忽略必须通过基类指针或引用触发动态绑定。
  • 只会说 virtual 关键字,不知道 vptr、vtable 和间接调用路径。
  • 多态基类析构函数不写 virtual,导致 delete 基类指针时派生资源泄漏。
  • 在构造或析构函数里依赖虚函数分派到派生类实现。

面试官追问

虚函数有什么开销?

通常对象会多一个 vptr,虚调用多一次间接寻址和跳转,且编译器更难内联。现代编译器在能证明动态类型时可能去虚化,但不能把它当成总是免费的机制。

为什么基类析构函数要 virtual?

如果类会被当作多态基类使用,外部可能通过基类指针删除派生对象。虚析构能先调用派生析构,再调用基类析构,保证资源完整释放。

重载、重写和隐藏有什么区别?

重载是同一作用域内函数名相同参数不同;重写是派生类以兼容签名覆盖基类虚函数;隐藏是派生类声明同名函数遮蔽基类函数,可能并没有形成虚函数重写。

构造函数能是虚函数吗?

构造函数不能是虚函数。对象的动态类型需要在构造过程中逐步建立,虚表指针也随构造阶段调整,构造还没完成时不能按最终派生类型虚分派。