菱形继承原理

在C++中,菱形继承的内存模型会因是否使用虚继承产生本质差异。我们通过具体示例说明两种场景的区别:


一、普通菱形继承的内存模型

class A { int a; };
class B : public A { int b; };
class C : public A { int c; };
class D : public B, public C { int d; };

内存布局特点:

|-------------------|
| B::A::a (4字节)   |
| B::b (4字节)      |
|-------------------|
| C::A::a (4字节)   |
| C::c (4字节)      |
|-------------------|
| D::d (4字节)      |
|-------------------|

关键问题:

  1. 冗余存储:派生类D包含两份A的成员变量(B::A::a 和 C::A::a)
  2. 访问二义性d.a 需要明确指定路径(d.B::ad.C::a

二、虚继承后的内存模型

class A { int a; };
class B : virtual public A { int b; };
class C : virtual public A { int c; };
class D : public B, public C { int d; };

典型内存布局(以GCC为例):

|-------------------|
| B::vbptr (8字节*) | ➝ 虚基类表,记录A的偏移量
| B::b (4字节)      |
|-------------------|
| C::vbptr (8字节*) | ➝ 同样指向A的偏移量
| C::c (4字节)      |
|-------------------|
| D::d (4字节)      |
|-------------------|
| A::a (4字节)      | ← 唯一一份A的成员
| Padding (4字节)   | (对齐填充)
|-------------------|

核心变化:

  1. 共享基类:虚基类A的成员a在D中只有一份
  2. 间接访问:通过虚基类指针(vbptr)定位共享的A实例
  3. 初始化责任:D的构造函数直接初始化A

三、关键差异对比

特征普通继承虚继承
基类冗余存储存在两份A共享唯一A实例
派生类大小较大(含重复数据)较小但含指针开销
访问基类成员直接访问通过虚基类表间接访问
初始化方式中间类负责初始化最终派生类负责初始化

四、验证示例

#include <iostream>
using namespace std;class A { public: int a; };
class B : virtual public A { public: int b; };
class C : virtual public A { public: int c; };
class D : public B, public C { public: int d; };int main() {D d;d.B::a = 1;  // 虚继承后,修改的是同一份A::ad.C::a = 2;  cout << d.B::a;  // 输出2,证明A是共享的
}

五、注意:在虚继承情况下,虚基类的构造由最底层的派生类直接负责,而不是由中间的基类来构造的。

六、典型应用

在C++标准库中,经典的虚继承解决菱形继承的案例体现在输入输出流(iostream)库的实现中。以下是具体分析:


标准库中的流类继承体系
            basic_ios<...>↑     ↑虚|     ||     |basic_istream<...>  basic_ostream<...>↖       ↗basic_iostream<...>
关键结构解析
  1. **基类 **basic_ios
    所有流类的公共基类,负责管理流的状态(如错误标志、格式化设置等)。
  2. **中间派生类 basic_istream 和 **basic_ostream
    • basic_istream(输入流)通过虚继承派生自 basic_ios
    • basic_ostream(输出流)通过虚继承派生自 basic_ios
  3. **最终派生类 **basic_iostream
    同时继承 basic_istreambasic_ostream,需确保 basic_ios 仅存在一份实例。

虚继承的作用
  • 避免菱形继承的二义性
    basic_istreambasic_ostream 未虚继承 basic_ios,则 basic_iostream 将包含两个独立的 basic_ios 实例,导致访问公共成员(如 good()setf())时出现二义性。
  • 确保单一共享基类
    通过虚继承,basic_iostream 仅保留一个 basic_ios 实例,避免冗余存储和成员冲突。

验证虚继承的示例
#include <iostream>int main() {std::iostream& io = std::cin;  // 合法:std::cin是std::istream&,但向上转型安全io.get();  // 正确调用basic_ios的成员,无二义性return 0;
}
  • 构造顺序
    basic_iostream 的构造函数直接初始化虚基类 basic_ios,确保基类仅构造一次。

标准库实现代码片段(简化)
// 基类
template<typename CharT, typename Traits>
class basic_ios : public ios_base { /*...*/ };// 输入流(虚继承)
template<typename CharT, typename Traits>
class basic_istream : virtual public basic_ios<CharT, Traits> { /*...*/ };// 输出流(虚继承)
template<typename CharT, typename Traits>
class basic_ostream : virtual public basic_ios<CharT, Traits> { /*...*/ };// 最终流
template<typename CharT, typename Traits>
class basic_iostream : public basic_istream<CharT, Traits>,public basic_ostream<CharT, Traits> {
public:// 显式调用虚基类构造函数explicit basic_iostream(/*...*/) : basic_ios<CharT, Traits>(/*...*/),basic_istream<CharT, Traits>(/*...*/),basic_ostream<CharT, Traits>(/*...*/) {}
};

