1.3 c++虚基类的用途以及内存模型

news2024/11/28 0:59:21

1.3 虚基类

1.3.1 虚基类(菱形继承)的语法实现

对于如下的继承体系,定义了一个公共基类A。类B和类C都由类A公有派生,类D由类B和类C公有派生。

Alt
其示例代码如下所示,这段代码的45行是无法通过编译器的,这即是多重继承存在的一个问题:存在二义性。对象B和对象C里都有保存一个对象A,导致编译器不知道访问哪一个。不仅如此,由于基类对象A在派生类对象B,C中都有存储,会造成存储空间的浪费。这时在vs的命令行使用命令cl /d1reportSingleClassLayoutD CppObjectModelV.cpp,可以得到其空间存储如下所示,可以发现类D内部会存储两个类A的对象:
Alt

class A {

public:
    int x;

    void SetX(int a) { x = a; }

    explicit A(int a = 0) : x(a) { cout << "调用A构造函数" << endl; }

    void print_a() { cout << "PA" << x << endl; }
};

class B : public A {
public:
    int y;

    void SetY(int a) { y = a; }

    explicit B(int a = 0, int b = 0) : A(a), y(b) { cout << "调用B构造函数" << endl; }

    void print_b() { cout << "PB" << "x=" << x << "y=" << y << endl; }
};

class C : public A {
public:
    int z;

    void SetZ(int a) { z = a; }

    explicit C(int a = 0, int b = 0) : A(a), z(b) { cout << "调用C构造函数" << endl; }

    void print_c() { cout << "PC" << "x=" << x << "z=" << z << endl; }
};

class D : public B, C {
    int m;
public:
    explicit D(int a = 0, int b = 0, int c = 0, int d = 0, int e = 0) : B(a, b), C(c, d), m(e) {
        cout << "调用D构造函数" << endl;
    }

    void print_d() {
        print_b();
        print_c();
        cout << "x=" << x << "y=" << y << "z=" << z << endl;
    }
};

若要在菱形继承中,希望公共基类在派生类中只有一个拷贝,则可将该基类作为虚基类进行继承,即在定义派生类时,在继承的公共基类类名前加上关键字virtual,其余保持不变即可。新定义后就不会再出现编译错误,其代码如下所示:

class A {

public:
    int x;

    void SetX(int a) { x = a; }

    explicit A(int a = 0) : x(a) { cout << "调用A构造函数" << endl; }

    void print_a() { cout << "PA " << x << endl; }
};

class B : virtual public A {
public:
    int y;

    void SetY(int a) { y = a; }

    explicit B(int a = 0, int b = 0) : A(a), y(b) { cout << "调用B构造函数" << endl; }

    void print_b() { cout << "PB " << "x= " << x << " y= " << y << endl; }
};

class C : virtual public A {
public:
    int z;

    void SetZ(int a) { z = a; }

    explicit C(int a = 0, int b = 0) : A(a), z(b) { cout << "调用C构造函数" << endl; }

    void print_c() { cout << "PC " << " x= " << x << " z= " << z << endl; }
};

class D : public B, C {
    int m;
public:
    explicit D(int a = 0, int b = 0, int c = 0, int d = 0, int e = 0) : B(a, b), C(c, d), m(e) {
        cout << "调用D构造函数" << endl;
    }

    void print_d() {
        cout << "x= " << x << " y= " << y << " z= " << z << " m= " << m << endl;
    }
};

int main() {
    D d(1, 2, 3, 4, 5);
    d.print_d();
    return 0;
}

1.3.2 菱形继承内存模型

在虚拟继承的情况下,类B和类C的结构分别如下所示:注意因为这里采用了虚拟继承,所以类B和类C都会多产生一个虚基表指针,指向一个虚基表。此虚基表的作用是记录基类的数据在子类内存中存储位置(距离子类起始位置的偏移量),同时在虚基表的第一项也存储了基类虚基表指针距离此基类起始位置的偏移量。
如下所示在子类B的内存起始位置即存储了一个4个字节大小的虚基表指针vbptr,然后存储B自己内部的数据y,然后存储基类A。可以发现虚继承时,基类是存储在子类内存里的尾端的。同时虚基表指针vbptr指向B的虚基表,此虚基表的第一项存储了虚基表指针距离基类的偏移值,显然vptr存储在类B的头部,因此偏移值是0。第二项记录了基类A的数据项x距离基类起始地址的偏移值,这里显然是8。因为类C和类B的内存一致,所以这里不再重复分析。
其实虚基类只要没有虚函数,则其第一项通常都是0,如下所示:
Alt

Alt

Alt

如下所示是D类的内存模型,可以发现这时D会含有两个虚基表与两个虚基表指针,其表内容分析和上述一样。也可以参考博客:
图说C++对象模型:对象内存布局详解

Alt

