C++对象模型(整理)

2023-05-16

C++对象模型

1、何为C++对象模型?

C++对象模型可以概括为以下2部分:

  • ① 语言中直接支持面向对象程序设计的部分
    • 面向对象程序设计部分:如构造函数析构函数虚函数继承(单继承、多继承、虚继承)、多态等。
  • ② 对于各种支持的底层实现机制

在C++类中有两种数据成员、三种成员函数

  • 两种数据成员:static、nonstatic。
  • 三种成员函数:static、nonstatic、virtual。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-WCVz1h2A-1624582513579)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20210527111257338.png)]

代码举例说明:

//base.h
#pragma once
#include<iostream>
using namespace std;
class Base
{
public:
	Base(int);
	virtual ~Base(void);

	int getIBase() const;
	static int instanceCount();
	virtual void print() const;

protected:
	int iBase;
	static int count;
};

问:Base类在机器中我们如何构建出各种成员数据和成员函数的呢?

2、基本C++对象模型

在介绍C++使用的对象模型之前,介绍2种对象模型:简单对象模型(a simple object model)、表格驱动对象模型(a table-driven object model)。

a)简单对象模型

所有的成员占用相同的空间(跟成员类型无关),对象只是维护了一个包含成员指针的一个表。表中放的是成员的地址,无论上成员变量还是函数,都是这样处理。对象并没有直接保存成员而是保存了成员的指针。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-LspY6TpE-1624582513583)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20210527112146984.png)]

b)表格驱动对象模型

这个模型在 简单对象模型 的基础上又添加了 一个间接层

将成员分成函数和数据,并且用两个表格保存,然后对象只保存了 两个指向表格的指针

表格驱动对象模型 可以保证所有的 对象 具有相同的大小(只保存俩指针); 简单对象模型 的类对象还与成员的个数相关。

  • 其中数据成员表中包含实际数据;
  • 函数成员表中包含的实际函数的地址(与数据成员相比,多一次寻址)。

c)C++对象模型

在C++对象模型中:

  • ① nonstatic 数据成员被放置到对象内部;
  • ② static数据成员、static and nonstatic 函数成员均被放到对象之外。
  • ③ 对虚函数的支持分为两步:
    • a)每个class会为每个虚函数生成一个指针,这些指针统一放在虚函数表中(vtbl)
    • b)每个class的对象都会添加一个指针(vptr),指向相关的虚函数表(vtbl)。
      • vptr的设定(setting)和重置(resetting)都由每一个class的构造函数析构函数拷贝赋值运算符自动完成。
  • ④ 另外,虚函数表地址的前面设置了一个指向type_info类的指针。
    • C++提供了一个type_info类来获取对象类型信息。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-OzJFHlm3-1624582513587)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20210527114108905.png)]

C++对象模型优点与缺点:

  • 优点:在于它的空间和存取时间的效率
  • 缺点:当所使用的类的non static数据成员添加删除或修改时,需要重新编译。

d)模型验证测试

class Base
{
public:
    Base(int i) :baseI(i){};
    virtual void print(void){ cout << "调用了虚函数Base::print()"; }
    virtual void setI(){cout<<"调用了虚函数Base::setI()";}
    virtual ~Base(){}
private:
    int baseI;
};
  • 当一个类本身定义了虚函数,或其父类有虚函数时,为了支持多态机制,编译器将为该类添加一个虚函数指针(vptr)。

  • 虚函数指针一般都放在 对象内存布局的第一个位置 上,这是为了保证在多层继承或多重继承的情况下能以最高效率取到虚函数表。

  • 当vptr位于对象内存最前面时,对象的地址即为虚函数指针地址。我们可以取得虚函数指针的地址:

    Base b(1000);
    int *vptrAddr = (int *)(&b);   //强行把类对象的地址转换为 int* 类型,取得了虚函数指针的地址。
    
  • 虚函数指针指向虚函数表,虚函数表中存储的是一系列虚函数的地址,虚函数地址出现的顺序与类中虚函数声明的顺序一致。

  • 对虚函数指针地址值,可以得到虚函数表的地址,也即是虚函数表第一个虚函数的地址:

    typedef void(*Fun)(void);
    Fun vfunc = (Fun)*( (int *)*(int*)(&b));
    /*
    取出虚函数表指针的值: *(int*)(&b);      它是一个地址,虚函数表的地址
    把虚函数表的地址强制转换成 int* :(int *)*(int*)(&b);
    再把它转化成我们Fun指针类型 : (Fun)*(int*)*(int*)(&b)
    */
    cout << "第一个虚函数的地址是:" << (int *)*(int*)(&b) << endl;
    cout << "通过地址,调用虚函数Base::print():";
    vfunc();
    

