GV

GeezerVison

The World Wide Web, restored to 800 pixels of glory.

Home|About|Gaming cabinet|Archives|How it works|Starter sites

On this site

  • 卡码笔记-最强八股文
  • 计算机基础
  • Python
  • C++
  • Java
  • Go
  • ChatGPT充值
  • Claude充值
  • 国内使用Claude
  • 中转站推荐
  • 🔥大模型🔥
  • 大模型面经
  • Java面经
  • C++面经

This view

  • Original page
  • New address

GeezerVison is showing notes.kamacoder.com — C++多态实现机制:编译时多态与运行时多态、虚函数表、动态绑定、override与final

# C++多态的实现机制

面试官问"C++多态是怎么实现的",很多人只答"虚函数+继承"就停了。但面试官真正想听的是底层怎么走的——vtable 在哪、vptr 什么时候初始化、调用链是怎样的。只说表面概念,面试官一定会追问到你答不上来。

把编译时多态和运行时多态分清楚,再把运行时多态的调用链讲明白,这道题就稳了。

# 简要回答

C++ 多态分两种:编译时多态靠函数重载和模板,编译期就确定调用谁;运行时多态靠 virtual 函数 + 继承 + 基类指针/引用,运行时通过 vptr → vtable → 函数地址 动态绑定到正确的实现。

# 详细回答

维度 编译时多态 运行时多态
实现手段 函数重载、模板 虚函数 + 继承
绑定时机 编译期(静态绑定) 运行期(动态绑定)
关键机制 编译器根据参数类型/模板参数选择 vptr → vtable → 函数地址
触发条件 直接调用即可 必须通过基类指针或引用调用
性能 无额外开销,可内联 多一次间接寻址,无法内联
典型场景 sort 的比较函数、max<T> 基类接口统一调度派生类行为

运行时多态的三个必要条件:基类声明 virtual 函数、派生类重写(override)该函数、通过基类指针或引用调用。三者缺一不可——如果用派生类对象直接调用,编译器会静态绑定,不走 vtable。

虚函数调用的底层流程(以 Animal* p = new Dog(); p->speak(); 为例):

  1. 编译器为每个含虚函数的类生成一张 vtable,表中按声明顺序存放虚函数地址。Dog 的 vtable 中 speak 槽位存的是 Dog::speak 的地址
  2. 每个对象的内存布局最前面藏一个 vptr,构造时指向本类的 vtable
  3. 调用 p->speak() 时:通过 p 找到 Dog 对象 → 取 vptr → 在 vtable 中查 speak 的偏移 → 跳转执行 Dog::speak()

这就是为什么基类析构函数必须声明为 virtual——否则 delete p 时只调基类析构,派生类资源泄漏。

C++多态虚函数调用流程

# 知识拓展

Q:抽象类和纯虚函数是什么关系?

含有纯虚函数(virtual void run() = 0;)的类就是抽象类,不能实例化,只能被继承。派生类必须实现所有纯虚函数才能实例化,相当于强制定义接口规范。

Q:override 和 final 分别干什么?

override 让编译器检查你是否真的重写了基类虚函数,写错函数签名会直接报错,防止"以为重写了其实没有"的 bug。final 禁止派生类继续重写某个虚函数,或者禁止某个类被继承。

Q:虚函数调用比普通函数慢多少?

多一次指针间接寻址(取 vptr + 查 vtable),在现代 CPU 上通常只差几纳秒。真正的性能损失不在寻址本身,而在于虚函数调用无法被内联优化。热路径上如果频繁调用小虚函数,可以考虑 CRTP(静态多态)替代。

Q:构造函数里能调虚函数吗?

能调,但不会多态。构造函数执行时 vptr 指向的是当前正在构造的类的 vtable,不是最终派生类的。所以在基类构造函数里调虚函数,调的是基类版本,不是派生类版本。

← C++的重载和重写,以及它们的区别和实现方式 C++中的虚函数和纯虚函数有什么区别? →

评论

验证登录状态...

You are visitor 01402503 — GeezerVison does not store the pages you look up.
Best viewed with Netscape Navigator 4.7 or Internet Explorer 6 at 800×600. No cookies required.