可以看到这时不再会出现编译报错,但是可以发现x的结果是0不是1。这是因为类D的构造函数调用了BC的构造函数,类BC又调用了类A的构造函数,但是因为由于虚基类AD中只有一个拷贝,编译器无法确定是由B还是C的构造函数调用A的构造函数。因此编译器约定,执行BC的构造函数时不调用虚基类A的构造函数,而在类D的构造函数中直接调用A的默认的构造函数。
如果派生类继承了多个基类,基类有虚基类和非虚基类,那么在创建该派生类的对象时,先调用虚基类的构造函数,然后调用非虚基类的构造函数,最后调用派生类的构造函数。若虚基类有多个,则虚基类构造函数的调用顺序取决于它们继承时的说明顺序。
Alt

1.3.3 虚基表存在的原因

可以看到,为了实现虚继承,必须定义相应的虚继承表用于指向虚基类在继承类中的偏移量,虚基类表与虚基类指针的设定都是在编译阶段由编译器生成相应的默认构造函数完成的。
若编译器已经知道了所有的偏移量,也就是D在最终的内存构造,那么所有的偏移量都可以在编译阶段计算出来,则虚基表存在的意义是什么?
还是对于上述的继承模型,若存在如下函数f,则对于第二行的调用,显然在编译期间编译器并不知道会生成哪个实际对象,同时若在函数f内有对基类变量的操纵,显然因为编译期间无法确定实际生成的是对象B还是对象D,那也就无法在此处定义此基类变量的实际偏移量,因此必须使用虚基表来定位这个偏移量。

