4. C++ 类的大小

news2024/11/15 19:52:48

C++ 类的大小

​ C++类的大小,是一个比较经典的问题,学过C++后,应该对类大小有清晰的认识,长话短说,本文精简凝练,我们进入正题!!!

1.类的大小与什么有关系?
  • 与类大小有关的因素:普通成员变量,虚函数,继承(单一继承,多重继承,重复继承,虚拟继承)

  • 与类大小无关的因素:静态成员变量,静态成员函数及普通成员函数

2.空类

空类即什么都没有的类,按上面的说法,照理说大小应该是0,但是,空类的大小为1,因为空类可以实例化,实例化必然在内存中占有一个位置,因此,编译器为其优化为一个字节大小。

某类继承自空类:

class base
{
};
class derived:public base
{
private:
    int a;
}

此时,derived类的大小为4,derived类的大小是自身int成员变量的大小,至于为什么没有加上父类base的大小1是因为空白基优化的问题,在空基类被继承后,子类会优化掉基类的1字节的大小,节省了空间大小,提高了运行效率。

3.一般类的大小(注意内存对齐)

首先上两个类的示例:

class base1
{
private:
    char a;
    int b;
    double c;
};

class base2
{
private:
    char a;
    double b;
    int c;
};

虽然上述两个类成员变量都是一个char,一个int,一个double,但是不同的声明顺序,会导致不同的内存构造模型,对于base1,base2,其成员排列是酱紫的:

base1:img

base2:img
base 1类对象的大小为16字节,而base 2类对象的大小为24字节,因为不同的声明顺序,居然造成了8字节的空间差距,因此,我们将来在自己声明类时,一定要注意到内存对齐问题,优化类的对象空间分布。

4.含虚函数的单一继承

首先呈上示意类:(64位,指针大小8字节)

class Base
{
private:
    char a;
public:
    virtual void f();
    virtual void g();
};

class Derived:public Base
{
private:
    int b;
public:
    void f();
};

class Derived1:public Base
{
private:
    double b;
public:
    void g();
    virtual void h();
};

基类Base中含有一个char型成员变量,以及两个虚函数,此时Base类的内存布局如下:

img
​ 内存布局的最一开始是vfptr(virtual function ptr)即虚函数表指针(只要含虚函数,一定有虚函数表指针,而且该指针一定位于类内存模型最前端),接下来是Base类的成员变量,按照在类里的声明顺序排列,当然啦,还是要像上面一样注意内存对齐原则!
继承类Derived继承了基类,重写了Base中的虚函数f(),还添加了自己的成员变量,即int型的b,这时,Derived的类内存模型如下:

img

此种情况下,最一开始的还是虚函数表指针,只不过,在Derived类中被重写的虚函数f()在对应的虚函数表项的Base::f()已经被替换为Derived::f(),接下来是基类的成员变量char a,紧接着是继承类的成员变量int b,按照其基类变量声明顺序与继承类变量声明顺序进行排列,并注意内存对齐问题。
继承类Derived1继承了基类,重写了Base中的虚函数g(),还添加了自己的成员变量(即double型的b)与自己的虚函数(virtual h() ),这时,Derived1的类内存模型如下:

img
​ 此种情况下,Derived1类一开始仍然是虚函数表指针,只是在Derived1类中被重写的虚函数g()在对应的虚函数表项的Base::g()已经被替换为Derived1::g(),新添加的虚函数virtual h()位于虚函数表项的后面,紧跟着基类中最后声明的虚函数表项后,接下来仍然是基类的成员变量,紧接着是继承类的成员变量。

5.含虚函数的多重继承

首先上示意类:

class Base1
{
private:
    char a;
public:
    virtual void f();
    virtual void g1();
};

class Base2
{
private:
    int b;
public:
    virtual void f();
    virtual void g2();
};

class Base3
{
private:
    double c;
public:
    virtual void f();
    virtual void g3();
};

