詳解C++中的菱形繼承問(wèn)題
一、多重繼承或多繼承
由多個(gè)基類共同派生出新的類,這樣的繼承結(jié)構(gòu)被稱為多重繼承或多繼承。舉個(gè)例子:
#include<iostream> using namespace std; class fuelEngine //燃油引擎 { private: int cylindenum;//汽缸數(shù) public: fuelEngine(int c = 4):cylindenum(c){} ~fuelEngine(){} void start(){} }; class electricEngine//電動(dòng)引擎 { private: float power; public: electricEngine(float p=60):power(p){} ~electricEngine(){} void Start(){} }; class Hybirdcar:public fuelEngine,public electricEngine//混動(dòng)汽車 { public: Hybirdcar(int c,int p):fuelEngine(c),electricEngine(p){} ~Hybirdcar(){} };
混動(dòng)汽車類是由電動(dòng)引擎類和燃油引擎類共同派生出來(lái)的派生類,這種繼承結(jié)構(gòu)就為多繼承。
二、菱形繼承(二義性和數(shù)據(jù)冗余問(wèn)題)
在多繼承結(jié)構(gòu)中,存在著很多問(wèn)題,比如從不同基類中繼承了同名成員,派生類中也定義了同名成員,這種二義性問(wèn)題很好解決,加上要訪問(wèn)的基類的類名限制就可以了,例:
#include<iostream> using namespace std; class Base1 { public: int var; void fun() { cout<<"Member of Base1"<<endl; } }; class Base2 { public: int var; void fun() { cout<<"Member of Base2"<<endl; } }; class Derived:public Base1,public Base2 { public: int var; void fun(){cout<<"Member of Derived"<<endl;} }; int main() { Derived d; Derived *p = &d; //訪問(wèn)Derived類成員 d.var =1; d.fun(); //訪問(wèn)Base1基類成員 d.Base1::var=2; d.Base1::fun(); //訪問(wèn)Base2基類成員 p->Base2::var=3; p->Base2::fun(); return 0; }
而在多繼承中還存在一種特殊情況——菱形繼承。我們還是用一段代碼來(lái)舉例說(shuō)明菱形繼承:
#include<iostream> using namespace std; class Person { private: int _idPerson; public: Person(int id) :_idPerson(id) { cout << "Create Person" << endl; } ~Person(){} }; class Student:public Person//學(xué)生 { private: int _snum; public: Student(int id,int s):Person(id),_snum(s){} ~Student(){} }; class Employee:public Person//職工 { private: int _enum; public: Employee(int id,int e):Person(id),_enum(e){} ~Employee(){} }; class GStudent:public Student//研究生 { private: int _gsnum; public: GStudent(int g,int s,int id):Student(s,id),_gsnum(g){} ~GStudent(){} }; class EGStudent :public GStudent,public Employee//在職研究生 { private: int _egsnum; public: EGStudent(int es,int s,int g,int e,int sid,int eid) :GStudent(s,g,sid),Employee(e,eid),_egsnum(es){} ~EGStudent(){} }; int main() { EGStudent egs(1,2,3,4,5,6); return 0; }
代碼中設(shè)計(jì)了Person類,由Person類派生出學(xué)生Student類和職工Employee類,Student類又向下派生出研究生GStudent類,再由GStudent類和Employee類共同派生出在職研究生EGStudent類,根據(jù)此段代碼的繼承關(guān)系可畫出示意圖 :
不難發(fā)現(xiàn),在菱形繼承中也存在二義性,并且出現(xiàn)數(shù)據(jù)冗余浪費(fèi)了內(nèi)存空間,由基類Person的_idPerson身份證號(hào)有兩條路徑繼承到EGStudent類中,兩個(gè)身份證號(hào)在邏輯上是相同的,但在物理上被分配了不同的內(nèi)存空間,是兩個(gè)變量。
示例中對(duì)象egs的內(nèi)存分布圖如下:
那如何解決這種數(shù)據(jù)冗余和二義性問(wèn)題呢?——虛繼承
在C++中可以把共同基類設(shè)置為虛基類,這樣從不同路徑繼承來(lái)的同名數(shù)據(jù)成員在內(nèi)存中只有一份,虛基類定義方式:class 派生類名:virtual 訪問(wèn)限定符 基類類名{...};或 class 派生類名:訪問(wèn)限定符 virtual 基類類名{...};(virtual關(guān)鍵字只對(duì)緊隨其后的基類名起作用)
在上述示例代碼中,兩個(gè)身份證號(hào)顯然是不合理的,所以我們可以把class Person 設(shè)置成虛基類,即class Student : virtual public Person {...}; 和 class Employee : virtual public Person {...};
菱形繼承就變成了菱形虛擬繼承。修改后代碼如下:
#include<iostream> using namespace std; class Person { private: int _idPerson; public: Person(int id) :_idPerson(id) { cout << "Create Person" << endl; } ~Person(){} }; class Student:public virtual Person//學(xué)生 { private: int _snum; public: Student(int id,int s):Person(id),_snum(s){} ~Student(){} }; class Employee:public virtual Person//職工 { private: int _enum; public: Employee(int id,int e):Person(id),_enum(e){} ~Employee(){} }; class GStudent:public Student//研究生 { private: int _gsnum; public: GStudent(int g,int s,int id):Student(s,id),Person(id),_gsnum(g){} ~GStudent(){} }; class EGStudent :public GStudent,public Employee { private: int _egsnum; public: EGStudent(int es,int s,int g,int e,int id) :GStudent(s,g,id),Employee(e,id),_egsnum(es),Person(id){} ~EGStudent(){} }; int main() { EGStudent egs(1, 2, 3, 4, 5); Person* p = ⪖ return 0; }
三、菱形虛擬繼承
1.虛繼承中派生類對(duì)象構(gòu)造過(guò)程
在派生類對(duì)象的創(chuàng)建過(guò)程中,
首先是虛基類的構(gòu)造函數(shù)按聲明的順序進(jìn)行構(gòu)造,
第二批是非虛基構(gòu)造函數(shù)按聲明順序構(gòu)造,
第三批是成員對(duì)象的構(gòu)造函數(shù),最后是派生類自己的構(gòu)造函數(shù)。
2.菱形虛擬繼承對(duì)象的內(nèi)存分布
其實(shí)是通過(guò)兩個(gè)指針(虛基表指針)指向了一張?zhí)摶?,虛基表中存的是偏移量,通過(guò)偏移量可以找到_idPerson的位置
有了多繼承,就有菱形繼承,有了菱形繼承就有了菱形虛擬繼承,底層實(shí)現(xiàn)很復(fù)雜,一般不建議設(shè)計(jì)出多繼承,否則在復(fù)雜度和性能上可能都會(huì)出現(xiàn)問(wèn)題。
到此這篇關(guān)于詳解C++中的菱形繼承問(wèn)題的文章就介紹到這了,更多相關(guān)C++的菱形繼承內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
C語(yǔ)言線程對(duì)象和線程存儲(chǔ)的實(shí)現(xiàn)
這篇文章主要介紹了C語(yǔ)言線程對(duì)象和線程存儲(chǔ)的實(shí)現(xiàn),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2021-03-03C/C++ 中怎樣使用SetConsoleTextAttribute()函數(shù)來(lái)控制輸出字符的顏色
這篇文章主要介紹了C/C++ 中如何使用SetConsoleTextAttribute()函數(shù)來(lái)控制輸出字符的顏色,本文通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2021-03-03Visual Studio 2022 Preview 使用 C++20 Module的詳細(xì)過(guò)程
這篇文章主要介紹了Visual Studio 2022 Preview 使用 C++20 Module的過(guò)程,本文通過(guò)項(xiàng)目分析實(shí)例代碼相結(jié)合給大家介紹的非常詳細(xì),需要的朋友可以參考下2021-09-09聊聊C語(yǔ)言中sizeof運(yùn)算符的一個(gè)陷阱
在C語(yǔ)言中,sizeof()是一個(gè)判斷數(shù)據(jù)類型或者表達(dá)式長(zhǎng)度的運(yùn)算符,下面這篇文章主要給大家介紹了關(guān)于C語(yǔ)言中sizeof運(yùn)算符的一個(gè)陷阱的相關(guān)資料,文中通過(guò)實(shí)例代碼介紹的非常詳細(xì),需要的朋友可以參考下2022-11-11VS2019安裝配置MFC(安裝vs2019時(shí)沒(méi)有安裝mfc)
這篇文章主要介紹了VS2019安裝配置MFC(安裝vs2019時(shí)沒(méi)有安裝mfc),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-03-03基于Protobuf C++ serialize到char*的實(shí)現(xiàn)方法分析
本篇文章是對(duì)Protobuf C++ serialize到char*的實(shí)現(xiàn)方法進(jìn)行了詳細(xì)的分析介紹。需要的朋友參考下2013-05-05