总结

  • 普通菱形继承:基类冗余存储,存在数据冗余和二义性。
  • 虚继承:通过虚基类指针共享唯一基类,牺牲间接访问性能换取空间和语义统一。编译器通过虚基类表(如GCC的vbptr)管理偏移量,确保派生类正确访问共享基类。
  • 最后,尽量不使用菱形继承:
    ● 组合代替继承:将共享功能封装为工具类,通过对象组合调用。
    ● 接口分离:将基类拆分为多个职责单一的接口,避免多重继承。
    ● 依赖注入:通过参数传递依赖对象,而非直接继承。

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

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

相关文章

2025认证杯数学建模第二阶段A题小行星轨迹预测思路+模型+代码

2025认证杯数学建模第二阶段思路模型代码&#xff0c;详细内容见文末名片 一、问题重述 1.1 问题背景 在浩瀚无垠的宇宙中&#xff0c;近地小行星&#xff08;NEAs&#xff09;宛如一颗颗神秘的“太空子弹”&#xff0c;其轨道相对接近地球&#xff0c;给我们的蓝色星球带来…

掌握Docker Commit:轻松创建自定义镜像

使用 docker commit 命令可以通过对现有容器进行修改来创建新的镜像。-a 选项用于指定作者信息&#xff0c;-m 选项用于添加提交信息。以下是具体步骤&#xff1a; 启动并修改容器 启动一个容器并进行必要的修改。例如&#xff0c;启动一个 Ubuntu 容器并安装一些软件包&…

Java虚拟机 - JVM与Java体系结构

Java虚拟机 JVM与Java体系结构为什么要学习JVMJava与JVM简介Java 语言的核心特性JVM&#xff1a;Java 生态的基石JVM的架构模型基于栈的指令集架构&#xff08;Stack-Based&#xff09;基于寄存器的指令集架构&#xff08;Register-Based&#xff09;JVM生命周期 总结 JVM与Jav…

【PostgreSQL系列】PostgreSQL 复制参数详解

&#x1f49d;&#x1f49d;&#x1f49d;欢迎来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 推荐:kwan 的首页,持续学…

阿里巴巴开源移动端多模态LLM工具——MNN

MNN 是一个高效且轻量级的深度学习框架。它支持深度学习模型的推理和训练&#xff0c;并在设备端的推理和训练方面具有行业领先的性能。目前&#xff0c;MNN 已集成到阿里巴巴集团的 30 多个应用中&#xff0c;如淘宝、天猫、优酷、钉钉、闲鱼等&#xff0c;覆盖了直播、短视频…

Vue.js---watch 的实现原理

4.7 watch 的实现原理 watch本质上就是使用了effect以及options.scheduler 定义watch函数&#xff1a; // watch函数:传入参数source以及回调函数function watch(source , cb) {effect(() > source.foo,{scheduler(){// 回调函数cb()}})}watch接收两个参数分别是source和c…

SpringBoot3+AI

玩一下AI 1. SSE协议 我们都知道tcp&#xff0c;ip&#xff0c;http&#xff0c;https&#xff0c;websocket等等协议&#xff0c;今天了解一个新的协议SSE协议&#xff08;Server-Sent Events&#xff09; SSE&#xff08;Server-Sent Events&#xff09; 是一种允许服务器…

vscode中Debug c++

在vscode中Debug ros c程序 1 在Debug模式下编译 如果用命令行catkin_make&#xff0c;在输入catkin_make时加上一个参数&#xff1a; catkin_make -DCMAKE_BUILD_TYPEDebug 或者直接修改CMakelist.txt&#xff0c;添加以下代码&#xff1a; SET(CMAKE_BUILD_TYPE "D…

【ROS2】 核心概念6——通信接口语法(Interfaces)

古月21讲/2.6_通信接口 官方文档&#xff1a;Interfaces — ROS 2 Documentation: Humble documentation 官方接口代码实战&#xff1a;https://docs.ros.org/en/humble/Tutorials/Beginner-Client-Libraries/Single-Package-Define-And-Use-Interface.html ROS 2使用简化的描…

C#里与嵌入式系统W5500网络通讯(2)