class Derived:public Base1, public Base2, public Base3
{
private:
    double d;
public:
    void f();
    virtual void derived_func();
}

首先继承类多重继承了三个基类,此外继承类重写了三个基类中都有的虚函数virtual f(),还添加了自己特有的虚函数derived_func(),那么,新的继承类内存布局究竟是什么样子的呢?请看下图!先来看3个基类的内存布局:

img
紧接着是继承类Derived的内存布局:

img
​ 首先,Derived类自己的虚函数表指针与其声明继承顺序的第一个基类Base1的虚函数表指针合并此外,若Derived类重写了基类中同名的虚函数,则在三个虚函数表的对应项都应该予以修改,Derived中新添加的虚函数位于第一个虚函数表项后面,Derived中新添加的成员变量位于类的最后面,按其声明顺序与内存对齐原则进行排列。

6.菱形继承的问题及解决方案:虚拟继承

首先在讲这一节之前,先贴出几个重要的信息(干货):

(1)不同环境下虚拟继承对类大小的影响

在vs环境下,采用虚拟继承的继承类会有自己的虚函数表指针(假如基类有虚函数,并且继承类添加了自己新的虚函数)

在gcc环境下及mac下使用clion,采用虚拟继承的继承类没有自己的虚函数表指针(假如基类有虚函数,无论添加自己新的虚函数与否),而是共用父类的虚函数表指针。

(2)虚拟继承会给继承类添加一个虚基类指针(virtual base ptr 简称vbptr),其位于类虚函数指针后面,成员变量前面,若基类没有虚函数,则vbptr其位于继承类的最前端关于虚拟继承,首先我们看看为什么需要虚拟继承及虚拟继承解决的问题。虚拟继承主要是为了解决菱形继承下公共基类的多份拷贝问题:

img

class Base
{
public:
    int a;
}
class Base1:virtual public Base
{
}
class Base2:virtual public Base
{
}
class Derived:public Base1,public Base2
{
private:
    double b;
public:
}

Base1与Base2本身没有任何自身添加的数据成员与虚函数,因此,Base1与Base2都只含有从Base继承来的int a与一个普通的方法,然后Derived又从Base1与Base2继承,这时会导致二义性问题及重复继承下空间浪费的问题:

二义性问题:

Derived de;
de.a=10; //这里是错误的,因为不知道操作的是哪个a

重复继承下空间浪费:Derived重复继承了两次Base中的int a,造成了无端的空间浪费

虚拟继承是怎么解决上述问题的?

虚基继承可以使得上述菱形继承情况下最终的Derived类只含有一个Base类,Base类在虚拟继承后,位于继承类内存布局最后面的位置,继承类通过vbptr寻找基类中的成员及vfptr。

虚拟继承对继承类的内存布局影响可以先看以下示例代码,理解以后,我们在最后列出上述菱形虚拟继承情况下Base1,Base2与Derived代码及内存布局,看到虚拟继承起的作用。

class base
{
public:
    int a
    virtual void f();
}

class derived:virtual public base
{
public:
    double d;
    void f();
}

Derived类内存布局如下图,由于虚拟继承,Derived只会有一个最初基类的拷贝,该拷贝位于类对象模型的最下面,而想要访问到基类的元素,需要vbptr指明基类的位置(vbptr作用),假如Base中含有虚函数,而继承类中没有增添自己的新的虚函数,那么Derived类统一的布局如下:

img
​ 如果添加了自己的新的虚函数(代码如下):

class base
{
public:
    int a
    virtual void f();
}

class derived:virtual public base
{
public:
    double d;
    void f();
    virtual void g();//这是Derived类自己新添加的虚函数
}

**那么Derived在VC下继承类会有自己的虚函数指针,而在Gcc下是共用基类的虚函数指针,其分布如下img
** 现在有了上述代码的理解我们可以写出菱形虚拟继承代码及每个类的内存布局:

