詳解C++中菱形繼承的原理與解決方法
菱形繼承形成原因
多繼承,呈菱形狀

菱形繼承代碼:
class A
{
public:
A() {}
int _a ;
};
class B :public A
{
public:
//會(huì)存在父的int a;
B() {}
int _b ;
};
class C :public A
{
public:
//會(huì)存在父的int a;
C() {}
int _c ;
};
class D :public B,public C
{
public:
//會(huì)存在父的int a; 問題來了這個(gè)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ào)錯(cuò) 提示b.a具有二義性
return 0;
}
出現(xiàn)二義性變量的內(nèi)存布局

可以看到上圖中紫色框兩部分,是出現(xiàn)二義性的兩份A::_a變量,編譯器無法自主確定需要用哪一個(gè),可以d::A_a 或者 d::B_a這樣使用;
應(yīng)對方案
虛繼承 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,這個(gè)2是D類成員通過繼承,使得d自己獨(dú)有且只有一份的 A::_a成員變量;
return 0;
}
解決二義性變量內(nèi)存布局–虛基表

可以看到原先存放兩個(gè)二義性數(shù)據(jù)A::a和B::a的位置,變成了一個(gè)地址?
這個(gè)兩個(gè)地址就叫虛基表指針;
通過對虛基表的進(jìn)一步內(nèi)存研究,發(fā)現(xiàn)了虛基表緊著得下一個(gè)位置存放了一個(gè)偏移量,這個(gè)偏移量是存放該虛基表指針的內(nèi)存位置,與當(dāng)前派生類獨(dú)有一份的成員變量_a之間的偏移長度;
這樣我們直接使用d::a的時(shí)候,因?yàn)楠?dú)有一份,也不會(huì)出現(xiàn)數(shù)據(jù)二義性的問題了;
注意,這和多態(tài)中的虛表(虛函數(shù)表兩回事)
其次,虛基表的指針,經(jīng)過測試,也是某個(gè)多繼承派生類的多對象共用的;
eg: D d1,d2; 其中d1,d2兩個(gè)對象 上圖的這兩張?zhí)摶碇羔樖且粯拥模蚝芎唵?,D類類型都一樣,那么某個(gè)位置起,到另一個(gè)相對位置偏移量肯定是固定的!
(同類型的虛表指針也如此,復(fù)用節(jié)省空間嘛)
感悟
繼承,多態(tài)無疑為我們創(chuàng)造了很多的價(jià)值,但是像菱形繼承這種弊端也是存在的,本質(zhì)是多繼承而引起的問題,在一些語言中禁止了多繼承的行為,總之有利有弊,雖然C++允許了多繼承,但還是盡量別寫多繼承這種模式,復(fù)雜度和出現(xiàn)問題的概率都很大;
關(guān)于代碼復(fù)用等的另一種關(guān)系-組合
繼承是每個(gè)派生類都能相對于繼承is-a的關(guān)系; 每個(gè)派生類對象都是一個(gè)基類對象; 耦合度高
// Car和BMW Car和Benz構(gòu)成is-a的關(guān)系
class Car{
protected:
string _colour ="白色"; // 顏色
string _num ="陜ABITB";// 車牌號
};
class BMW:public Car{
public:
void Drive() {cout <<"好開-操控"<< end1;}
};
class Benz : public Car{
public:
void Drive() {cout <<"好坐-舒適"<< endl;}
};優(yōu)先考慮組合has-a的關(guān)系; eg: 汽車-輪胎,has-a,組合(class 輪胎 作為class 車的成員嵌套) 耦合度低
// Tire和Car構(gòu)成has-a的關(guān)系
class Tiref{
protected:
string _brand = "Michelin";// 品牌
size_t size = 17;// 尺寸
};
class Car{
protected:
string _colour ="白色";// 顏色
string _num ="陜ABITO";// 車牌號
Tire _t;// 輪胎
};到此這篇關(guān)于詳解C++中菱形繼承的原理與解決方法的文章就介紹到這了,更多相關(guān)C++菱形繼承內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
C語言的隨機(jī)數(shù)rand()函數(shù)詳解
這篇文章主要為大家詳細(xì)介紹了C語言的隨機(jī)數(shù)rand()函數(shù),使用數(shù)據(jù)庫,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下,希望能夠給你帶來幫助2022-02-02
C語言獲取Linux系統(tǒng)精確時(shí)間的方法
下面小編就為大家?guī)硪黄狢語言獲取Linux系統(tǒng)精確時(shí)間的方法。小編覺得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2017-09-09
C語言中break與continue的用法和區(qū)別詳解
當(dāng)我們使用while或for循環(huán)時(shí),如果想提前結(jié)束循環(huán)(在不滿足結(jié)束條件的情況下結(jié)束循環(huán)),可以使用break或continue關(guān)鍵字,這篇文章主要給大家介紹了關(guān)于C語言中break與continue的用法和區(qū)別的相關(guān)資料,需要的朋友可以參考下2021-10-10
C++ sdl實(shí)現(xiàn)渲染旋轉(zhuǎn)視頻的方法分享
一般情況下播放視頻時(shí)不需要旋轉(zhuǎn),但是如果是移動(dòng)端錄制的視頻有時(shí)會(huì)出現(xiàn)rotate參數(shù),且視頻寬高也是互換的。所以本文為大家準(zhǔn)備了利用sdl實(shí)現(xiàn)渲染旋轉(zhuǎn)視頻的方法,需要的可以參考一下2022-12-12

