虚函数的复杂(继承)内存布局

news2024/12/24 7:37:16

文章目录

  • 单继承(无虚函数覆盖)
  • 单继承(有虚函数覆盖)
  • 多继承(无虚函数覆盖)
  • 多继承(有虚函数覆盖)
  • 菱形继承(有虚函数覆盖)
  • 菱形虚拟继承(有虚函数覆盖)

继承关系一般分为: 单继承多继承,而多继承的出现就又导致了一种特殊的继承关系 菱形继承,为了解决菱形继承的冗余和二义性,又有了 菱形虚拟继承的解决方案。本篇主要介绍以上四种继承关系的 类对象虚函数内存布局
tips:本篇中的例程和内存布局均采用编译器visual studio 2022。

单继承(无虚函数覆盖)

假设有如下所示的继承关系:

class Base{
public:
    int ibase;
    Base():ibase(10){}
    virtual void a(){ cout << "Base::a()" << endl; }
    virtual void b(){ cout << "Base::b()" << endl; }
};
class Derive : public Base{
public:
    int iderive;
    Derive():iderive(100){}
    virtual void c(){ cout << "Derive::c()" << endl; }
};

在这个继承关系中,派生类并没有重写基类的任何函数。那么,在派生类的的实例中,其内存布局如下:

image-20230203221658797

可以看到以下几点结论:

  1. 派生类继承了基类的虚函数表;
  2. 派生类的虚函数进入了虚函数表并且排在基类的虚函数后面。

单继承(有虚函数覆盖)

假设有如下所示的继承关系:

class Base{
public:
    int ibase;
    Base():ibase(10){}
    virtual void a(){ cout << "Base::a()" << endl; }
    virtual void b(){ cout << "Base::b()" << endl; }
};
class Derive : public Base{
public:
    int iderive;
    Derive():iderive(100){}
    virtual void a(){ cout << "Derive::a()" << endl; }
    virtual void c(){ cout << "Derive::c()" << endl; }
};

在这个继承关系中,派生类中有重写了基类的部分函数。那么,在派生类的的实例中,其内存布局如下:

image-20230203221726390

可以看到以下几点结论:

  1. 重写后的虚函数放在了虚函数表中基类对应虚函数原来的位置;
  2. 没有被重写的虚函数在在虚函数表中不变。

多继承(无虚函数覆盖)

假设有如下所示的继承关系:

class Base1{
public:
    int ibase1;
    Base1():ibase1(11){}
    virtual void a(){ cout << "Base1::a()" << endl; }
    virtual void b(){ cout << "Base1::b()" << endl; }
};
class Base2{
public:
    int ibase2;
    Base2():ibase2(22){}
    virtual void a(){ cout << "Base2::a()" << endl; }
    virtual void b(){ cout << "Base2::b()" << endl; }
};
class Derive : public Base1, public Base2{
public:
    int iderive;
    Derive():iderive(100){}
    virtual void c(){ cout << "Derive::c()" << endl; }
};

在这个继承关系中,派生类中有重写了基类的部分函数。那么,在派生类的的实例中,其内存布局如下:

image-20230203221737292

可以看到以下几点结论:

  1. 派生类继承了两个基类的两个虚函数表;
  2. 派生类的两个虚函数表指针分别在自己的区域内指向自己的虚函数表;
  3. 派生类继承的两个基类按继承顺序在内存中排布。

多继承(有虚函数覆盖)

假设有如下所示的继承关系:

class Base1{
public:
    int ibase1;
    Base1():ibase1(11){}
    virtual void a(){ cout << "Base1::a()" << endl; }
    virtual void b(){ cout << "Base1::b()" << endl; }
};
class Base2{
public:
    int ibase2;
    Base2():ibase2(22){}
    virtual void a(){ cout << "Base2::a()" << endl; }
    virtual void b(){ cout << "Base2::b()" << endl; }
};
class Derive : public Base1, public Base2{
public:
    int iderive;
    Derive():iderive(100){}
    virtual void a(){ cout << "Derive::a()" << endl; }
    virtual void c(){ cout << "Derive::c()" << endl; }
};

