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

【C++】虚表和虚基表到底有哪些区别?

虚表和虚基表

  • 虚表
  • 虚基表
  • 虚拟继承和虚函数都存在时的对象模型

虚表

我们知道,如果类中声明了的方法是用virtual进行修饰的,则说明当前这个方法要作为虚函数,而虚函数的存储和普通函数的存储是有区别的
当有虚函数声明时,编译器会创建一个虚函数表,将当前的虚函数按照声明次序放入虚函数表中,而这个虚函数表实际上就是一个函数指针数组,然后将当前这个虚函数表的地址放入对象模型的最起始位置。

class A
{
public:virtual void fun1(){cout << "A::fun1()" << endl;}virtual void fun2(){cout << "A::fun2()" << endl;}virtual void fun3(){cout << "A::fun3()" << endl;}int _a;
};

它对应的对象模型是这样的:
在这里插入图片描述

所以说,本质上虚函数表是一个函数指针数组,而对象模型中存放的是虚函数表的首地址,当我们需要调用虚函数时,传递对应的对象,就可以通过对象的地址获取对象的虚表指针,从而获取虚表,进而得到对应虚函数表中某个虚函数的地址,以此来进行调用(知道函数的入口地址,就可以调用对应的函数)

虚基表

我们知道,当出现菱形继承时,一定会出现对象模型中有多个基类对象成员。

//普通继承
class A
{
public:int _a;
};
class B : public A
{
public:int _b;
};
class C : public A
{
public:int _c;
};
class D : public B, public C
{
public:int _d;
};

上述代码中,D类对象中一定会存在B类和C类对象继承自A类对象的_a这个成员,这样就出现了两份_a成员,导致访问_a时出现二义性,并且随着继承深度和广度的增加,对象成员会越来越冗余。
为了解决这个问题,出现了虚拟继承。

//菱形虚拟继承
class A
{
public:int _a;
};
class B : virtual public A
{
public:int _b;
};
class C : virtual public A
{
public:int _c;
};
class D : public B, public C
{
public:int _d;
};

通过让B类和C类虚拟继承A类后,对象模型就从图1变成了图2
在这里插入图片描述

在这里插入图片描述

这样的转变,使得B类和C类虽然继承了A类,但是B类和C类中并没有存储A类的对象(基类对象只有一份,被存放在了整个对象模型的最后),除了子类新增之外,只有一个指针,这个指针就被称为虚基表指针。

虚基表指针所指向的是一个虚基表,对于B类ptr1这个虚基表,总大小为8个字节(32位系统下),前4个字节存储的是子类对象相对于自己起始位置的偏移量,(目前来看是0,当存在虚函数的虚拟继承时,就不是0了),后4个字节存储是子类对象相对于基类部分的偏移量。
在这里插入图片描述
ptr2指向C类这个对象的虚基表,总大小为8个字节(32位系统下),前4个字节存储的是子类对象相对于自己起始位置的偏移量,(目前来看是0,当存在虚函数的虚拟继承时,就不是0了),后4个字节存储是子类对象相对于基类部分的偏移量。
在这里插入图片描述

可以发现,虚表在整个类对象中只存储一份,也就是说一个类的不同对象共享同一份虚表。而虚基表有多份,取决于当前类是否虚拟继承了基类,若虚拟继承了基类,就会创建一个虚基表指针,指向一个虚基表。

虚拟继承和虚函数都存在时的对象模型

那么就存在另外一个问题,当虚拟继承和虚函数同时出现在继承体系中,对象模型又是什么样子呢?

class A
{
public:virtual void fun1(){cout << "A::fun1()" << endl;}virtual void fun2(){cout << "A::fun2()" << endl;}int _a;
}
class B : virtual public A
{
public:virtual void fun1(){cout << "B::fun1()" << endl;}virtual void fun3(){cout << "B::fun3()" << endl;}int _b;
};
class C : virtual public A
{
public:virtual void fun2(){cout << "C::fun2()" << endl;}virtual void fun4(){cout << "C::fun4()" << endl;}int _c;
};
class D : public B, public C
{
public:virtual void fun1(){cout << "D::fun1()" << endl;}virtual void fun2(){cout << "D::fun2()" << endl;}virtual void fun5(){cout << "D::fun5()" << endl;}int _d;
}

上述代码中,B类和C类都继承自A类,并且对A类中的虚函数进行了重写,同时也新增了虚函数。D类继承了B和C类,对B和C类中的虚函数进行了重写,同时也新增了虚函数。

