C++类开发的第六篇(虚拟继承实现原理和cl命令的使用的bug修复)

这篇具有很好参考价值的文章主要介绍了C++类开发的第六篇(虚拟继承实现原理和cl命令的使用的bug修复)。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

Class_memory

接上一篇末尾虚拟继承的简单介绍之后,这篇来详细讲一下这个内存大小是怎么分配的。

使用cl

cl 是 Microsoft Visual Studio 中的 C/C++ 编译器命令。通过在命令行中键入 cl 命令,可以调用 Visual Studio 的编译器进行编译操作。cl 命令提供了各种选项和参数,用于指定源文件、编译选项、输出目标等信息,从而进行编译过程。

cl /d1 reportSingleClassLayoutBigBase useclub.cpp这是我们要查看内存使用的命令,具体的语法是

cl /d1 reportSingleClassLayoutSSSS MMMM.cpp

SSSS代表的是你想要查看的类,MMMM就是所要针对的文件名。

这条命令 cl /d1 reportSingleClassLayoutSSSS MMMM.cpp 是使用 Microsoft Visual C++ 编译器的特殊选项来生成有关指定类的单个类布局报告的命令。

具体来说:

  • /d1 是一个编译选项,用于启用或禁用某些特定的编译器功能。
  • reportSingleClassLayoutSSSS 是一个编译器选项,用于指示编译器生成关于类的单个类布局报告。在这里,SSSS 是指要生成报告的类的名称。
  • MMMM.cpp 是包含类定义的源文件。

通过运行这个命令,编译器将会生成关于指定类的单个类布局报告,其中包括类的成员变量在内存中的偏移量、大小等信息。这对于调试和优化代码时了解类的内部布局非常有用。

C++类开发的第六篇(虚拟继承实现原理和cl命令的使用的bug修复)

但是最初我们还没有把cl命令放进环境变量里,所以无法运行。这个是VS自带的一个工具,所以无法运行。这个是VS自带的一个工具,根据型号我们选择的都是X86,找到图里三个文件的位置放进PATH环境里C++类开发的第六篇(虚拟继承实现原理和cl命令的使用的bug修复)

C++类开发的第六篇(虚拟继承实现原理和cl命令的使用的bug修复)

完成这一步之后运行cl命令就没问题了。C++类开发的第六篇(虚拟继承实现原理和cl命令的使用的bug修复)

但是又会出现找不到头文件位置fatal error C1034: iostream: 不包括路径集这种问题是由于头文件也还没有导入到系统变量里面。C++类开发的第六篇(虚拟继承实现原理和cl命令的使用的bug修复)

所以在 Windows 系统中,INCLUDE 环境变量是用于指定 C/C++ 编译器在编译过程中搜索头文件的路径的一个环境变量。当您编译 C/C++ 代码时,编译器会根据 INCLUDE 环境变量中指定的路径去查找要包含的头文件。

通常情况下,INCLUDE 环境变量会包含一系列目录路径,这些路径是编译器用来搜索头文件的位置。如果您使用的是 Visual Studio 或者其他集成开发环境,通常会自动配置好这个环境变量,使得编译器可以顺利地找到所需的头文件。编译器会自动查找 C:\Program Files (x86)\Windows Kits\10\Include 目录中的头文件,因此开发人员无需手动指定这个路径,所以我们把这里的文件都放进去,并且变量名命名为INCLUDE。

C++类开发的第六篇(虚拟继承实现原理和cl命令的使用的bug修复)

此时的问题就变为了下图的新问题fatal error LNK1104: 无法打开文件'libcpmt.lib'

C++类开发的第六篇(虚拟继承实现原理和cl命令的使用的bug修复)

在 Windows 系统中,LIB 环境变量是用于指定编译器在链接过程中搜索库文件的路径的一个环境变量。当您使用编译器链接代码时,编译器会根据 LIB 环境变量中指定的路径去查找要链接的库文件。

类似于 INCLUDE 环境变量用于指定头文件路径,LIB 环境变量用于指定库文件路径。这些库文件包括静态库(.lib)和动态链接库(.dll)等,它们包含了已经编译好的函数和数据结构,可以供程序在运行时调用和使用。

通常情况下,LIB 环境变量会包含一系列目录路径,这些路径是编译器用来搜索库文件的位置。如果您使用的是 Visual Studio 或其他集成开发环境,通常会自动配置好这个环境变量,使得编译器可以顺利地找到所需的库文件。

所以现在再把lib相关的路径也添加进去

C++类开发的第六篇(虚拟继承实现原理和cl命令的使用的bug修复)

C++类开发的第六篇(虚拟继承实现原理和cl命令的使用的bug修复)

这下每个类的内存图就都有了。

使用内存图

