1. 程式人生 > >虛繼承詳解及其記憶體分佈

虛繼承詳解及其記憶體分佈

什麼是虛繼承?

根據百度百科:

虛繼承 是面向物件程式設計中的一種技術,是指一個指定的基類,在繼承體系結構中,將其成員資料例項共享給也從這個基型別直接或間接派生的其它類。

虛擬繼承是多重繼承中特有的概念。虛擬基類是為解決多重繼承而出現的。


如上圖:

假設類a是父類,b類和c類都繼承了a類,而d類又繼承了bc,那麼由於d類進行了兩次多重繼承a類,就會出現兩份相同的a的資料成員或成員函式,就會出現程式碼冗餘。同時,我們在d類的例項化物件中呼叫從a類繼承來的資料成員或者成員函式的時候,無法分清是來自於b類或者是c類,會發生編譯錯誤。這也就是我們常說的二義性。

故為了保證虛基類在派生類中只繼承一次,應當在該基類的所有直接派生類中宣告為虛基類,否則仍然會出現對基類的多次繼承。

那麼解決這個問題的關鍵就是虛繼承。

我們直接看程式碼:

#include <cstring>
#include <iostream>
using namespace std;
class Base_A
{
public:
    Base_A():m_base_a("Base A") {}
    virtual ~Base_A() {}
    //~Base_A() {}
    void printA() { cout << this->m_base_a << endl; }
private:
    string m_base_a;
};

class Derived_B :virtual public Base_A
{
public:
    Derived_B():m_derived_b("Derived B") {}
    virtual ~Derived_B() {}
    void printB() { cout << m_derived_b << endl; }
private:
    string m_derived_b;
};

class Derived_C :virtual public Base_A
{
public:
    Derived_C():m_derived_c("Derived C") {}
    virtual~Derived_C() {}
    void printC() { cout << m_derived_c << endl; }
private:
    string m_derived_c;
};

class Derived_D :public Derived_B, public Derived_C
{
public:
    Derived_D():m_derived_d("Derived D") {}
    virtual ~Derived_D() {}
    void printD() { cout << m_derived_d << endl; }
private:
    string m_derived_d;
};

int main()
{
    Derived_D D;

    cout << sizeof(Base_A) << endl;
    cout << sizeof(Derived_B) << endl;
    cout << sizeof(Derived_C) << endl;
    cout << sizeof(Derived_D) << endl;
    return 0;
}

作業系統為MacOS,IDE為Clion。程式碼直接輸出了4個類的記憶體大小。

在這我們要注意一下,在我的程式設計環境下,string型別的大小是24(無論string中是否有內容,內容的多少,均佔24個位元組)。

我們一步步來分析一下數字是怎麼來的。為了方便理解,我們首先看一下沒有虛繼承的情況。以下簡稱4個類為ABCD

若沒有虛繼承:

類A中存在string資料成員,佔據24個位元組,又因虛解構函式的存在,所以有一個虛擬函式表指標,佔8位元組(64bit),24+8=32

類B繼承了類A,同時類B也擁有一個自己的string型別資料成員,同時有一個虛擬函式表指標,共24+24+8=56

類C一樣繼承了類A,同為56

類D同時繼承了類B和類C,即除了自己的string資料成員,還擁有B、C兩個類的資料成員,同時由於是多繼承,那麼類D應該擁有兩個虛擬函式表,即擁有兩個虛擬函式表指標(分別指向B和C的虛擬函式表)。故24(D類自身的資料成員)+24*4(B和C類的資料成員和兩個A類資料成員)+8*2(兩個虛擬函式指標)=136

若有虛繼承:

類A不變,還是32

此時類B繼承了類A,除了資料成員跟虛擬函式表指標以外,額外增添了一個指標,該指標指向了從類A中繼承過來的資料。也成為資料的共享(虛繼承的關鍵)即56+8=64

類C同理,也為64