在这个继承关系中,派生类中有重写了基类的部分函数。那么,在派生类的的实例中,其内存布局如下:

image-20230203221752676

可以看到以下结论:

​ 派生类重写的虚函数对所有基类都生效。

菱形继承(有虚函数覆盖)

假设有如下所示的继承关系:

class Parent{
public:
    int iparent;
    Parent():iparent(10){}
    virtual void a() { cout << "Parent::a()" << endl; }
    virtual void b() { cout << "Parent::b()" << endl; }
    virtual void c() { cout << "Parent::c()" << endl; }
};
class Child1 : public Parent{
public:
    int ichild1;
    Child1():ichild1(111){}
    virtual void a() { cout << "Child1::a()" << endl; }
    virtual void b_child1() { cout << "Child1::b_child1()" << endl; }
    virtual void c_child1() { cout << "Child1::c_child1()" << endl; }
};
class Child2 : public Parent{
public:
    int ichild2;
    Child2():ichild2(222){}
    virtual void a() { cout << "Child2::a()" << endl; }
    virtual void b_child2() { cout << "Child2::b_child2()" << endl; }
    virtual void c_child2() { cout << "Child2::c_child2()" << endl; }
};
class GrandChild : public Child1, public Child2{
public:
    int igrandchild;
    GrandChild():igrandchild(1000){}
    virtual void a() { cout << "GrandChild::a()" << endl; }
    virtual void b_child1() { cout << "GrandChild::b_child1()" << endl; }
    virtual void b_child2() { cout << "GrandChild::b_child2()" << endl; }
    virtual void c_grandchild() { cout << "GrandChild::c_grandchild()" << endl; }
};

在此继承关系中,两个中间类分别重写了父类的a函数,孙子类又重写了爷爷类的a函数、分别重写了两个父类的b_child2函数和b_child1函数。

image-20230203221803692

可以看到以下结论:

  1. 菱形继承导致数据冗余(Parent::iparent = 10);
  2. 当访问Parent::iparent时会产生二义性,需要手动添加Child1::或者Child2::;
  3. 派生类的虚函数自动排往第一个虚函数表结尾。

菱形虚拟继承(有虚函数覆盖)

为了解决菱形继承产生的数据冗余和二义性,所以C++引入了虚基类的概念,也就是进行虚拟继承,只需在继承的时候再添加一个关键字virtual

如下所示继承关系和上个例子一样,仅仅只是添加了关键字virtual变成了虚拟继承。

class Parent{ ...... };
class Child1 : virtual public Parent{ ...... };
class Child2 : virtual public Parent{ ...... };
class GrandChild : public Child1, public Child2{ ...... };

image-20230203221814894

可以看到以下结论:

  1. 本来冗余的数据Parent::iparent=10不再冗余,Parent整体部分搬迁到最末尾;
  2. 虚函数表由两个变为三个,中间类的两个虚函数表中都去掉重复的部分(即继承下来的Parent的虚函数);
  3. Parent的虚函数由Parent的虚函数表指针单独维护;
  4. 派生类的虚函数仍自动排往第一个虚函数表;
  5. 额外多了两个指针__vbptr,该指针指向一个整形数组,元素都为偏移值;
  6. 根据__vbTable中的偏移值即可找到Parent实例的位置。

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

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

相关文章

浅析git

目录 git 的历史 git 的感性认识 git 在Linux下的操作 git三板斧 git 的历史 雷纳斯托瓦兹&#xff0c;想必大家对这个名字并不陌生&#xff0c;他是Linux内核的最早作者&#xff0c;随后发起了这个开源项目&#xff0c;担任Linux内核的首要架构师与项目协调者&#xff0c…

Kafka入门(一)