class Base
{
public:
    int a;
}
class Base1:public virtual Base
{
}
class Base2:public virtual Base
{
}
class Derived:public Base1,public Base2
{
private:
    double b;
public:
}

img

带实线的框是类确确实实有的,带虚线是针对Base,及Base1,Base2做了扩展后的情况:Base有虚函数,Base1还添加了自己新的虚函数,Base1也有自己成员变量,Base2添加了自己新的虚函数,Base2也有自己成员变量,则上图全部虚线中的部分都将存在于对象内存布局中。

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

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

相关文章

C#,数值计算,解微分方程的龙格-库塔四阶方法与源代码

Carl Runge Martin Wilhelm Kutta 1 龙格-库塔四阶方法 数值分析中,龙格-库塔法(Runge-Kutta)是用于模拟常微分方程的解的重要的一类隐式或显式迭代法。这些技术由数学家卡尔龙格和马丁威尔海姆库塔于1900年左右发明。 对于一阶精度的欧拉公式有: yi+1=yi+h*K1  K1=f(…

Portraiture2024中文版广泛应用于人像处理的磨皮美化插件

Portraiture插件是一款广泛应用于人像处理的磨皮美化插件,尤其在Photoshop和Lightroom等图像编辑软件中备受欢迎。这款插件能够帮助用户快速实现智能磨皮效果,使皮肤看起来更加平滑细腻,同时保留自然纹理和其他重要细节。 Portraiture for Ph…

基于JAVA实现五子棋游戏设计【附项目源码】分享

基于JAVA实现五子棋游戏设计: 项目源码地址:https://download.csdn.net/download/weixin_43894652/88842612 一、引言 五子棋,又称连珠、连五、五目、五目棋等,是一种传统的棋类游戏。本需求文档旨在详细阐述一个基于Java环境开…

LIGHTHOUSE Apex RBP应用案例|汽车涂装行业 电动汽车电池制造行业的颗粒物监测首选

Lighthouse ApexBP汽车制造中的颗粒物监测技术无疑是汽车制造领域的一项革命性发展。它不仅提供了全面、高精度的颗粒检测,而且能够轻松集成到现有的制造流程中,满足自动化需求,加强质量控制,确保电动汽车电池生产的安全性和效率。…

展览厅设计如何创新而独特

一、独特的建筑外观 展览厅的建筑外观是展览的第一印象,因此需要设计一个独特而有吸引力的外观。可以使用独特的建筑形态、创新的材料和结构,以及艺术化的立面设计。 二、灵活的展示空间 创新的展览厅设计应具备灵活的展示空间,以适应不同类型…

java中几种对象存储(文件存储)中间件的介绍

一、前言 在博主得到系统中使用的对象存储主要有OSS(阿里云的对象存储) COS(腾讯云的对象存储)OBS(华为云的对象存储)还有就是MinIO 这些玩意。其实这种东西大差不差,几乎实现方式都是一样&…

StringBuilder --java学习笔记

StringBuilder 代表可变字符串对象,相当于是一个容器,它里面装的字符串是可以改变的,就是用来操作字符串的StringBuilder比String更适合做字符串的修改操作,效率会更高,代码也会更简洁 StringBuilder的常用构造器和方…

单目标/多目标樽海鞘群优化算法——源码

目录 一、樽海鞘群优化算法: 二、多目标樽海鞘群优化算法: 三、代码运行结果: 四、代码下载: 一、樽海鞘群优化算法: 澳大利亚学者Seyedali Mirjalili等人于2017年提出了樽海鞘群算法,该算法源于对海底…

【C++】string类(介绍、常用接口)

🌈个人主页:秦jh__https://blog.csdn.net/qinjh_?spm1010.2135.3001.5343🔥 系列专栏:http://t.csdnimg.cn/eCa5z 目录 string类的常用接口说明 string类对象的常见构造 ​编辑 string字符串的遍历(迭代器&#xf…

攻防演练|某车企攻防小记

前言 专注于web漏洞挖掘、内网渗透、免杀和代码审计,感谢各位师傅的关注!网安之路漫长,与君共勉! 实习期间针对某车企开展的一次攻防演练,过程很曲折,当时的记录没有了只是简单的总结一下。 攻击路径 收…

【掌握版本控制:Git 入门与实践指南】操作仓库文件|分支管理

🎬慕斯主页:修仙—别有洞天 ♈️今日夜电波:泥中に咲く—ウォルピスカーター 0:34━━━━━━️💟──────── 4:46 🔄 ◀️ ⏸ ▶…

【SQL】601. 体育馆的人流量(with as 临时表;id减去row_number()思路)

前述 知识点学习: with as 和临时表的使用12、关于临时表和with as子查询部分 题目描述 leetcode题目:601. 体育馆的人流量 思路 关键:如何确定id是连续的三行或更多行记录 方法一: 多次连表,筛选查询方法二&…

Camtasia2024使用问题、功能特点和更新内容等相关信息

作为软件专家,对于市面上各类软件都有较为深入的了解,下面是关于Camtasia2024的使用问题、功能特点和更新内容等相关信息: Camtasia2024win-安装包下载如下: https://wm.makeding.com/iclk/?zoneid56867 Camtasia2024mac-安装包下载如下: …

基于PyTorch深度学习实战入门系列-(3)Numpy基础下

使用mat创建矩阵 a np.mat([[5, 6], [7, 8]]) b np.mat([[1, 2], [3, 4]]) print(a) print(b) print(type(a)) print(type(b))矩阵的加减乘除运算 data1 np.mat([[1, 2], [3, 4], [5, 6]]) data2 np.mat([1, 2]) data3 np.mat([[5, 6], [7, 8]]) print(data1 data2) prin…

【linux线程(一)】什么是线程?怎样操作线程?

💓博主CSDN主页:杭电码农-NEO💓   ⏩专栏分类:Linux从入门到精通⏪   🚚代码仓库:NEO的学习日记🚚   🌹关注我🫵带你学更多操作系统知识   🔝🔝 Linux线程 1. 前言2. 什么是线…

CFINet

文章目录 AbstractIntroductionContributionsRelated Works锚点细化和区域候选小目标检测的特征模拟目标检测的对比学习MethodTowards Better ProposalsLimitations of Cascade RPNCourse-to-fine RPN(CRPN)Loss Function小目标检测的特征模拟范例特征Feat2Embed ModuleLoss …

20240309web前端_第一周作业_豆瓣电影

作业四&#xff1a;豆瓣电影 成果展示&#xff1a; 完整代码&#xff1a; <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0…

地平线旭日x3派部署yolov5--全流程

地平线旭日x3派部署yolov5--全流程 前言一、深度学习环境安装二、安装docker三、部署3.1、安装工具链镜像3.2、配置天工开物OpenExplorer工具包3.3、创建深度学习虚拟空间&#xff0c;安装依赖&#xff1a;3.4、下载yolov5项目源码并运行3.5、pytorch的pt模型文件转onnx3.6、最…

JavaWeb--Maven

一&#xff1a;概述 1.简介 Maven 是专门用于管理和构建 Java 项目的工具&#xff0c;它的主要功能有&#xff1a; 提供了一套标准化的项目结构 提供了一套标准化的构建流程&#xff08;编译&#xff0c;测试&#xff0c;打包&#xff0c;发布 …… &#xff09; 提供了一套…

这个学习Python的神仙网站,后悔没早点发现

Python 作为时下最流行的编程语言&#xff0c;很多初学者都将它作为自学编程的首选。不管是有编程经验的开发者&#xff0c;还是新手小白&#xff0c;在这个 AIGC 时代&#xff0c; Python 都可以带你探索新世界。 入门 Python 绝非难事&#xff0c;但如何让自己坚持学下去是如…