C++菱形繼承及解決方法詳解
菱形繼承
繼承是類和類之間的關(guān)系,是代碼復用的重要手段,允許在保持原有類結(jié)構(gòu)的基礎(chǔ)上進行擴展,創(chuàng)建的新類與原有的類類似,只是多了幾個成員變量和成員函數(shù)。
需要注意的是:
1.若不表明是以何種方式繼承,使用關(guān)鍵字class時默認是私有繼承,使用struct關(guān)鍵字時默認公有繼承
2.上面的不可訪問是指派生類對象不管是在類里還是類外都不能不訪問
在談菱形繼承之前,需要先了解單繼承和多繼承:
- 單繼承是一個子類只有一個直接父類時稱這個繼承關(guān)系是單繼承
- 一個子類有兩個或兩個以上的直接父類時稱這個繼承關(guān)系為多繼承
- 繼承中子類中成員的排列次序與繼承次序有關(guān)
- (也就是說先繼承的類,類中成員的地址越靠下)
菱形繼承:
- 兩個子類同時繼承一個父類,而又有子類同時繼承這兩個子類
缺點:
- 導致數(shù)據(jù)冗余和二義性的問題
解決方法:
- 對造成二義性的屬性使用域訪問限定符,這樣從本質(zhì)上并沒有解決二義性
- 想要解決二義性和數(shù)據(jù)冗余,這就需要用到虛擬繼承。屬性只會生成一份,要調(diào)用它需要使用虛基表指針,虛基表指針指向虛基表,虛基表中存在偏移量,通過偏移量就可以找到共有的那個屬性。
封裝,繼承,多態(tài)。這是C++語言的三大特性,而每次在談到繼承時我們不可避免的要談到一個很重要的問題——菱形繼承。
多繼承,呈菱形狀

菱形繼承代碼:
class A
{
public:
A() {}
int _a ;
};
class B :public A
{
public:
//會存在父的int a;
B() {}
int _b ;
};
class C :public A
{
public:
//會存在父的int a;
C() {}
int _c ;
};
class D :public B,public C
{
public:
//會存在父的int a; 問題來了這個a是 父親b中的a 還是 父親c中的a -- 二義性;
//父的int b;
//父的int c;
D() {}
int _d ;
};
int main()
{
D d;
d.B::_a = 1;
d.C::_a = 2;
d._b = 3;
d._c = 4;
b._d = 5;
cout << d._a << endl;//報錯 提示b.a具有二義性
return 0;
}出現(xiàn)二義性變量的內(nèi)存布局

可以看到上圖中紫色框兩部分,是出現(xiàn)二義性的兩份A::_a變量,編譯器無法自主確定需要用哪一個,可以d::A_a 或者 d::B_a這樣使用;
應對方案
虛繼承 vitrual
vitrual修飾使派生類出現(xiàn)二義性的父類繼承部分(菱形的腰部)
class A
{
public:
A() {}
int _a ;
};
class B :virtual public A //virtual修飾
{
public:
//父的int a;
B() {}
int _b;
};
class C :virtual public A //virtual修飾
{
public:
//父的int a;
C() {}
int _c ;
};
class D :public B,public C
{
public:
//自己的 int a;(二義性的父親繼承經(jīng)virtual修飾)
//父的int b;
//父的int c;
D() {}
int _d;
};
int main()
{
D d;
d.B::_a = 1;
d.C::_a = 2;
d._b = 3;
d._c = 4;
b._d = 5;
cout << d._a << endl;//沒問題,輸出2,這個2是D類成員通過繼承,使得d自己獨有且只有一份的 A::_a成員變量;
return 0;
}解決二義性變量內(nèi)存布局–虛基表

可以看到原先存放兩個二義性數(shù)據(jù)A::a和B::a的位置,變成了一個地址?
這個兩個地址就叫虛基表指針;
通過對虛基表的進一步內(nèi)存研究,發(fā)現(xiàn)了虛基表緊著得下一個位置存放了一個偏移量,這個偏移量是存放該虛基表指針的內(nèi)存位置,與當前派生類獨有一份的成員變量_a之間的偏移長度;
這樣我們直接使用d::a的時候,因為獨有一份,也不會出現(xiàn)數(shù)據(jù)二義性的問題了;
注意,這和多態(tài)中的虛表(虛函數(shù)表兩回事)
其次,虛基表的指針,經(jīng)過測試,也是某個多繼承派生類的多對象共用的;
eg: D d1,d2; 其中d1,d2兩個對象 上圖的這兩張?zhí)摶碇羔樖且粯拥?,原因很簡單,D類類型都一樣,那么某個位置起,到另一個相對位置偏移量肯定是固定的!
(同類型的虛表指針也如此,復用節(jié)省空間嘛)
感悟
繼承,多態(tài)無疑為我們創(chuàng)造了很多的價值,但是像菱形繼承這種弊端也是存在的,本質(zhì)是多繼承而引起的問題,在一些語言禁止了多繼承的行為,總之有利有弊,雖然C++允許了多繼承,但還是盡量別寫多繼承這種模式,復雜度和出現(xiàn)問題的概率都很大;
關(guān)于代碼復用等的另一種關(guān)系-組合
繼承是每個派生類都能相對于繼承is-a的關(guān)系; 每個派生類對象都是一個基類對象; 耦合度高

優(yōu)先考慮組合has-a的關(guān)系; eg: 汽車-輪胎,has-a,組合(class 輪胎 作為class 車的成員嵌套) 耦合度低

到此這篇關(guān)于C++菱形繼承及解決方法詳解的文章就介紹到這了,更多相關(guān)C++菱形繼承內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
這篇文章主要介紹了C++強制轉(zhuǎn)換與智能指針示例,智能指針(Smart Pointer)是一種抽象的數(shù)據(jù)類型。在程序設(shè)計中,它通常是經(jīng)由類模板來實現(xiàn),借由模板來達成泛型,借由類別的析構(gòu)函數(shù)來達成自動釋放指針所指向的存儲器或?qū)ο?/div> 2022-11-11
C++實現(xiàn)LeetCode(156.二叉樹的上下顛倒)
這篇文章主要介紹了C++實現(xiàn)LeetCode(156.二叉樹的上下顛倒),本篇文章通過簡要的案例,講解了該項技術(shù)的了解與使用,以下就是詳細內(nèi)容,需要的朋友可以參考下2021-07-07最新評論