那么当前在这个继承体系下,对象模型是什么样子呢?
其实不难想到,由于B类和C类都是虚拟继承,那么A类成员只会保留一份在最下方,同时B类和C类都会保存自己的虚基表指针,而D类由于是普通继承,按照顺序,新增的虚函数被放到B类的虚表中。
在这里插入图片描述

我们通过取地址发现,对象模型确实是上述的样子,但是在D类和A类之间,放了00000000作为对象分割区分(猜测)
在这里插入图片描述

请注意:当前的验证情况是在vs2019中进行验证的。

总结:当虚基表和虚表同时存在(虚拟继承和虚函数同时存在时),对象模型从整体上来说还是和虚拟继承相同(基类对象顺序按照声明的顺序从上到下排列,对象中没有祖父类的成员,祖父类成员被放到了模型的最下方)。但是由于有虚函数的存在,B类对A类的虚函数进行重写的虚函数在A类中直接修改,B类新增的虚函数被放到B类内部的虚表中,C类对A类的虚函数进行重写的虚函数在A类中直接修改,C类新增的虚函数被放到C类内部的虚表中。D类对B类和C类进行重写的虚函数直接在对应类中进行修改,D类新增的就直接放到B类的虚表中。

通过上述的描述,可以知道对于B类,C类和A类的虚表中存放的虚函数分别为:

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

而对于虚基表来说,表示的是子类对象相对于自己起始位置的偏移量,如果是B类,B类对象的起始位置已经有了一个虚表指针,那么虚基表中前四个字节要表示相对自己起始位置的偏移量就需要为-4,而后四个字节是相对于基类的偏移量是正常的计算方式。

对于B类C类的虚基表来说,其中的值为:
在这里插入图片描述
在这里插入图片描述

相关文章:

【C++】虚表和虚基表到底有哪些区别?

虚表和虚基表 虚表虚基表虚拟继承和虚函数都存在时的对象模型 虚表 我们知道&#xff0c;如果类中声明了的方法是用virtual进行修饰的&#xff0c;则说明当前这个方法要作为虚函数&#xff0c;而虚函数的存储和普通函数的存储是有区别的 当有虚函数声明时&#xff0c;编译器会…...

剑指 Offer 04. 二维数组中的查找解题思路

文章目录 标题解题思路优化 标题 在一个 n * m 的二维数组中&#xff0c;每一行都按照从左到右 非递减 的顺序排序&#xff0c;每一列都按照从上到下 非递减 的顺序排序。请完成一个高效的函数&#xff0c;输入这样的一个二维数组和一个整数&#xff0c;判断数组中是否含有该整…...

冯诺依曼体系结构详解

一.冯诺伊曼体系结构的概念&#xff1a; 约翰冯诺依曼&#xff08;John von Neumann&#xff0c;1903.1.28-1957.2.8&#xff09;&#xff0c;美籍匈牙利数学家&#xff0c;计算机科学家&#xff0c;物理学家。是20世纪最重要的数学家之一&#xff0c;后来被称为计算机之父。 后…...

ISO证书“带标”与“不带标”的区别是什么?

ISO9001质量管理体系认证是企业产品获得“通行绿卡”的最直接最有效的途径。 通过认证在打破贸易壁垒&#xff0c;提高产品知名度&#xff0c;降低生产成本&#xff0c;提高经济效益&#xff0c;维护消费者权益&#xff0c;减少重复审核负担等方面的作用越来越为企业界所共知。…...

RocketMQ 领域模型概述

本文为您介绍 Apache RocketMQ 的领域模型。 Apache RocketMQ 是一款典型的分布式架构下的中间件产品&#xff0c;使用异步通信方式和发布订阅的消息传输模型。通信方式和传输模型的具体说明&#xff0c;请参见下文通信方式介绍和消息传输模型介绍。 Apache RocketMQ 产品具备…...

黄河千年清一回与人类健康

黄河千年清一回奏响一曲曲让人类走进幸福新时代的壮丽凯歌。疫情之后的首届全世界健康产业发展大会 5 月28 日上午 9 时在中国首都北京召开 The Yellow River has played a magnificent song of triumph in the millennium, ushering humanity into a new era of happiness. T…...

Android java层hook------xposed框架的使用

xposed曾经是android平台上最好的java层hook和调试工具&#xff0c;由于已经不再更新&#xff0c;当前支持的android系统版本比较老旧&#xff0c;目前只能支持到android6.0&#xff0c;故已经逐渐落伍&#xff0c;目前android上最广泛使用的hook工具是frida&#xff0c;这是另…...

css奇淫巧计