3、C++模型中加入单继承

class Base
{
public:
    Base(int i) :baseI(i){};
    virtual void print(void){ cout << "调用了虚函数Base::print()"; }
    //virtual void setI(){cout<<"调用了虚函数Base::setI()";}
    virtual ~Base(){}
private:
    int baseI;
};
class Derive : public Base
{
public:
    Derive(int d) :Base(1000),      DeriveI(d){};
    //override父类虚函数
    virtual void print(void){ cout << "Drive::Drive_print()" ; }
    // Derive声明的新的虚函数
    virtual void Drive_print(){ cout << "Drive::Drive_print()" ; }
    virtual ~Derive(){}
private:
    int DeriveI;
};

简单继承下有重写的C++对象模型: 无重写的话,在子类虚函数表中是不会覆盖父类虚函数的。

继承类图为

4、C++模型中加入多继承

4.1 一般的多重继承(非菱形继承)

从单继承可以知道,派生类中只是扩充了基类的虚函数表。如果是多继承的话,又是如何扩充的?

  • ① 每个基类都有自己的虚表
  • ② 子类的虚函数被放到了第一个基类的虚函数表中。
  • ③ 内存布局中,其父类布局依次按声明顺序排列。
  • ④ 每个基类的虚表中的print()函数都被overwrite成了子类的print ()。这样做就是为了解决不同的基类类型的指针指向同一个子类实例,而能够调用到实际的函数。
class Base
{
public:
 
    Base(int i) :baseI(i){};
    virtual ~Base(){}
 
    int getI(){ return baseI; }
 
    static void countI(){};
 
    virtual void print(void){ cout << "Base::print()"; }
 
private:
 
    int baseI;
 
    static int baseS;
};
class Base_2
{
public:
    Base_2(int i) :base2I(i){};

    virtual ~Base_2(){}

    int getI(){ return base2I; }

    static void countI(){};

    virtual void print(void){ cout << "Base_2::print()"; }
 
private:
 
    int base2I;
 
    static int base2S;
};
 
class Drive_multyBase :public Base, public Base_2
{
public:

    Drive_multyBase(int d) :Base(1000), Base_2(2000) ,Drive_multyBaseI(d){};
 
    virtual void print(void){ cout << "Drive_multyBase::print" ; }
 
    virtual void Drive_print(){ cout << "Drive_multyBase::Drive_print" ; }
 
private:
    int Drive_multyBaseI;
};

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-BcdzbFOm-1624582513591)(H:\Xmind共享\C++\入门\C++对象模型.assets\image-20210528101015496.png)]

4.2 菱形继承

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wRNOyCnF-1624582513594)(H:\Xmind共享\C++\入门\C++对象模型.assets\image-20210528101444074.png)]

D类对象内存布局中:

  • 图中青色表示b1类子对象实例,黄色表示b2类子对象实例,灰色表示D类子对象实例。

  • 由于D类间接继承了B类两次,导致D类对象中含有两个B类的数据成员ib,一个属于来源B1类,一个来源B2类。这样不仅增大了空间,更重要的是引起了程序歧义:

    D d;
    d.ib = 1;    //二义错误,调用的是B1::ib还是B2::ib?
    
    //正确调用方式
    d.B1::ib = 1;
    d.B2::ib = 1;
    

5、C++模型中加入虚继承

什么是虚继承

//类的内容与前面相同
class B{...}
class B1 : virtual public B

虚继承是为了解决 重复继承中多个间接父类 的问题的,所以不能使用上面简单的扩充并为每个虚基类提供一个虚函数指针(这样会导致重复继承的基类会有多个虚函数表)形式。

