詳解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)二義性變量的內存布局
可以看到上圖中紫色框兩部分,是出現(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;(二義性的父親繼承經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; }
解決二義性變量內存布局–虛基表
可以看到原先存放兩個二義性數(shù)據(jù)A::a和B::a的位置,變成了一個地址?
這個兩個地址就叫虛基表指針;
通過對虛基表的進一步內存研究,發(fā)現(xiàn)了虛基表緊著得下一個位置存放了一個偏移量,這個偏移量是存放該虛基表指針的內存位置,與當前派生類獨有一份的成員變量_a之間的偏移長度;
這樣我們直接使用d::a的時候,因為獨有一份,也不會出現(xiàn)數(shù)據(jù)二義性的問題了;
注意,這和多態(tài)中的虛表(虛函數(shù)表兩回事)
其次,虛基表的指針,經過測試,也是某個多繼承派生類的多對象共用的;
eg: D d1,d2; 其中d1,d2兩個對象 上圖的這兩張?zhí)摶碇羔樖且粯拥模蚝芎唵?,D類類型都一樣,那么某個位置起,到另一個相對位置偏移量肯定是固定的!
(同類型的虛表指針也如此,復用節(jié)省空間嘛)
感悟
繼承,多態(tài)無疑為我們創(chuàng)造了很多的價值,但是像菱形繼承這種弊端也是存在的,本質是多繼承而引起的問題,在一些語言中禁止了多繼承的行為,總之有利有弊,雖然C++允許了多繼承,但還是盡量別寫多繼承這種模式,復雜度和出現(xiàn)問題的概率都很大;
關于代碼復用等的另一種關系-組合
繼承是每個派生類都能相對于繼承is-a的關系; 每個派生類對象都是一個基類對象; 耦合度高
// Car和BMW Car和Benz構成is-a的關系 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的關系; eg: 汽車-輪胎,has-a,組合(class 輪胎 作為class 車的成員嵌套) 耦合度低
// Tire和Car構成has-a的關系 class Tiref{ protected: string _brand = "Michelin";// 品牌 size_t size = 17;// 尺寸 }; class Car{ protected: string _colour ="白色";// 顏色 string _num ="陜ABITO";// 車牌號 Tire _t;// 輪胎 };
到此這篇關于詳解C++中菱形繼承的原理與解決方法的文章就介紹到這了,更多相關C++菱形繼承內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
C++實現(xiàn)LeetCode(101.判斷對稱樹)
這篇文章主要介紹了C++實現(xiàn)LeetCode(101.判斷對稱樹),本篇文章通過簡要的案例,講解了該項技術的了解與使用,以下就是詳細內容,需要的朋友可以參考下2021-07-07