1.input文本内容替换 -webkit-text-security&#xff1a;通过用形状替换字符,仅影响那些字段不是的typepassword 可选值:none &#xff08;无&#xff09;&#xff0c;circle &#xff08;圆圈&#xff09;&#xff0c;disc &#xff08;圆形&#xff09;&#xff0c;square &a…...

Web服务器实现|基于阻塞队列线程池的Http服务器|线程控制|Http协议

基于阻塞队列生产者消费者模型线程池的多线程Web服务器 代码地址&#xff1a;WebServer_GitHub_Addr README 摘要 本实验通过C语言&#xff0c;实现了一个基于阻塞队列线程池的多线程Web服务器。该服务器支持通过http协议发送报文&#xff0c;跨主机抓取服务器上特定资源。与…...

【C++】运算符重载(日期类的实现)

【C】运算符重载&#xff08;日期类的实现&#xff09; 前言运算符重载operator全局和类中 日期类的实现成员变量的确定构造函数拷贝构造 运算符重载部分的重载思路实现GETmonthdayoperator 的重载思路实现 -的与-的重载实现 各个比较运算符的重载实现 前置与后置实现 &#xf…...

【Linux】线程分离 | 线程库 | C++调用线程 | 线程局部存储

文章目录 1. 线程分离1. 为什么要线程分离&#xff1f;2. 具体使用3. 为什么有时候分离在调用join 会正常运行&#xff1f; 2. 如何理解线程库&#xff1f;如何理解 先描述 在组织&#xff1f; 3. C中使用多线程4. 线程局部存储局部变量全局变量 1. 线程分离 1. 为什么要线程分…...

c++ ffmpeg 浅谈YUV444、YUV422、YUV420(2)

本期将会给大家介绍YUV相关基础知识&#xff0c;同时也介绍威创网络分布式系统的卓越色彩处理技术。 1.什么是YUV色彩空间 2.YUV采样格式 3.YUV不同采样格式对图像画质的影响分析 一、什么是YUV色彩空间? YUV是视频、图片、相机等应用中常常使用的一类图像格式&#xff0c;是…...

Redis在Windows下安装配置教程

Redis是一个开源的高性能键值对存储数据库&#xff0c;常用于缓存、消息队列等场景。本文将介绍如何在Windows系统下安装配置Redis。 1. 下载地址 Redis官方网站提供了Windows版本的安装包下载地址&#xff0c;网址为&#xff1a;https://github.com/tporadowski/redis/relea…...

数据库服务器

数据库服务器&#xff0c;联系Web服务器与DBMS的中间件是负责处理所有的应用程序服务器&#xff0c;包括在web服务器和后台的应用程序或数据库之间的事务处理和数据访问。 基本信息 中文名 数据库服务器 外文名 database server 功能 数据库服务器建立在数据库系统基础上&a…...

VS输出路径和生成事件

在生成时&#xff0c;常常希望输出文件夹整洁&#xff0c;因此需要设置dll或exe输出位置&#xff0c;同时也希望对一些文件做一些特殊操作 VS的 UI 常用缩写 “./”&#xff1a;代表目前所在的目录。 " . ./"代表上一层目录。 “/”&#xff1a;代表根目录。 生成…...

从 WebKit 看浏览器内核架构

浏览器常见的浏览器内核有&#xff1a;Blink、WebKit、Gecko、Trident 等&#xff0c;目前 WebKit 内核占据了非常大的的市场&#xff0c;包括 Chrome、Safari、安卓浏览器等市面上的主流浏览器&#xff0c;都使用了 WebKit 内核。 从 WebKit 看浏览器内核架构 既然 WebKit 这么…...

使用原生的 JavaScript,不依赖于任何特定的库与 ROSBridge进行通信

使用原生的 JavaScript&#xff0c;不依赖于任何特定的库与 ROSBridge进行通信 创建与 ROS 的连接&#xff1a; var rosbridge_url "ws://localhost:9090"; var ws new WebSocket(rosbridge_url);ws.onopen function(event) {console.log(Connected to rosbri…...

MATLAB第十章_图像处理算法

目录 图像处理算法 图像处理基础 图像处理函数 默认显示方式 添加颜色条 显示多帧图像 显示动画 三维材质图像 图像的直方图 灰度变换 均衡直方图 图像处理应用 图像增强 图像重建 图像变换 图像压缩 图像分割 图像边缘检测 图像识别 图像处理算法 图像处理…...

RobotFramework接口测试方案

1. Robot FrameWork介绍 1.1 介绍 Robot Framework是用于验收测试和回归测试的通用测试自动化框架。它使用易于理解的表格数据语法&#xff0c;非常友好的实现了关键字驱动和数据驱动模式。它的测试功能可以通过使用Python或Java实现的测试库进行扩展&#xff0c;用户可以使用…...