1、Kafka简介 Kafka是一个分布式的基于发布/订阅模式的消息队列&#xff08;Message Queue&#xff09;&#xff0c;主要应用于大数据实时处理领域&#xff08;hadoop集群&#xff09;、物联网领域。其主要设计目标如下&#xff1a; 以时间复杂度为O(1)的方式提供消息持久化能…

华为路由器Talent服务配置

前言 Telnet&#xff1a;console是通过本地进行设备管理&#xff0c;还有一种是通过远程登录的方式进行设备管理&#xff0c;也就是虚拟终端。通过发送信息进行控制&#xff0c;不受终端和服务器的位置限制&#xff08;只要可以通信&#xff0c;服务器启用了Telnet功能即可&am…

Python 的基础语法

第一个 Python 程序交互式编程交互式编程不需要创建脚本文件&#xff0c;是通过 Python 解释器的交互模式进来编写代码。linux上你只需要在命令行中输入 Python 命令即可启动交互式编程,提示窗口如下&#xff1a;$ pythonPython 2.7.6 (default, Sep 9 2014, 15:04:36)[GCC 4.2…

LinkedHashSet源码阅读理解

概述 1、底层&#xff1a;HashSet LinkedHashMap 2、创建节点时将节点插入链表&#xff0c;因此有序 3、线程不安全 源码理解 demo&#xff1a; public class LinkedHashSetDemo {public static void main(String[] args) {test();}public static void test(){LinkedHas…

CDMP认证考试考前你需要了解的那些事

对国内的数据从业人员来说&#xff0c;CDMP算比较新的考试&#xff0c;目前相关介绍很少&#xff0c;小编整理了CDMP考试先关的一些内容&#xff0c;希望对正在考虑考取CDMP认证的你有所帮助&#xff01;CDMP认证有几个等级&#xff1f;4个。A级&#xff08;基础级&#xff09;…

为什么Google优化排名前期要做长尾关键词?谷歌seo怎么做?

本文主要分享关于谷歌长尾词对于外贸网站获取流量和排名的重要性。 本文由光算创作&#xff0c;有可能会被修改和剽窃&#xff0c;我们佛系对待这种行为吧。 Google优化排名是指在Google 搜索结果中&#xff0c;使外贸站的排名更高。 长尾关键词是指长度较长的&#xff0c;不…