在嵌入式代码里,需要从嵌入式的MCU访问W5500芯片。 这个是通过SPI通讯来实现的,所以要先连接SPI的硬件通讯线路。 接着下来,就是怎么样访问这个芯片了。 要访问这个芯片,需要通过SPI来发送数据,而发送数据又要有一定的约定格式, 于是芯片厂商就定义下面的通讯格式: …

SuperYOLO:多模态遥感图像中的超分辨率辅助目标检测之论文阅读

摘要 在遥感影像&#xff08;RSI&#xff09;中&#xff0c;准确且及时地检测包含数十像素的多尺度小目标仍具有挑战性。现有大多数方法主要通过设计复杂的深度神经网络来学习目标与背景的区分特征&#xff0c;常导致计算量过大。本文提出一种兼顾检测精度与计算代价的快速准确…

计算机软件的基本组成

计算机软件的基本组成 一, 计算机软件的分类 软件按其功能分类, 可分为系统软件和应用软件 图解 (1)系统软件 系统软件是一组保证计算机系统高效, 正确运行的基础软件, 软件通常作为系统资源提供给用户使用. 系统软件主要有操作系统(OS), 数据库管理系统(DBMS), 语言处理程…

unity开发游戏实现角色筛选预览

RenderTexture通俗解释 RenderTexture就像是Unity中的"虚拟相机胶片"&#xff0c;它可以&#xff1a; 捕获3D内容&#xff1a;将3D场景或对象"拍照"记录下来 实时更新&#xff1a;不是静态图片&#xff0c;而是动态视频&#xff0c;角色可以动起来 用作…

Spring源码主线全链路拆解:从启动到关闭的完整生命周期

Spring源码主线全链路拆解&#xff1a;从启动到关闭的完整生命周期 一文看懂 Spring 框架从启动到销毁的主线流程&#xff0c;结合原理、源码路径与伪代码三位一体&#xff0c;系统学习 Spring 底层机制。 1. 启动入口与环境准备 原理说明 Spring Boot 应用入口是标准 Java 应…

SAP RF 移动屏幕定制

SAP RF 移动屏幕定制 ITSmobile 是 SAP 当前将移动设备连接到 SAP 系统的技术基础。它基于 SAP Internet Transaction Server (ITS)&#xff0c;从 Netweaver 2004 开始作为 Netweaver 平台的一部分提供。ITSmobile 提供了一个框架&#xff0c;用于为任何 SAP 事务生成基于 HT…

Spark,数据提取和保存

以下是使用 Spark 进行数据提取&#xff08;读取&#xff09;和保存&#xff08;写入&#xff09;的常见场景及代码示例&#xff08;基于 Scala/Java/Python&#xff0c;不含图片操作&#xff09;&#xff1a; 一、数据提取&#xff08;读取&#xff09; 1. 读取文件数据&a…

如何用mockito+junit测试代码

Mockito 是一个流行的 Java 模拟测试框架&#xff0c;用于创建和管理测试中的模拟对象(mock objects)。它可以帮助开发者编写干净、可维护的单元测试&#xff0c;特别是在需要隔离被测组件与其他依赖项时。 目录 核心概念 1. 模拟对象(Mock Objects) 2. 打桩(Stubbing) 3. 验…

最新缺陷检测模型:EPSC-YOLO(YOLOV9改进)

目录 引言:工业缺陷检测的挑战与突破 一、EPSC-YOLO整体架构解析 二、核心模块技术解析 1. EMA多尺度注意力模块:让模型"看得更全面" 2. PyConv金字塔卷积:多尺度特征提取利器 3. CISBA模块:通道-空间注意力再进化 4. Soft-NMS:更智能的重叠框处理 三、实…

【Linux网络与网络编程】12.NAT技术内网穿透代理服务

1. NAT技术 之前我们说到过 IPv4 协议中IP 地址数量不充足的问题可以使用 NAT 技术来解决。还提到过本地主机向公网中的一个服务器发起了一个网络请求&#xff0c;服务器是怎么将应答返回到该本地主机呢&#xff1f;&#xff08;如何进行内网转发&#xff1f;&#xff09; 这就…

uniapp的适配方式

文章目录 前言✅ 一、核心适配方式对比&#x1f4cf; 二、rpx 单位&#xff1a;uni-app 的核心适配机制&#x1f9f1; 三、默认设计稿适配&#xff08;750宽&#xff09;&#x1f501; 四、字体 & 屏幕密度适配&#x1f6e0; 五、特殊平台适配&#xff08;底部安全区、刘海…