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 决定,上面的表格只是便于理解的简化模型。
相关推荐
by209991 小时前
C++ 智能指针的所有权与使用
开发语言·c++·经验分享
砚底藏山河1 小时前
python量化自定义周期 K 线合成
java·开发语言·数据库·python·金融
IT召唤狮2 小时前
开源企业级零信任项目Pangolin,零信任能挡住PHP核弹漏洞吗?
开发语言·开源·php
知已难寻2 小时前
03 · OpenStack的计算管理 Nova
运维·服务器·开发语言·云原生·云计算·openstack·开源软件
Rover Ramble2 小时前
C++ 用互斥锁实现读写锁
开发语言·c++
你怎么知道我是队长2 小时前
C++零基础入门第2周:数据类型、运算符与表达式完全指南(附20段可运行代码+避坑清单)
c++
say_fall2 小时前
【Linux系统编程】Ext 文件系统:磁盘与存储、文件与目录、现代计算机究竟如何寻找文件
linux·运维·服务器·数据库·c++
码流子3 小时前
移动收费怎么做?手持终端+App+收费中心的架构实践
java·开发语言·架构
雪落漂泊3 小时前
C++算法 滑动窗口篇(下)
开发语言·c++
云创智城-yuncitys3 小时前
YunParking 4.0:JDK 17 与 Spring Boot 3.2 的停车云升级
java·开发语言·spring boot