C++菱形繼承和虛繼承的實(shí)現(xiàn)
菱形繼承和虛繼承本身就是一個(gè)"bug",甚至在C++程序員當(dāng)中有"誰用誰尚阿比"的說法。至于為什么要談菱形繼承和虛繼承,那就是因?yàn)槊嬖嚬僖獑枴?/p>
1.什么是菱形繼承和虛繼承
C++作為"第一個(gè)吃螃蟹的人",勇敢地設(shè)計(jì)出了多繼承的語法,多繼承出現(xiàn)之后,由于一些頂尖程序員的腦洞非常大,就發(fā)現(xiàn)了菱形繼承所帶來數(shù)據(jù)冗余和二義性的問題,C++標(biāo)準(zhǔn)委員會(huì)為了解決這個(gè)問題,就設(shè)計(jì)出了虛繼承。從此之后,后面"抄作業(yè)的人"就沒有多繼承的語法,例如java。
2.菱形繼承所帶來的問題
先理解一段簡單的代碼:
/*B、C繼承自A---D繼承自B、C *從而構(gòu)成菱形繼承*/ class A { public: int _a; }; class B : public A { public: int _b; }; class C : public A { public: int _c; }; class D : public B, public C { public: int _d; }; int main() { D d; //d._a = 3; // 報(bào)錯(cuò),_a不明確 d.B::_a = 3; d.C::_a = 8; return 0; }
這段代碼的調(diào)試結(jié)果為:
這就很好解釋了二義性的問題,因?yàn)樵贒類對象當(dāng)中存在了兩份A類對象,所以要訪問D類對象中的A類對象時(shí)必須指明訪問,否則就會(huì)觸發(fā)二義性。如果在某些應(yīng)用場景中,兩份A類對象確實(shí)是多余的,那么就又觸發(fā)了數(shù)據(jù)冗余問題。所以菱形繼承存在數(shù)據(jù)冗余和二義性的問題。下面給出這段程序的繼承關(guān)系示意圖和D類對象模型示意圖:
3.虛繼承的解決方案
在介紹如何解決菱形繼承的問題之前,先理解一段簡單的虛擬單繼承的代碼:
class A { public: int _a = 1; }; class B : virtual public A // virtual為虛繼承關(guān)鍵字 { public: int _b = 2; }; int main() { B b; return 0; }
調(diào)試-內(nèi)存窗口截圖如下:
如上圖所示,B類對象中的A類對象不再存儲(chǔ)成員變量,而是存儲(chǔ)一個(gè)未知值,這個(gè)位置本應(yīng)該存儲(chǔ)A類對象的成員變量,但是A類的成員變量卻跑到了B類對象的最后。如此類推,如果再有一個(gè)C類虛繼承自A類,那么C類對象模型也應(yīng)該像上圖一樣。
解決菱形繼承的方案就是在繼承體系的"腰部"使用虛繼承,以下面這段代碼為例:
class A { public: int _a = 1; }; class B : virtual public A { public: int _b = 2; }; class C : virtual public A { public: int _c = 3; }; class D : public B, public C { public: int _d = 4; }; int main() { D d; /*都不報(bào)錯(cuò)了,他們操作的都是同一個(gè)_a*/ d._a = 1; d.B::_a = 3; d.C::_a = 8; return 0; }
最終調(diào)試的結(jié)果如下:
不要被監(jiān)視窗口所誤導(dǎo),上圖三個(gè)紅色箭頭所指向的_a實(shí)際上是同一個(gè)_a,也就是說D類對象的模型當(dāng)中只存在一份A類對象了。
通過內(nèi)存窗口觀察D類對象的模型:
與之前介紹的一樣,B類對象和C類對象當(dāng)中本該存儲(chǔ)A類對象的位置存儲(chǔ)了一個(gè)隨機(jī)值。實(shí)際上這個(gè)隨機(jī)值是一個(gè)指針,它指向了虛基表。
3.1虛基表
對于上面的圖片,介紹了所謂的"隨機(jī)值"是指針,指向了一個(gè)名為虛基表的東西,那么再另起一個(gè)內(nèi)存窗口,觀察虛基表的構(gòu)成:
由此可見,虛基表存儲(chǔ)的有效內(nèi)容為偏移量,具體的來說,當(dāng)某一指針或引用指向D類對象時(shí),需要訪問_a時(shí),就需要通過虛基表當(dāng)中的偏移量來確定訪問目標(biāo)的位置。雖然虛基表的存在增加了幾次指針的運(yùn)算,但是試想以下,如果A類對象足夠大,在菱形繼承體系中不使用虛繼承,那么最終的D類對象就會(huì)有兩份A類對象,并且A類對象是一個(gè)巨大的對象,那么如果使用了虛繼承,就能將兩份A類對象壓縮成一份A類對象。
所以使用虛繼承,能夠解決菱形繼承帶來的數(shù)據(jù)冗余和二義性問題。最后以一張圖描述D類對象的模型:
4.繼承與組合
組合的類設(shè)計(jì)方式是這樣的:
class A { public: int _a; }; class B { public: A a; };
可以明顯看出與繼承的差別:組合的耦合度更低,繼承的耦合度更高。實(shí)際上在真實(shí)的設(shè)計(jì)環(huán)境當(dāng)中是很忌諱高耦合的,但是某些場景當(dāng)中卻不得不這么做。
繼承是一種is-a的關(guān)系,例如下面這個(gè)例子:
class Person {}; class Student : public Person {};
這個(gè)例子所表達(dá)的意思就是Student是Person,即學(xué)生是人。
組合是一種has-a的關(guān)系,例如最開頭的那段代碼,表達(dá)的意思就是B類對象當(dāng)中有一個(gè)A類對象。
針對不同的場景使用不同的復(fù)用手段,當(dāng)條件只允許使用is-a的關(guān)系時(shí)就使用繼承;只允許使用has-a的關(guān)系時(shí)就使用組合;當(dāng)既可以使用繼承又可以使用組合的關(guān)系時(shí)使用組合。
為什么要盡量使用組合關(guān)系?
因?yàn)閷τ诶^承來說,它相當(dāng)于一種白箱復(fù)用,即箱子里面的內(nèi)容能夠清清楚楚的看到;對于組合來說,它相當(dāng)于一種黑箱復(fù)用,即箱子里面的內(nèi)容大多是不可見的,能夠看見的也僅僅是一部分(例如設(shè)計(jì)類時(shí)提供給外部的成員函數(shù))。對于繼承來說,如果基類的非private成員發(fā)生了變動(dòng),由于耦合度高的原因,派生類也將會(huì)受到影響;對于組合來說,被包含的對象只有public成員發(fā)生變動(dòng)時(shí),才有可能影響到包含該對象的對象。
到此這篇關(guān)于C++菱形繼承和虛繼承的實(shí)現(xiàn)的文章就介紹到這了,更多相關(guān)C++菱形繼承和虛繼承內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
C++實(shí)現(xiàn)一個(gè)簡單的線程池的示例代碼
本文主要介紹了C++實(shí)現(xiàn)一個(gè)簡單的線程池的示例代碼,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2022-05-05詳談全排列next_permutation() 函數(shù)的用法(推薦)
下面小編就為大家?guī)硪黄斦勅帕衝ext_permutation() 函數(shù)的用法(推薦)。小編覺得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2017-03-03c++如何控制輸出浮點(diǎn)數(shù)小數(shù)點(diǎn)后若干位
這篇文章主要介紹了c++如何控制輸出浮點(diǎn)數(shù)小數(shù)點(diǎn)后若干位問題,具有很好的參考價(jià)值,希望對大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-09-09用VC++6.0的控制臺(tái)實(shí)現(xiàn)2048小游戲的程序
本文是作者拜讀劉地同學(xué)的《C語言控制臺(tái)版2048》之后感覺非常不錯(cuò),添加了注釋之后分享給大家的,方便更多的初學(xué)者閱讀學(xué)習(xí),有需要的小伙伴參考下。2015-03-03linux c 獲取本機(jī)公網(wǎng)IP的實(shí)現(xiàn)方法
本篇文章是對在linux中使用c語言獲取本機(jī)公網(wǎng)IP的方法進(jìn)行了詳細(xì)的分析介紹,需要的朋友參考下2013-05-05