((蓝桥杯 刷题全集)【备战(蓝桥杯)算法竞赛-第2天】( 从头开始重新做题,记录备战竞赛路上的每一道题 )距离蓝桥杯还有65天

&#x1f3c6;&#x1f3c6;&#x1f3c6;&#x1f3c6;&#x1f3c6;&#x1f3c6;&#x1f3c6; 欢迎观看我的博客&#xff0c;如有问题交流&#xff0c;欢迎评论区留言&#xff0c;一定尽快回复&#xff01;&#xff08;大家可以去看我的专栏&#xff0c;是所有文章的目录&a…

【计算机网络】Linux下路由配置总结

文章目录路由的基础知识Linux内核路由表使用route -n命令查看Linux内核路由表三种路由类型说明(Flags)配置路由route的命令设置包转发静态路由配置参考路由的基础知识 1&#xff09;路由概念 路由&#xff1a; 跨越从源主机到目标主机的一个互联网络来转发数据包的过程路由器…

多线程代码案例之单例模式

目录 单例模式 饿汉模式 懒汉模式 问题一 问题二 问题三 单例模式 单例模式&#xff0c;是设计模式的一种。在有些特定场景中&#xff0c;有的特定的类&#xff0c;只能创建出一个实例&#xff0c;不应该创建多个实例。单例模式就可以保证这样的需求。例如JDBC中的Data…

OpenMMLab AI实战营笔记前两次课

文章目录1计算机视觉算法基础与 OpenMMLabCV引入OpenMMLab基础知识&#xff1a;2 计算机视觉之图像分类算法基础传统方法--设计图像特征AlexNet VGG 等神经网络搜索&#xff08;2016&#xff09;Vision/Swin Transformer轻量化卷积神经网络注意力机制 Attention Mechanism模型学…

文档存储Elasticsearch系列--3分布式存储和搜索过程

前言&#xff1a;ES 作为分布式文档的存储&#xff0c;它的存储过程是怎样的&#xff0c;它的分布式检索过程又是怎样的&#xff1b; 1 分布式存储过程&#xff1a; 为了说明目的, 我们 假设有一个集群由三个节点组成。 它包含一个叫 blogs 的索引&#xff0c;有两个主分片&a…

linux查看/设置某个进程运行的CPU核

目录 1.ps -eF 2.top命令 3.pidstat命令 4.使用taskset指令 5.使用taskset指定进程运行在CPU核 1.ps -eF #查看fwd进程运行在哪个cpu核上 [rootCENTOS57 rpm]# ps -eF | grep fwd 2.top命令 (1)top (2)按f键可以选择下面配置选项 P Last Used Cpu (SMP) (3)Es…

【深度学习】YOLO系列(v1-v3+tinyv3)解析

YOLOv1 正负样本选取 如果目标的中心落在cell中,那么这个cell就负责预测这个类别。 由于每个cell预测两个bbox,那么选择与GT IOU大的bbox来预测这个目标,也就是这一个框的 1 i j o b j = 1 , 1 i j n o b j

通过Python的pptx库操作ppt-替换文本和图片-批量生成任意自定义图片

通过Python的pptx库操作ppt-替换文本和图片-批量生成任意自定义图片 作者&#xff1a;虚坏叔叔 博客&#xff1a;https://xuhss.com 早餐店不会开到晚上&#xff0c;想吃的人早就来了&#xff01;&#x1f604; 一、前言 这是一个全部的脚本&#xff0c;我们知道&#xff0c;…

阿里巴巴最全Java、架构师、大数据、算法PPT技术栈图册

我只截图不说话&#xff0c;PPT大全&#xff0c;氛围研发篇、算法篇、大数据、Java后端架构&#xff01;除了大家熟悉的交易、支付场景外&#xff0c;支撑起阿里双十一交易1682亿元的“超级工程”其实包括以下但不限于客服、搜索、推荐、广告、库存、物流、云计算等。 Java核心…

第二章 Linux系统安装

第一节 安装计划 基本思路是使用VMWare这样的虚拟机软件创建一个“虚拟计算机”&#xff0c;在虚拟机上安装Linux系统。 安装vm软件通过vm软件来创建一个虚拟机空间通过vm软件来在创建好的虚拟空间上&#xff0c;安装我们的Centos操作系统使用Centos 第二节 vmware下载安装 和…

python-实现保留3位有效数字(四舍六入五成双规则)

项目场景&#xff1a; 实现保留3位有效数字&#xff08;四舍六入五成双规则&#xff09; 问题描述 输入&#xff1a;输出&#xff1a; 1234 123412 12.04 4.000.2 0.2000.32 0.3201.3 1.301.235 1.241.245 1.241.2451 1.25示例分析&#xff1a; 解决代码&#xff1a; from de…

jvm启动流程以及自定义加载器

类加载运行过程&#xff0c;当我们用java命令运行某个类的main函数启动程序时&#xff0c;首先需要通过类加载器把主类加载到JVM。public class Math {public static final int initData 666;public static final User user new User();public int compute() {// 一个方法对应…

【C++】对象与类

【C】对象与类 文章目录【C】对象与类1、定义1.1 对象的定义1.2 类的定义2、对象与类的创建2.1 类的创建2.2 对象的创建3、封装3.1 访问限定符3.2 对封装的解释4、类的实例化5、类、对象大小6、this指针6.1 this指针概念6.2 this指针特点1、定义 1.1 对象的定义 现实世界对对…