chatgpt赋能python:Python中日期转换:从字符串到日期对象

Python中日期转换&#xff1a;从字符串到日期对象 作为一个经验丰富的Python工程师&#xff0c;日期转换在我的日常编码工作中经常遇到。Python提供了一些内置函数和模块&#xff0c;可以将字符串转换为日期对象或将日期对象格式化为特定的字符串。本篇文章将带您深入了解Pyth…...

conda相比python好处

Conda 作为 Python 的环境和包管理工具&#xff0c;相比原生 Python 生态&#xff08;如 pip 虚拟环境&#xff09;有许多独特优势&#xff0c;尤其在多项目管理、依赖处理和跨平台兼容性等方面表现更优。以下是 Conda 的核心好处&#xff1a; 一、一站式环境管理&#xff1a…...

(十)学生端搭建

本次旨在将之前的已完成的部分功能进行拼装到学生端&#xff0c;同时完善学生端的构建。本次工作主要包括&#xff1a; 1.学生端整体界面布局 2.模拟考场与部分个人画像流程的串联 3.整体学生端逻辑 一、学生端 在主界面可以选择自己的用户角色 选择学生则进入学生登录界面…...

Qt/C++开发监控GB28181系统/取流协议/同时支持udp/tcp被动/tcp主动

一、前言说明 在2011版本的gb28181协议中&#xff0c;拉取视频流只要求udp方式&#xff0c;从2016开始要求新增支持tcp被动和tcp主动两种方式&#xff0c;udp理论上会丢包的&#xff0c;所以实际使用过程可能会出现画面花屏的情况&#xff0c;而tcp肯定不丢包&#xff0c;起码…...

shell脚本--常见案例

1、自动备份文件或目录 2、批量重命名文件 3、查找并删除指定名称的文件&#xff1a; 4、批量删除文件 5、查找并替换文件内容 6、批量创建文件 7、创建文件夹并移动文件 8、在文件夹中查找文件...

JVM垃圾回收机制全解析

Java虚拟机&#xff08;JVM&#xff09;中的垃圾收集器&#xff08;Garbage Collector&#xff0c;简称GC&#xff09;是用于自动管理内存的机制。它负责识别和清除不再被程序使用的对象&#xff0c;从而释放内存空间&#xff0c;避免内存泄漏和内存溢出等问题。垃圾收集器在Ja…...

cf2117E

原题链接&#xff1a;https://codeforces.com/contest/2117/problem/E 题目背景&#xff1a; 给定两个数组a,b&#xff0c;可以执行多次以下操作&#xff1a;选择 i (1 < i < n - 1)&#xff0c;并设置 或&#xff0c;也可以在执行上述操作前执行一次删除任意 和 。求…...

ESP32 I2S音频总线学习笔记(四): INMP441采集音频并实时播放

简介 前面两期文章我们介绍了I2S的读取和写入&#xff0c;一个是通过INMP441麦克风模块采集音频&#xff0c;一个是通过PCM5102A模块播放音频&#xff0c;那如果我们将两者结合起来&#xff0c;将麦克风采集到的音频通过PCM5102A播放&#xff0c;是不是就可以做一个扩音器了呢…...

IoT/HCIP实验-3/LiteOS操作系统内核实验(任务、内存、信号量、CMSIS..)

文章目录 概述HelloWorld 工程C/C配置编译器主配置Makefile脚本烧录器主配置运行结果程序调用栈 任务管理实验实验结果osal 系统适配层osal_task_create 其他实验实验源码内存管理实验互斥锁实验信号量实验 CMISIS接口实验还是得JlINKCMSIS 简介LiteOS->CMSIS任务间消息交互…...

【7色560页】职场可视化逻辑图高级数据分析PPT模版

7种色调职场工作汇报PPT&#xff0c;橙蓝、黑红、红蓝、蓝橙灰、浅蓝、浅绿、深蓝七种色调模版 【7色560页】职场可视化逻辑图高级数据分析PPT模版&#xff1a;职场可视化逻辑图分析PPT模版https://pan.quark.cn/s/78aeabbd92d1...

【JVM面试篇】高频八股汇总——类加载和类加载器

目录 1. 讲一下类加载过程&#xff1f; 2. Java创建对象的过程&#xff1f; 3. 对象的生命周期&#xff1f; 4. 类加载器有哪些&#xff1f; 5. 双亲委派模型的作用&#xff08;好处&#xff09;&#xff1f; 6. 讲一下类的加载和双亲委派原则&#xff1f; 7. 双亲委派模…...