类名 普通继承 虚继承
BigBase C++类开发的第六篇(虚拟继承实现原理和cl命令的使用的bug修复) C++类开发的第六篇(虚拟继承实现原理和cl命令的使用的bug修复)
Base1 C++类开发的第六篇(虚拟继承实现原理和cl命令的使用的bug修复) C++类开发的第六篇(虚拟继承实现原理和cl命令的使用的bug修复)
Base2 C++类开发的第六篇(虚拟继承实现原理和cl命令的使用的bug修复) C++类开发的第六篇(虚拟继承实现原理和cl命令的使用的bug修复)
Derived C++类开发的第六篇(虚拟继承实现原理和cl命令的使用的bug修复) C++类开发的第六篇(虚拟继承实现原理和cl命令的使用的bug修复)

因为使用了虚拟继承,所以会涉及到虚表指针(vptr)和虚表(vtable)的处理,以及对齐等问题。在大多数情况下,编译器为了内存对齐的目的会在数据成员后面填充一些字节,以保证存取效率。因此,尽管只有一个成员变量,但实际占用的空间会比较大。

首先注意到一点是上一篇中讲到这里使用sizeof(Derived)得到了24,但是此时的大小是12C++类开发的第六篇(虚拟继承实现原理和cl命令的使用的bug修复)

这两个大小指的是不同的概念。

sizeof(Derived) 表示派生类 Derived 对象所占用的内存大小。在这个例子中,Derived 类包含了一个虚基类 BigBase 的子对象和一些额外的信息,比如虚函数表等。因此,sizeof(Derived) 的结果为 24。

sizeof(class Derived) 表示仅考虑 Derived 类本身所需的内存大小,不包括其继承的部分。在这个例子中,Derived 类本身没有定义任何成员变量,因此 sizeof(class Derived) 的结果为 12,表示只包含了一些额外的信息,比如虚函数表等。

总结起来,sizeof(Derived) 考虑了整个 Derived 对象所需的内存大小,包括继承的部分和额外的信息,而 sizeof(class Derived) 只考虑了 Derived 类本身的大小。

但是我们可以看到在普通继承关系当中,Derived 类同时继承了 Base1Base2 两个类,而这两个类又都直接继承自 BigBase 类。这种多重继承的情况下,如果没有使用虚拟继承(virtual),每个基类会在派生类中各自存在一份实例,导致内存占用增加。

因为 Base1Base2 都直接继承自 BigBase,所以在 Derived 类中将会包含两份 BigBase 的子对象,每份包含一个 int 类型的成员变量 mParam。这就导致了 Derived 对象的大小等于 Base1Base2BigBase 中成员变量的总和,所以大小和类本身一样。

换句话说,每个基类都会在派生类中引入自己的成员变量和函数,而不会共享相同的基类实例。这种情况下,派生类的大小等于各个基类的大小之和,因此大小和类本身一样。

两种继承方式对比着看,

  1. BigBase 菱形最顶层的类,内存布局图没有发生改变。

  2. Base1和Base2通过虚继承的方式派生自BigBase,这两个对象的布局图中可以看出编译器为我们的对象中增加了一个vbptr (virtual base pointer),vbptr指向了一张表,这张表保存了当前的虚指针相对于虚基类的首地址的偏移量。

  3. Derived派生于Base1和Base2,继承了两个基类的vbptr指针,并调整了vbptr与虚基类的首地址的偏移量。

由此可知编译器帮我们做了一些幕后工作,使得这种菱形问题在继承时候能只继承一份数据,并且也解决了二义性的问题。现在模型就变成了Base1和 Base2 Derived三个类对象共享了一份BigBase数据。

当使用虚继承时,虚基类是被共享的,也就是在继承体系中无论被继承多少次,对象内存模型中均只会出现一个虚基类的子对象(这和多继承是完全不同的)。即使共享虚基类,但是必须要有一个类来完成基类的初始化(因为所有的对象都必须被初始化,哪怕是默认的),同时还不能够重复进行初始化,那到底谁应该负责完成初始化呢?C++标准中选择在每一次继承子类中都必须书写初始化语句(因为每一次继承子类可能都会用来定义对象),但是虚基类的初始化是由最后的子类完成,其他的初始化语句都不会调用。

class A {
public:	
	A() {
		cout << "A(): " << endl;
	}
};
class B : virtual public A {
public:
	B() :A() {
		cout << "B():A(): " << endl;
	}
};
class C : virtual public A {
public:
	C() :A() {
		cout << "C():A(): " << endl;
	}
};
class D : public C, public B {
public:
	D() {
		cout << "D() " << endl;
	}
};
void test() {
	D d;
}

C++类开发的第六篇(虚拟继承实现原理和cl命令的使用的bug修复)

这个输出代表了对象的构造顺序。根据输出可以看出:

  1. 首先,类 A 的构造函数被调用,输出 "A(): "。
  2. 接着,类 C 的构造函数被调用,由于 C 类继承了虚基类 A,所以会先调用 A 的构造函数,输出 "C():A(): "。
  3. 然后,类 B 的构造函数被调用,同样会先调用 A 的构造函数,输出 "B():A(): "。
  4. 最后,类 D 的构造函数被调用,由于 D 类同时继承了类 C 和类 B,而这两个类都已经初始化过虚基类 A 的部分,所以在 D 的构造函数中不需要再次调用 A 的构造函数。