A* f(B* b) {//……}
B* b = (rand() % 2 == 0) ? new B : new D; f(b);

虚函数表的作用:
Why does virtual inheritance need a vtable even if no virtual functions are involved?

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

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

相关文章

十二、组合API(2)

本章概要 响应式 API reactive() 方法watchEffect() 方法解构响应性状态深入 watchEffect()refreadonlycomputedwatch 11.3 响应式 API Vue 3.0 的核心功能主要是通过响应式 API 实现的&#xff0c;组合 API 将他们公开为独立的函数。 11.3.1 reactive() 方法 reactive()…

基于物联网的自动灌溉系统的设计与实现

本设计是基于物联网的自动灌溉系统&#xff0c;主要实现以下功能&#xff1a; 1&#xff0c;OLED显示温湿度和土壤温湿度&#xff1b; 2&#xff0c;可通过继电器实现自动灌溉和自动加热的功能&#xff1b; 3&#xff0c;通过lora构建自组网&#xff0c;进行主从机间的数据传输…

正点原子 核心板IMX6ULL IIC RTC驱动 PCF8563

目录前言IIC RTC PCF8563硬件使用IIC设备地址配置 menuconfig 自带PCF8563驱动修改设备树dtb编写应用App测试前言 此篇基于学完【正点原子】I.MX6U嵌入式Linux驱动开发指南V1.6 后&#xff0c;使用核心板进行自行设置。 IIC RTC PCF8563硬件使用 Imx6ul内部的RTC时钟不是很准…

VMware Workstation虚拟机网络相关配置

1、网络配置 1.1、方式一&#xff1a;配置文件 配置文件&#xff1a;网络参数之IP地址与子网掩码、网关地址、DNS 1.1.1、删除旧网卡配置文件 rm -rf /etc/sysconfig/network-scripts/ifcfg-* 1.1.2、grub内核引导程序&#xff0c;定义网卡重新命名规则 vim /etc/default/…

若依vue ruoyi-vue ant design版本使用

若依vue默认是使用element ui的&#xff0c;但是现在大部分项目都用ant design&#xff0c;ant design的组件也比element多&#xff0c;所以最近有想更改成ant design。网上搜了一下&#xff0c;已经有现成的了。 RuoYi-Antdv https://gitee.com/fuzui/RuoYi-Antdv RuoYi-Ant…

【scala】第二章——Scala 变量和数据类型

文章目录1 注释2 变量和常量&#xff08;重点&#xff09;3 标识符的命名规范4 字符串输出5 键盘输入6 数据类型&#xff08;重点&#xff09;7 整数类型&#xff08;Byte、Short、Int、Long&#xff09;8 浮点类型&#xff08;Float、Double&#xff09;9 字符类型&#xff08…

1秒钟搞懂tee和vim文件的使用命令(超级详细)

1秒钟搞懂tee和vim文件的使用命令&#xff08;超级详细&#xff09;一&#xff0c;tee的具体使用1&#xff0c;tee用来显示屏幕并且保存在文件中2&#xff0c;&#xff08;|&#xff09;管道符用来覆盖上一文件内容3&#xff0c;-a用来追加文件内容二&#xff0c;vim的命令模式…

[附源码]计算机毕业设计JAVA教室用电控制系统

[附源码]计算机毕业设计JAVA教室用电控制系统 项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; SSM mybati…

如何把图片文字转换成文字?图片转文字方法推荐

我们在上课的时候&#xff0c;常常会跟不上老师的板书&#xff0c;这时候我们就会对一些来不及记录的板书&#xff0c;拍成图片保存下来&#xff0c;等到课后再进行整理。可是当图片积累的过多的时候&#xff0c;再一张一张的进行抄写&#xff0c;就会很浪费时间和精力了。但其…

诚邀莅临 | 天奥智能参展第86届中国国际医疗器械博览会

11月23-26日&#xff0c;第86届中国国际医疗器械博览会&#xff08;CMEF&#xff09;在深圳国际会展中心&#xff08;宝安新馆&#xff09;隆重举办。本届大会以“创新科技、智领未来”为主题&#xff0c;吸引了超过4000家国内外医疗器械、医用耗材、医疗机器人等企业参会。 南…

机器学习参数|数学建模|自相关性

目录 1.定义和影响 1.1自相关性产生的原因 1.2自相关的后果 2.减小影响方法 2.1如何判断数据存在自相关性 a.用相关计量软件 b.Durbin-Watson Statistics(德宾-瓦特逊检验) c.Q-Statistics 以(box-pierce)- Eviews(7th version第七版本)为例子 2.2如何减弱模型的自相关…

pytorch使用GPU加速--windows11,GTX1650Super

使用的都是anaconda创建的环境 1.软件准备 下载cuda 查看自己的显卡驱动–进入NVIDIA的控制面板 然后根据显卡驱动下载对映的cuda 查看的网址 下载cuda的网址 下载cuDNN NVIDIA cuDNN是用于深度神经网络的GPU加速库。它强调性能、易用性和低内存开销。 cudnn下载网址 这个下…

使用CAPL 内置函数 memcpy 和memcmp 处理数组的若干问题

&#x1f345; 我是蚂蚁小兵&#xff0c;专注于车载诊断领域&#xff0c;尤其擅长于对CANoe工具的使用&#x1f345; 寻找组织 &#xff0c;答疑解惑&#xff0c;摸鱼聊天&#xff0c;博客源码&#xff0c;点击加入&#x1f449;【相亲相爱一家人】&#x1f345; 玩转CANoe&…

树状数组学习

树状数组简介 树状数组&#xff0c;用于维护和查询前缀和&#xff0c;与线段树功能类似。树状数组代码短&#xff0c;常数和空间小&#xff0c;时间复杂度小&#xff0c;所以这也是一个十分优秀的算法。 设a[i]a[i]a[i]为原数组上的点&#xff0c;s[i]s[i]s[i]为树状数组中各点…

WordPress管理仪表板:在15分钟内成为WordPress专家

WordPress管理仪表板是内容管理系统 (CMS)的核心和灵魂。在这里&#xff0c;您可以监督网站的各个方面&#xff0c;从配置基本设置到发布内容、安装插件和主题等等。如果您不熟悉 WordPress 管理仪表板&#xff0c;您将很难管理网站。 了解如何使用仪表板比您想象的要容易。所有…

PixiJs学前篇(三):Canvas基础【下篇】

前言 在上一篇文章 PixiJs学前篇&#xff08;二&#xff09;&#xff1a;Canvas基础【中篇】 中我们了解了Canvas的基本绘制形状&#xff0c;接下来我们看一下如何在 Canvas 中绘制文本。 绘制文本 文本的绘制也是 Canvas 中也是比较常见的&#xff0c;在 Canvas 的绘制中&a…

STC51单片机36——51单片机简单分两路控制步进电机

按键控制步进电机正反转一定设置的角度&#xff0c;比如一圈360度&#xff0c;按一次30度&#xff0c;一起12档。分两路控制&#xff0c;4个加减按键&#xff0c;一个按键控制复位&#xff0c;每路控制输出tb6600驱动器驱动两个42电机同步。同时数码管显示出来每次按键加减后的…

FastDFS(分布式文件管理系统)

一、简介 解决了大容量的文件存储和高并发访问的问题&#xff0c;文件存取时实现了负载均衡。 FastDFS服务端只有两个角色&#xff0c;tracker server和storage server。 所有同角色服务器集群节点都是平等的&#xff0c;不存在主从关系&#xff08;Master-Slave&#xff09;…

golang爬虫练习-抓取行业信息分类

抓取框架介绍 gathertool gathertool是golang脚本化开发库&#xff0c;目的是提高对应场景程序开发的效率&#xff1b;轻量级爬虫库&#xff0c;接口测试&压力测试库&#xff0c;DB操作库等。 地址&#xff1a; https://github.com/mangenotwork/gathertool 下载: go get …

醛肽:Gly-Phe-Gly-aldehyde、102579-48-6

可逆组织蛋白酶 B 抑制剂 GFG-醛缩氨基脲已用于通过亲和层析从日本血吸虫中纯化组织蛋白酶 B 样蛋白酶 Sj31&#xff0c;并用于从疟原虫物种中分离恶性疟原虫。编号: 200138 中文名称: 三肽Gly-Phe-Gly-aldehyde CAS号: 102579-48-6 单字母: H2N-GFG-CHO 三字母: H2N-Gly-Phe-G…