当前位置: 首页 > backend >正文

c++虚表的调用


前言

虚表(Virtual Table,简称 vtable)是 C++ 实现运行时多态(动态绑定)的核心机制。下面详细解释虚表的调用过程:

虚表的基本概念

  1. 虚表是什么:每个包含虚函数的类都有一个虚表,它是一个函数指针数组,存储该类所有虚函数的地址。

  2. 虚指针:每个包含虚函数的类的对象都有一个隐藏的虚指针(vptr),指向该类的虚表。

虚表调用过程

当通过基类指针或引用调用虚函数时,会发生以下步骤:

  1. 获取虚指针:通过对象中的 vptr 找到虚表

  2. 查找函数地址:在虚表中找到对应的虚函数地址

  3. 调用函数:通过函数指针调用实际的函数

class Animal {
public:virtual void speak() = 0;virtual ~Animal() {}
};class Dog : public Animal {
public:void speak() override { cout << "Woof!" << endl; }
};class Cat : public Animal {
public:void speak() override { cout << "Meow!" << endl; }virtual void purr() { cout << "Purr..." << endl; }
};void makeSound(Animal* animal) {animal->speak(); // 动态绑定
}
class Base {
public:virtual void f1() {}virtual void f2() {}int x;
};class Derived : public Base {
public:void f1() override {}virtual void f3() {}int y;
};

虚表结构示例

对于上面的类,虚表结构大致如下:

Derived 对象:
+---------------+  
| vptr          | → Derived 的虚表
+---------------+
| Base::x       |
+---------------+
| Derived::y    |
+---------------+Derived 的虚表:
+---------------+
| Derived::f1() | // 覆盖基类
+---------------+
| Base::f2()    | // 继承基类
+---------------+
| Derived::f3() | // 新增虚函数
+---------------+

1. 虚表的构建规则

  • 继承的虚函数:派生类会继承基类虚表中的所有条目

  • 重写的虚函数:覆盖对应槽位的函数指针

  • 新增的虚函数:追加到虚表末尾

2.虚析构函数的必要性

class Base {
public:~Base() {} // 非虚析构函数
};class Derived : public Base {
public:std::vector<int> data;~Derived() {}
};Base* p = new Derived();
delete p; // 未定义行为!Derived 的析构函数不会被调用

正确做法:基类析构函数应该声明为 virtual。

为什么构造函数不可以是虚函数

构造顺序问题

  • 构造函数的主要职责是初始化对象的内存建立对象的类型信息

  • 在构造函数执行之前,对象的内存空间刚刚分配,还没有形成有效的对象

  • 虚函数调用依赖于虚表指针(vptr),而vptr本身需要在构造函数中初始化

  1. 对象内存分配后,vptr初始化为0或未定义状态

  2. 进入构造函数时,编译器首先初始化vptr指向当前类的虚表

  3. 然后才执行构造函数体内的用户代码

  4. 在构造函数的成员初始化列表中,vptr已经指向当前类的虚表


总结

虚表(vtable)是编译器为每个包含虚函数的类生成的静态函数指针数组,存储该类所有虚函数的地址。每个对象通过内部的虚指针(vptr)指向其所属类的虚表。虚表在编译期生成,存放在程序的只读数据段,构造函数不能声明为虚函数,因为在对象构造期间vptr尚未完全初始化。而析构函数通常需要声明为虚函数,以确保通过基类指针删除派生类对象时能正确调用完整的析构链,避免资源泄漏。

http://www.xdnf.cn/news/13927.html

相关文章:

  • CSS 基础选择器、文字控制属性
  • 蓝牙协议GAP(Generic Access Profile)
  • 报表工具顶尖对决系列 — Echarts 展现与导出
  • CC工具箱使用指南:【断线连接】
  • Oracle OCP认证考试考点详解083系列18
  • 如何在SOLIDWORKS工程图中添加材料明细表?
  • 关于联咏(Novatek )自动曝光中Lv值的计算方式实现猜想
  • win11系统安装jdk11教程
  • day22
  • Python 实现一个带进度条的 URL 批量下载工具(含 GUI 界面)
  • 第三节 类型系统进阶-接口(interface)与类型别名(type)的区别
  • 算法导论第三章:数据结构艺术与高效实现
  • 【Mysql及各种关系型数据库全面对比与深度解析(2025版)】
  • 第六章网络互联设备
  • 前端八股文 - CSS 篇
  • GPU-CPU-FPGA三维异构计算统一内存架构实践:基于OpenCL的跨设备Kernel动态迁移方案(附内存一致性协议设计)
  • 深入剖析 Spring @Bean 注解:灵活定义与掌控你的 Bean
  • 权限管理设计思路
  • 土地利用遥感解译 ➕ CLUE未来预测 | 技术流分享!
  • STM32 Bootloader:使用文件头加载并启动应用程序
  • 2.监控领域中行业黑话知识学习指南
  • 基于深度学习的智能文本生成:从模型到应用
  • Vue3 Router 使用指南:从基础到高级用法
  • NVIDIA Container Toolkit 报错 Failed to initialize NVML: Unknown Error 的解决
  • Java8新特性 consumer
  • 访客预约到访填写表单如何制作?
  • C语言二级指针和void *应用
  • 质因数分解_java
  • 前端开发冷知识-requestIdleCallback优化主线程任务调度的API
  • 微服务集成seata分布式事务 at模式快速验证