由于类D是多重继承体系中的最底层类,它同时继承了类C和类B,而这两个类都间接继承了虚基类A。在这种情况下,编译器会负责确保虚基类A只被初始化一次文章来源地址https://www.toymoban.com/news/detail-837605.html

到了这里,关于C++类开发的第六篇(虚拟继承实现原理和cl命令的使用的bug修复)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • MySQL篇---第六篇

    varchar 与 char 的区别,char 是一种固定长度的类型,varchar 则是一种可变长度的类型。 varchar(30) 中 30 的涵义最多存放 30 个字符。varchar(30) 和 (130) 存储 hello 所占空间一 样,但后者在排序时会消耗更多内存,因为 ORDER BY col 采用 fixed_length 计算 col 长度 (memory 引擎也一样)。 对

    2024年02月08日
    浏览(40)
  • 初识Linux:第六篇

    👉本篇的主要目的:让大家能够使用vim在Linux上写代码和学会在yum上下载软件;👈 在Linux上安装软件有三种方式: 源代码安装 软件的源代码是软件的原始数据,任何人都可以通过源代码查看该软件的设计架构和实现方法,但是 源代码不能在计算机中直接运行安装。需要通过

    2024年02月06日
    浏览(43)
  • Redis篇----第六篇

    前些天发现了一个巨牛的人工智能学习网站,通俗易懂,风趣幽默,忍不住分享一下给大家。点击跳转到网站,这篇文章男女通用,看懂了就去分享给你的码吧。 Redis 提供两种持久化机制 RDB 和 AOF 机制: 1、 RDBRedis DataBase)持久化方式 : 是指用数据集快照的方式半持久化模式

    2024年02月19日
    浏览(40)
  • 第六篇:区块链概述及应用场景

    作者:禅与计算机程序设计艺术 区块链(Blockchain)是一种分布式数据库,用于管理对等网络上交易或数据记录的不可篡改性、透明性和可追溯性,并为用户提供了支付服务、记账本功能、身份认证、存证等多种应用领域。简而言之,区块链是一个去中心化的、共享的、永久

    2024年02月08日
    浏览(40)
  • Java学习手册——第六篇输入输出

    几乎所有的开发语言都会有输入输出,程序的定义里面也有输入输出,可以见得输入输出是何等的重要。如果没有输入,人类如何与计算机交流?如果没有输出,一切努力都是白费,因为我们看不到结果。 这里的输入输出你可以简单的理解为人与人之间的沟通交流,虽然我们

    2024年02月02日
    浏览(46)
  • C++三大特性—继承“复杂的菱形继承及菱形虚拟继承”

    C++的一个大坑:菱形继承 希望这篇文章能让你理解什么是菱形继承,以及菱形继承的注意事项 单继承:一个子类只有一个直接父类时称这个继承关系为单继承 多继承:一个子类有两个或以上直接父类时称这个继承关系为多继承  java语言不支持多继承,只支持单继承。 菱形

    2024年02月03日
    浏览(45)
  • 第六篇从严谨起,谈谈量子计算安全

    作者:禅与计算机程序设计艺术 由于科技的飞速发展,人类也变得越来越“工科化”。因为有了科技的进步,我们终于可以做到这样一个地步——把一切都变成数字。这种全新的数字世界正在引领着我们的生活。而与此同时,随之而来的便是更加复杂、更加迅猛的计算机革命

    2024年02月07日
    浏览(54)
  • 【JAVA基础篇教学】第六篇:Java异常处理

    博主打算从0-1讲解下java基础教学,今天教学第五篇: Java异常处理。 异常处理是Java编程中重要的一部分,它允许开发人员在程序运行时检测和处理各种错误情况,以保证程序的稳定性和可靠性。在Java中,异常被表示为对象,它们是Throwable类的子类。常见的异常包括受检异常

    2024年04月13日
    浏览(41)
  • 第六篇,STM32脉冲宽度调制(PWM)编程

    1.PWM概念 PWM叫脉冲宽度调制(Pulse Width Modulation),通过编程控制输出方波的频率和占空比(高低电平的比例),广泛应用在测量,通信,功率控制等领域(呼吸灯,电机)。     PWM由定时器驱动,PWM周期就是定时器的周期,为了调节占空比,需要在定时器的基础上加上一个比较计

    2023年04月09日
    浏览(41)
  • 【C++历险记】面向对象|菱形继承及菱形虚拟继承

    个人主页:兜里有颗棉花糖💪 欢迎 点赞👍 收藏✨ 留言✉ 加关注💓本文由 兜里有颗棉花糖 原创 收录于专栏【C++之路】💌 本专栏旨在记录C++的学习路线,望对大家有所帮助🙇‍ 希望我们一起努力、成长,共同进步。🍓 单继承:一个子类 只有一个直接父类 时称这个继承

    2024年02月10日
    浏览(42)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包