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

news2024/10/5 12:42:09

虚表和虚基表

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

虚表

我们知道,如果类中声明了的方法是用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类的虚基表来说,其中的值为:
在这里插入图片描述
在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/563925.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

运营-16.个性化推荐

个性化推荐 个性化推荐&#xff0c;是根据用户的行为来分析用户的喜好&#xff0c;进而做商品精准推荐。 为什么要做个性化推荐&#xff1f; 1. 收集用户信息&#xff0c;精准获取用户需求&#xff1b; 2. 减少用户搜索商品的页面层级&#xff0c;提高转化率&#xff1b; …

聊聊 Milvus GC:从一次数据丢失事件展开

QueryNode 日志中频繁报错&#xff1f;对象存储数据离奇消失[1]&#xff1f; 令人震惊的数据丢失事件就这样发生了&#xff0c;一位来自 BOSS 直聘的 AI 研发工程师无意卷入到此次的风波中&#xff0c;他和 Milvus 社区的伙伴经过层层排查、抽丝剥茧&#xff0c;成功找出了问题…

还在用 JS 做节流吗?CSS 也可以防止按钮重复点击

目录 一、CSS 实现思路分析 二、CSS 动画的精准控制 三、CSS 实现的其他思路 四、总结一下 众所周知&#xff0c;函数节流&#xff08;throttle&#xff09;是 JS 中一个非常常见的优化手段&#xff0c;可以有效的避免函数过于频繁的执行。 举个例子&#xff1a;一个保存按…

opencv_c++学习(二十)

一、形态学应用案例 开、闭运算、形态学梯度等原理&#xff1a; 相关函数: morphologyEx(InputArray src, OutputArray dst, int op, lnputArray kernel, Point anchor Point(-1,-1), int iterations 1, int borderType BORDER_CONSTANT, const Scalar & border…

Android中静态和动态文字的绘制和测量

Android中静态和动态文字的绘制和测量 Android中自定义视图的时候存在两种情况&#xff0c;静态文字和动态文字。 顾名思义&#xff0c;静态文字就是显示内容是固定的&#xff0c;不会产生变化的文字&#xff0c;而动态文字则是内容会不断产生变化的文字信息。 在说明为什么…

Revit技巧 | Revit中图元不可见怎么办?

在revit中&#xff0c;控制图元课件性的设置有很多种&#xff0c;因此图元不可见&#xff0c;也会有各种各样的原因&#xff0c;这也是经常困扰新手的问题&#xff0c;下面我把这些解决办法做一些归纳总结。 图元如果过远偏离当前视图的中心&#xff0c;将导致视图不可见这时&…

MySQL:数据库的查询与连接

目录 1.复合查询 1.1 多表查询&#xff08;联合查询&#xff09; 1.2 join on (inner join) 1.3 自连接 1.4 子查询 1.5 合并查询 2.内外连接 3.关于高内聚、低耦合 1.复合查询 1.1 多表查询&#xff08;联合查询&#xff09; 什么是多表插叙&#xff1f;实际开发中往…

网络安全管理员证书有什么用?2023证书怎么考?证书报考条件?

网络安全管理员是做什么工作的呢&#xff1f;现如今&#xff0c;网络高速发展&#xff0c;带动了很多行业的兴起&#xff0c;比如说电商行业&#xff0c;今天已经步入到足不出户即可购物的时代了&#xff0c;当然网络也是一把“双刃剑”&#xff0c;带来了好处的同时&#xff0…

Sui Move Object讲解

要了解Sui的独特特性&#xff0c;首先要了解Sui中以对象为中心的数据模型。 Sui的设计初衷是重新定义数字资产所有权的可能性。重新设计的一个基本部分 — — Sui是以对象为中心的数据模型&#xff0c;也是Sui和其他Layer 1区块链之间的一个显著区别。 其他L1如何处理资产所有…

day8 - 使用不同的滤波核进行图像降噪

本期主要介绍用于图像平滑处理的滤波&#xff0c;分别是方框滤波、均值滤波、中值滤波、高斯滤波&#xff0c;比较不同滤波的效果&#xff1b;并了解自定义滤波器进行图像处理。 完成本期内容&#xff0c;你可以&#xff1a; 会使用方框滤波、均值滤波、中值滤波、高斯滤波进行…

