C++ 虚函数表:基类和派生类共用一张 vtable 吗?

不完全对。

通常可以理解为:基类和派生类各自有对应的虚函数表,而不是共用基类的一张表。

另外,vtable 是主流编译器实现虚函数的方式,

C++ 标准并没有规定必须使用它。

示例

以常见的单继承为例:

cpp 复制代码
class Base {
public:
    virtual void f() { /* 基类实现 */ }
    virtual void g() { /* 基类实现 */ }
    virtual ~Base() = default;
};

class Derived : public Base {
public:
    void f() override { /* 派生类实现 */ }
};

可以把虚函数表简化理解为下面这样(暂时省略析构函数和其他信息):

虚函数槽位 Base 的虚表 Derived 的虚表
f 指向 Base::f 指向 Derived::f
g 指向 Base::g 指向 Base::g

三个关键点

  • 虚表通常属于类。 同一个类的多个对象通常共享对应的虚表,不会每创建一个对象就复制一张表。
  • 对象中通常有一个隐藏的虚表指针 vptr。 在这个简单例子里,构造完成的 Base 对象指向 Base 的虚表,Derived 对象指向 Derived 的虚表。
  • 派生类没重写的虚函数,表项仍指向基类实现。 这里共享的是函数实现,不意味着两个类共用同一张虚表。

虚调用如何选择实现

cpp 复制代码
Derived d;
Base* p = &d;

p->f();  // 调用 Derived::f
p->g();  // 调用 Base::g

虽然 p 的类型是 Base*,但它指向的是 Derived 对象,虚调用会根据对象对应的虚表选择实现。

编译器如果能确定具体类型,也可能直接调用函数(去虚化),省去查表。

更准确的表述

原来的理解可以改成:

基类声明虚函数后,派生类会继承它的虚函数机制;各个类通常有对应的虚表,派生类的表项根据是否重写,指向派生类或基类的实现。

补充

多重继承时,一个对象可能包含多个 vptr,对象布局会更复杂。




  • 几点说明:

  • "编译器直接调用"补充标注为"去虚化"(devirtualization),方便读者搜索相关资料,不需要可以删掉。

文章修改日志:

核心结论(各类有各自的虚表、vtable 不是标准要求、未重写的槽位指向基类实现)都是对的。有几处表述可以更严谨:

  1. "各个类通常有对应的虚表":严格说只有多态类(有虚函数或虚基类的类)才有虚表,没有虚函数的普通类没有。这里的上下文是有虚函数的继承体系,所以问题不大,但可以加半句限定。

  2. "构造完成的对象指向对应的虚表" :这个说法是对的,而且"构造完成"这个限定很关键。

    构造 Derived 时,先执行 Base 的构造函数,此时 vptr 指向 Base 的虚表,Base 构造完后才改指向 Derived 的虚表。

    所以在基类构造函数里调用虚函数,不会调用到派生类的重写。这是一个常见的坑,很适合作为补充。

  3. 表格只是概念模型 :真实的虚表还包含 RTTI 信息(type_info 指针)、offset-to-top 等内容,析构函数在 Itanium ABI(GCC/Clang)下还有 complete 和 deleting 两个条目。

    文中已经写了"简化理解、省略析构函数和其他信息",所以不算错,但可以明确说一句"实际布局因 ABI 而异"。

  4. "虚调用会根据对象对应的虚表选择实现" :更准确的说法是通过对象里的 vptr 找到虚表,再按槽位取函数地址,由对象的动态类型决定调用哪个实现。

可以在"补充"一节加一段:

补充

  • 只有多态类才会有虚表和 vptr。
  • 构造过程中 vptr 会随构造阶段变化:构造 Derived 时,先执行 Base 的构造函数,此时 vptr 指向 Base 的虚表,因此在基类构造函数中调用虚函数不会分派到派生类的重写。
  • 多重继承时,一个对象可能包含多个 vptr,对象布局会更复杂。
  • 虚表的具体布局(RTTI 指针、析构函数条目等)由各平台 ABI 决定,上面的表格只是便于理解的简化模型。
相关推荐
爱吃提升2 小时前
Dev-C++ 5.11 C++断点调试完整教程
开发语言·c++
夜晚回家2 小时前
Python 原始字符串详解 | r 前缀到底让什么不再转义
开发语言·python
陈年老古董3 小时前
PyQt5 入门指南:环境搭建、PyCharm 外部工具配置与登录界面实战
开发语言·qt·pycharm
看浪的路人3 小时前
第7讲:大模型后端服务化——Go 实现高并发 LLM Proxy / Router / Fallback
开发语言·后端·golang
iCxhust3 小时前
Matlab求解发电机的极限切除角
开发语言·算法·matlab·电力系统
zhanghaha13143 小时前
Qt 类完整例程【逐词拆解 + 逐步骤执行保姆教程】
开发语言·qt
xlq223223 小时前
全面复习7
c++
code2cat4 小时前
Java进阶篇之AtomicReferenceFieldUpdater:用不可变快照替换整份状态
java·开发语言·cas
Nathan__274 小时前
PHP面试题——情景应用
开发语言·php