虚继承的派生类的内存结构,和普通继承完全不同:

  • 虚继承的子类,有单独的虚函数表,另外也单独保存一份父类的虚函数表,两部分之间用一个四个字节的0x00000000来作为分界
  • 派生类的内存中,首先是自己的虚函数表,然后是派生类的数据成员,然后是0x0,之后就是基类的虚函数表,之后是基类的数据成员。
  • 如果派生类没有自己的虚函数,那么派生类就不会有虚函数表,但是派生类数据和基类数据之间,还是需要0x0来间隔。

总结

  • 因此,在虚继承中,派生类和基类的数据,是完全间隔的,先存放派生类自己的虚函数表和数据,中间以 0x0 分界,最后保存基类的虚函数和数据。
  • 如果派生类重载了父类的虚函数,那么则将派生类内存中基类虚函数表的相应函数替换
  • 在C++对象模型中,虚继承而来的子类会生成一个隐藏的虚基类指针(vbptr)
  • 因而,对某个类实例来说,如果它有虚基类指针,那么虚基类指针可能在实例的0字节偏移处(该类没有vptr时,vbptr就处于类实例内存布局的最前面,否则vptr处于类实例内存布局的最前面),也可能在类实例的4字节偏移处。

5.1 简单虚继承

类图如下:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-bYb0bFDV-1624582513596)(H:\Xmind共享\C++\入门\C++对象模型.assets\image-20210528103814876.png)]

子类对象模型如下:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-YLgSeCRy-1624582513599)(H:\Xmind共享\C++\入门\C++对象模型.assets\image-20210528103931087.png)]

5.2 菱形虚继承

类图如下:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-pbN6POQi-1624582513601)(H:\Xmind共享\C++\入门\C++对象模型.assets\image-20210528104351682.png)]
菱形虚拟继承下,最派生类D类的对象模型又有不同的构成了。在D类对象的内存构成上,有以下几点:

  • 在D类对象内存中,基类出现的顺序是:先是B1(最左父类),然后是B2(次左父类),最后是B(虚祖父类)
  • D类对象的数据成员id放在B类前面,两部分数据依旧以0来分隔。
  • 编译器没有为D类生成一个它自己的vptr,而是覆盖并扩展了最左父类的虚基类表,与简单继承的对象模型相同。
  • 超类B的内容放到了D类对象内存布局的最后。

菱形虚拟继承下的C++对象模型为:

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

C++对象模型(整理) 的相关文章