此時類D繼承C和D,額外再加兩個虛擬函式表指標和一個後來增添的指標(指向共享的資料內容),由於增添的指標指明瞭共享的資料,故此時類D中只包含一個A中的資料,故大小為24(自身資料成員)+24(A中資料成員)+48(B、C中資料成員)+8*3=120

這也就說明了通過引入虛繼承,可以減少記憶體的佔用量。同時,我們在呼叫基類的成員函式的時候,也不會發生二義性的錯誤。編譯器可以自動通過指標,來找到共享的內容。

但是,很多程式設計師不提倡在程式中使用多重繼承,只有在比較簡單和不易出現二義性的情況或實在必要時才使用多重繼承,能用單一繼承解決的問題就不要使用多重繼承。也正由於這個原因,C++之後的很多面向物件的程式語言(如JavaSmalltalkC#PHP等)並不支援多重繼承。

相關推薦

繼承及其記憶體分佈

什麼是虛繼承?根據百度百科:虛繼承 是面向物件程式設計中的一種技術,是指一個指定的基類,在繼承體系結構中,將其成員資料例項共享給也從這個基型別直接或間接派生的其它類。虛擬繼承是多重繼承中特有的概念。虛擬基類是為解決多重繼承而出現的。如上圖:假設類a是父類,b類和c類都繼承了a

C++之函數與繼承

類繼承 file 文件 技術分享 函數表 命令行 .com G1 mes 準備工作 1、VS2012使用命令行選項查看對象的內存布局 微軟的Visual Studio提供給用戶顯示C++對象在內存中的布局的選項:/d1reportSingleClassLayout。使用方法

C++繼承三 ----菱形繼承繼承

今天呢,我們來講講菱形繼承與虛繼承。這兩者的講解是分不開的,要想深入瞭解菱形繼承,你是繞不開虛繼承這一點的。它倆有著什麼關係呢?值得我們來剖析。 菱形繼承也叫鑽石繼承,它是多繼承的一種特殊例項吧,它的基本架構如下圖: 在我們的設想中,D所對應的物件模型應

Storm概念、原理及其應用(一)BaseStorm

when 結構 tails 並發數 vm 虛擬機 cif 異步 優勢 name 本文借鑒官文,添加了一些解釋和看法,其中有些理解,寫的比較粗糙,有問題的地方希望大家指出。寫這篇文章,是想把一些官文和資料中基礎、重點拿出來,能總結出便於大家理解的話語。與大多數“wordc

算法筆記--sg函數及其模板

clas ref http spa for tail details false art sg函數大神詳解:http://blog.csdn.net/luomingjun12315/article/details/45555495 模板: int f[N],SG[N];

Maven及其環境配置

下使用 .html tor update baidu load set mode dex Maven詳解 一.前言 以前做過的項目中,沒有真正的使用過Maven,只知道其名聲很大,其作用是用來管理jar 包的。最近一段時間在項目過程中使用Maven,用Mav

C++中的繼承

C++ 繼承 [TOC] 繼承基本知識 定義:  繼承是面向對復用的重要手段。通過繼承定義一個類,繼承是類型之間的關系建模,共享公有的東西,實現各自本質不同的東西。 繼承關系:  三種繼承關系下基類成員的在派生類的訪問關系變化(圖) 舉個栗子(公有繼承) ```c+

AIX下PVID及其修改方法

AIX下PVID詳解及其修改方法AIX 下 PVID 詳解及其修改方法1.PVID 是什麽PVID 全稱 physical volume identifier,它非常重要,相當於軟序列號,當把一個磁盤變成 PV 時, 就生成了 PVID , PVID 是由機器序列號 (uname -m的前8位 ) 和它生成的

Java中List,Set和Map及其區別

內部 特殊 set contain 快速查找 簡單 rar dset 維護 Java中的集合包括三大類,它們是Set(集)、List(列表)和Map(映射),它們都處於java.util包中,Set、List和Map都是接口,它們有各自的實現類。Set的實現類主要有Hash

python之路 第二篇 數據類型及其方法

字符 引號 print 成員 移除 join att pri str 字符串 #作用:描述名字,性別,國籍,地址等信息#定義:在單引號\雙引號\三引號內,由一串字符組成 name=‘Matthew‘ #優先掌握的操作: #1、按索引取值(正向取+反向取) :只能取 #2

jvm簡介及其記憶體分佈介紹(入門級)

一.jvm執行機制 jvm啟動流程: java虛擬機器啟動的命令是通過java +xxx(類名,這個類中要有main方法)或者javaw啟動的。 執行命令後,系統第一步做的就是裝載配置,會在當前路徑中尋找jvm的config配置檔案。 找到jvm的config

js中的繼承

js中的繼承 假設我們有一個Animal類,我們想構造Cat類,Cat類可以繼承Animal類的屬性和方法。以這個場景為列,我來講一講我所理解的js的繼承。 構造繼承 function Animal(name){ this.name = name; th

希爾排序超及其java實現

希爾排序雖然已經十分古老了,但其思想確實十分值得我們學習,非常的巧妙 雖然很多資料說希爾排序是叫shell的人提出來的,我個人卻十分好奇,shell的英文好意為殼的意思,感覺希爾排序的思想就像一層層的殼一樣,從內到外越來越大,當然這是我胡謅的,具體原理如下(用的一個例項進行說明的): 原理:

介面與繼承

介面繼承是出自設計模式中的一個概念。介面繼承,又稱子型別化。描述了一個物件什麼時候能夠被用來替代另一個物件。 1、介面概念 介面的基本概念 介面可以多繼承介面 ,是因為介面只定義行為,並不包含類別的含義,不指代具體的某類事物, 而且, 從語義上來說 ,介面對介面的繼承稱之為擴充套件更為合

LSTM網路層及其應用例項

上一節我們介紹了RNN網路層的記憶性原理,同時使用了keras框架聽過的SimpleRNN網路層到實際運用中。然而使用的效果並不理想,主要是因為simpleRNN無法應對過長單詞串的輸入,在理論上,當它接收第t個輸入時,它應該能把前面好幾個單詞的處理資訊記錄下來,但實際上它無法把前面已經

JAVA三大特性之 繼承

類和類之間的常見關係。 既然繼承是描述類和類之間的關係,就需要先來了解類和類之間的常見關係 ​​​​​​​現實生活的整體與部分 舉例說明 現實生活 學生   是人   狗     是動物

Android 獲取手機儲存資訊記憶體,外存等)

ActivityManager am = (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE); //系統記憶體資訊 ActivityManager.MemoryInfo memInfo = new ActivityManager

C語言實現八大排序演算法及其效能之間的

概述 排序是資料結構中的重要一節,也是演算法的重要組成部分。主要分為內部排序以及外部排序,今天我們講內部排序,也就是八大排序。 插入排序 直接插入排序 演算法思想 演算

JVM記憶體管理與垃圾回收機制1 - 記憶體管理

Java應用程式是執行在JVM上的,得益於JVM的記憶體管理和垃圾收集機制,開發人員的效率得到了顯著提升,也不容易出現記憶體溢位和洩漏問題。但正是因為開發人員把記憶體的控制權交給了JVM,一旦出現記憶體方面的問題,如果不瞭解JVM的工作原理,將很難排查錯誤。本文將從理論角度介紹虛擬機器的記憶

JVM記憶體管理與垃圾回收機制2 - 何為垃圾

隨著程式語言的發展,GC的功能不斷增強,效能也不斷提高,作為語言背後的無名英雄,GC離我們的工作似乎越來越遠。作為Java程式設計師,對這一點也許會有更深的體會,我們不需要了解太多與GC相關的知識,就能很好的完成工作。那還有必要深入瞭解GC嗎?學習GC的意義在哪兒? 不管效能提高到何種程