菱形繼承原理

在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.pswp.cn/news/906086.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/906086.shtml
英文地址,請注明出處:http://en.pswp.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;底部安全區、劉海…