随机推荐

  • 小程序input输入限制小数位数

    小程序input组件本身没有自带这个校验属性 xff0c 但有一个maxlength属性 xff0c 可以通过是否输入了小数点来动态计算设置maxlength的方法达到限制输入的目的 保留一位小数 lt view class 61 34 l
  • js数组对象去重

    removeID span class token operator 61 span span class token punctuation span arr span class token punctuation span span
  • 解决echarts刷新不重绘

    切换筛选条件重新查询时候Echart不重新绘制 需要绘制之前初始化Echart echarts span class token punctuation span span class token function init span sp
  • 判断是否有值,0也是有值的情况

    span class token operator span span class token function isNaN span span class token punctuation span span class token f
  • 基于高德地图SDK进行搜索

    高德地图SDK使用地址http lbs amap com 地图设置 define GDMAPKEY 64 34 key 34 import 34 ViewController h 34 import lt MapKit MapKit h g
  • Microsoft Visual C++ Build Tools.exe安装包损坏

    Python3安装支持库的过程中经常会遇到 Microsoft Visual C 14 0 is required 此时就需要安装Visual C build tools生成工具 在运行build tool安装时 提示安装包损坏 翻墙也无效
  • debian图形界面安装

    安装GNOME中文桌面环境 安装基本的X系统 apt get install x window system core 安装GNOME桌面环境 apt get install gnome 到现在为止 xff0c 我们已成功安装完成gnome
  • Qt 调试时 程序异常结束

    在调试时 xff0c 关闭窗口 xff0c 应用程序输出窗口提示 Qt 调试时 程序异常结束 21 20 48 程序异常结束 21 20 48 The process was ended forcefully 21 20 48 G proj
  • c#webservice的简单示例

    是webservice 就概念上来说 xff0c 可能比较复杂 xff0c 不过我们可以有个宏观的了解 xff1a webservice就是个对外的接口 xff0c 里面有 函数可供外部客户调用 xff08 注意 xff1a 里面同样有客户
  • 实现常规厂家&品牌&型号业务对接物联网平台(snack3加json赋能)

    前言 之前介绍过通过snack3快速对接物模型 xff0c 不知道大家还有没有影响 记得还留了一个作业给大家想想 xff0c 就是这么兼容多型号 多版本 xff0c 这次就来跟大家分享下这么集成多型号 一 物模型文件调整 上次是利用snac
  • 组合OSS服务实现打包业务文件zip下载

    前言 实现文件打包成zip下载 xff0c 支持zip包含目录 文件 废话不多说 xff0c 直接上码 一 设计思路 后端组织文件 xff0c 打包成zip上传到OSS存储返回文件名称给前端前端根据返回的文件名称 xff08 url xff
  • Spring Boot + Disruptor = 王炸!!

    01 背景 工作中遇到项目使用Disruptor做消息队列 对你没看错 不是Kafka 也不是rabbitmq Disruptor有个最大的优点就是快 还有一点它是开源的哦 下面做个简单的记录 02 Disruptor介绍 Disrupto
  • Dbeaver连接ES问题一站解决

    前言 最近几天一直做ES的TPS测试 xff0c 每次看数据ES的数据都在嫌麻烦 xff08 在postman指定索引通过url请求查看数据 xff09 最后决定还是整整Dbeaver连接ES 一 当前境况 1 ES版本比较老 xff0c
  • Dbeaver连接TDengine时序数据库

    前言 还是结合上一阶段的工作 xff0c 为TPS满足合同里的要求 xff0c 预研数据库切换为TDengine 所以查看数据的工具我得能连上去看 xff0c 习惯了Dbeaver xff0c 所以先把Dbeaver整的能连接使用 一 Db
  • idea+ApifoxUploader+Apifox真是内外双修,香

    前言 最近部门为整合后端组 前端组 测试组 需求组 产品组等组之间的工作流程 xff0c 旨在提高协调与高效 xff0c 其中之一就是希望开发组 xff08 后端 前端 xff09 开发的接口能及时更新 xff0c 测试组能做接口测试 xf
  • SpringBoot3+最新MybatisPlus+Mysql与TDengine双数据源

    前言 昨天写的idea 43 Apifox uploader插件 43 apifox新年第一天上班就上榜了 xff0c 真是不错 今天来补一篇 xff0c 本来应该是在前一篇之前发的 实际上就是最新的springBoot集成最新的mybat
  • 业务平台扩展支持TDengine时序数据库方案

    1 场景与架构 1 1业务架构 这里涉及项目隐私 xff0c 架构图不方便公开 大致情况就是 xff1a 应用层的园区畅行 生态宜居 安全守护是我方要交付的系统 平台层的物联网感知中台是我方平台 1 2数据架构 从数据架构看 xff0c 园
  • 物联网平台+业务平台基本架构设计与优化想法

    前言 目前的交付底座有点老 xff0c 而且集成的有点杂 xff0c 计划是要升级下 xff0c 先说想法 xff0c 看领导做不做 1 业务平台定位 我们的愿景 xff1a 通过物联平台赋能 xff0c 让数据产生价值 为客户提供可视化的
  • Qt 正则表达式匹配失败的一个原因

    在Qt中做正则表达式时 xff0c 遇到一个很坑爹的问题 xff0c 还是经验不足导致 在正则表达式中 xff0c 有很多需要元字符 xff0c 是需要使用普通字符加转义符号搭配使用的 比如 w xff0c s 对于这类字符 xff0c 在
  • C++对象模型(整理)

    C 43 43 对象模型 1 何为C 43 43 对象模型 xff1f C 43 43 对象模型可以概括为以下2部分 xff1a 语言中直接支持面向对象程序设计的部分 面向对象程序设计部分 xff1a 如构造函数 析构函数 虚函数 继承 x