实时聊天组合功能,你了解吗?

你有兴趣安装实时聊天组合功能吗&#xff1f;如果您选择了SaleSmartly&#xff08;ss客服&#xff09;&#xff0c;您的实时聊天插件可以不仅仅只是聊天通道&#xff0c;还可以有各种各样的功能&#xff0c;你不需要包含每一个功能&#xff0c;正所谓「宁缺勿滥」&#xff0c;功…

Windows主机中构建适用于K8S Operator开发环境

基于 win 10 打造K8S应用开发环境 一、wsl子系统安装 在cmd命令行终端或powershell中操作 1.1 确认windows操作系统版本 1.2 开启wsl功能 1.3 wsl配置 PS C:\Users\cpf> wsl提示&#xff1a;适用于 Linux 的 Windows 子系统没有已安装的分发版。可以通过访问 Microsoft St…

使用canvas给图片添加水印

上接文章“图片处理” canvas元素其实就是一个画布&#xff0c;我们可以很方便地绘制一些文字、线条、图形等&#xff0c;它也可以将一个img标签里渲染的图片画在画布上。 我们在上传文件到后端的时候&#xff0c;使用input标签读取用户本地文件后得到的其实是一个Blob对象&a…

Redis7实战加面试题-基础篇(Redis持久化,Redis事务,Redis管道,Redis发布订阅)

Redis持久化 RDB (Redis DataBase) RDB&#xff08;Redis 数据库&#xff09;&#xff1a;RDB 持久性以指定的时间间隔执行数据集的时间点快照。实现类似照片记录效果的方式&#xff0c;就是把某一时刻的数据和状态以文件的形式写到磁盘上&#xff0c;也就是快照。这样一来即使…

HCIA-ARP、MAC、交换机工作原理

目录 万能数据转发模型 ARP协议&#xff1a;地址解析协议 以太网帧的交换 IP地址和Mac地址的区别&#xff1a; 以太网交换机介绍&#xff1a; 交换机的工作原理&#xff1a; ​编辑交换机处理数据的三种方式&#xff1a; Mac表和ARP表的区别&#xff1a; 万能数据转发模…

自定义注解和@Target、@Retention注解的使用

说明&#xff1a;注解可以理解为另一种形式的配置&#xff0c;可用于在类上、方法上等&#xff0c;标志是“”&#xff0c;如重写方法上的“Override”就是一种注解。这里我通过一个实例&#xff0c;来介绍自定义注解和java元注解&#xff08;Target、Retention&#xff09;的使…

案例20:Java物流管理系统设计与实现开题报告

博主介绍&#xff1a;✌全网粉丝30W,csdn特邀作者、博客专家、CSDN新星计划导师、java领域优质创作者,博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌ &#x1f345;文末获取源码联系&#x1f345; &#x1f447;&#x1f3fb; 精彩专…

ChatGPT全球最大开源平替OpenAssistant:基于Pythia和LLaMA微调而来

论文地址&#xff1a;https://drive.google.com/file/d/10iR5hKwFqAKhL3umx8muOWSRm7hs5FqX/view 项目地址&#xff1a;https://github.com/LAION-AI/Open-Assistant 数据集地址&#xff1a;https://huggingface.co/datasets/OpenAssistant/oasst1 体验地址&#xff1a;http…

Hiredis的基本使用

目录 前言 一.hiredis的安装 二.同步API 2.1.连接Redis数据库 2.1.1 无超时时间&#xff0c;阻塞等待连接 2.1.2 设置超时时间&#xff0c;阻塞等待连接。 2.1.3 非阻塞&#xff0c;不管连接与否&#xff0c;立即返回。 2.2.执行命令 2.2.1 返回执行上下文 2.2.2 没有返回执…

伪类元素的用法总结

1:自闭标签不适用伪类元素 自闭合标签 1. 一般标签   由于有开始符号和结束符号&#xff0c;因此可以在内部插入其他标签或文字。 <p>“绿叶&#xff0c;给你初恋般的感觉。”</p> 2. 自闭合标签   由于只有开始符号而没有结束符号&#xff0c;因此不可以在内…