與原來的示例代碼
有許多與你的榜樣問題懷疑的問題。
首先,您不必在基類中提供函數體。改用純虛函數。其次,你的類D1和D2都沒有功能,所以它們應該是抽象的(這會阻止你從它們中創建被剝奪的對象)。如果你真的爲你的基類使用純虛擬函數,這第二個問題將變得清晰。編譯器將開始發出警告。
與D134一樣,將D1例化爲new D1
,這是糟糕的設計,因爲D1沒有setLength方法的真正功能實現,即使您給它一個「虛擬」主體。給它一個'虛擬'身體(一個沒有做任何有用的東西),所以掩蓋了你的設計錯誤。
所以你的評論(但我不喜歡不得不把默認函數放在基類中)證明了一個適當的直覺。必須這樣做的信號有缺陷的設計。 D1對象無法理解setLength,而其繼承的公共接口承諾它可以。
並且:如果使用正確,多繼承沒有任何問題。它非常強大和優雅。但你必須在適當的地方使用它。 D1和D2是B的部分實現,所以抽象,並從兩者繼承將確實給你一個完整的實現,非常具體。
也許一個很好的規則是:只有在看到引人注目的需求時才使用多重繼承。但是,如果你這樣做,這是非常有用的。與例如相比,它可以防止相當醜陋的不對稱和代碼重複。像Java這樣的語言已經禁止了它。
我不是樹醫生。當我使用鏈鋸時,會危害我的腿。但這並不是說電鋸沒有用處。
在哪裏放假人:無處請,不要取消...
[編輯OP的第一個註釋後]
如果你得到的B類D1,將打印的「未實現setLength」如果你調用它的setLength方法,應該如何調用者作何反應?它不應該首先調用它,如果D1不是從具有這種方法的B派生的,那麼調用者可能已經知道這個方法,純粹是虛擬的。那麼很明顯,它不支持這種方法。擁有B基類讓D1感到賓至如歸的元素類型,B *或B &的多態數據結構承諾其用戶正確支持getLength,而不支持getLength。
儘管在你的例子中情況並非如此(但也許你遺漏了一些東西),但當然可以有一個很好的理由從B中推導出D1和D2。B可以保存最終接口的一部分或實現它的派生類D1和D2都需要。
假設B有一個方法setAny(key,value)(設置字典中的一個值),D1和D2都使用,D1在setColor中調用它,D2在setLength中調用它。 在這種情況下,使用通用基類是有道理的。在這種情況下,B不應該有虛擬方法setColor或setLength,既不是虛擬方法也不是純粹方法。你應該只在你的D1類中有一個setColor,在你的D2類中有一個setLength,但在你的B類中都沒有。
有一個在面向對象設計的基本原則:
不要剝奪繼承權
通過引入「方法,這是不適用」的具體類的概念,這正是你正在做。現在像這樣的規則不是教條。但違反這條規則幾乎總是指向一個設計缺陷。
所有B的一個數據結構是唯一有用的有他們這樣做一招,他們都明白...
[EDIT2 OP的第二COMENT後]
OP希望有一個地圖,可以容納來自B的任何類別的對象。
這正是問題出現的地方。要了解如何存儲指向我們對象的指針和引用,我們必須問:用於存儲的是什麼。如果一個地圖,比如說mapB被用來存儲指向B的指針,那麼必須有一個點。數據存儲的樂趣在於檢索數據並做一些有用的事情。
讓我們通過使用日常生活中的清單來簡化這一點。假設我有一個personList 1000人,每個人都有他們的全名和電話號碼。現在說我的廚房水槽有問題。我實際上可以通讀清單,打電話給每個人,問:你能修理我的廚房水槽嗎?換句話說:你是否支持repairKitchenSink方法。或者:你有沒有可能成爲水管工人的一個例子(你是水管工)。但是後來我花了很長時間打電話,或許經過500次電話會議後,我很幸運。
現在我personList上的所有1000人都支持talkToMe方法。所以,每當我感到孤獨時,我都可以打電話給任何人,並調用該人的talkToMe方法。但他們不應該都有修理KidchenSink方法,即使不是純粹的虛擬或虛擬變體也會做別的事情,因爲如果我將這種方法稱爲類Burglar的人,他可能會響應這個呼叫,但是在一個意外的方式。
所以類人不應該包含一個方法repairKitchenSink,即使不是一個純粹的虛擬人。因爲它不應該被稱爲personList迭代的一部分。在迭代plumberList時應該調用它。該列表僅保存支持repairKitchenSink方法的對象。
使用純虛函數只有在適當
他們可能會支持它以不同的方式,但。換句話說,在Plumber類中,方法repairKitchenSink可以例如是純粹的虛擬。有可能例如是2派生類,PvcPlumber和CopperPlumber。 CopperPlumber將通過調用lightFlame實現(代碼)repairKitchenSink方法,然後調用solderDrainToSink,而PvcPlumber將實現它作爲applyGlueToPvcTube和glueTubeToSinkOutlet的連續調用。但是這兩個水管工子類僅以不同的方式實施repairKitchenSink。這並且唯一證明在他們的基類水暖工中擁有純虛函數repairKitchenSink。當然,一個班級可能來自水管工,不會實施該方法,比如說WannabePlumber類。但是由於它是抽象的,你不能從它實例化對象,這很好,除非你想溼腳。
Person可能有很多不同的子類。他們例如代表不同的職業,或不同的政治偏好或不同的宗教。如果一個人是民主黨的Budhist Plumber,那麼他(M/F)可能在繼承民主黨,Budhist和水管工階級的衍生階級。使用繼承或者甚至爲政治偏好或宗教信仰,甚至職業以及這些組合的無盡組合這樣動盪的東西打字,在實踐中並不方便,但這僅僅是一個例子。在現實中,職業,宗教和政治偏好可能是屬性。但這並沒有改變這裏重要的一點。如果某個類是不支持某種操作的,那麼它不應該在數據結構中暗示它的作用。
除了personList,擁有plumberList,animistList和democratList之外,您一定會打電話給理解您的方法inviteBillToPlayInMyJazzBand或worshipTheTreeInMyBackyard的人。
列表不包含對象,它們只包含指向對象的指針或引用。所以我們的民主Budhist管道工被包含在personList,democratList,budhistList和plumberList中沒有任何問題。列表就像數據庫索引。不包含記錄,他們只是指它們。你可以在一個表上有許多索引,而且你應該這麼做,因爲索引很小,並且使你的數據庫更快。
對於多態數據結構也是如此。目前,即使personList,democratList,budhistList和plumberList變得如此之大以至於內存不足,解決方案通常不會只有一個personList。因爲那樣你就會遇到一個性能問題和一個代碼複雜性問題,而這個問題通常會變得更糟。
所以,回到你的評論:你說你想讓你所有的派生類都在B的列表中。很好,但是B的接口應該只包含爲列表中的所有內容實現的方法,所以沒有虛擬方法。這就像是通過圖書館並瀏覽所有書籍,尋找一種支持教學關於浮萍的方法。老實說,告訴你所有這些,我一直犯下一個資本罪。我一直在推銷一般真理。但在軟件設計中這些不存在。我一直在試圖將它們出售給你,因爲我已經教了30年的面向對象設計,而且我認爲我認識到了你卡在哪裏。但是對於每一條規則,都有很多例外。儘管如此,如果我已經正確地解決了你的問題,在這種情況下,我認爲你應該選擇單獨的數據結構,每個數據結構只保存對象的引用或指針,這些對象確實可以在你迭代特定數據結構時進行欺騙。
點是方形圓
部分的混亂中適當地使用多態數據結構(數據結構保持指針或引用不同的對象類型)來對關係數據庫的世界。 RDB與平面記錄表一起工作,每個記錄具有相同的字段。由於某些領域可能不適用,所以發明了一種叫做「約束」的東西。在C++類中,Point將包含字段x和y。 Class Circle可以繼承它,並且還包含字段'radius'。類Square也可以繼承Point,但除x和y之外還包含字段「side」。在RDB世界約束中,不是字段,是繼承的。因此,一個圓的約束半徑將爲== 0,而一個Square的約束點的值爲== 0.一個點將繼承兩個約束,所以它將滿足既是正方形又是圓的條件:一個點是一個正方形這在數學上確實是這樣。請注意,與C++相比,約束繼承層次結構是'顛倒'的。這可能會讓人困惑。
無論是普遍認爲繼承與專業化並駕齊驅,兩者都無濟於事。雖然通常情況下並不總是如此。在許多情況下,C++繼承是擴展而不是專業化。這兩者往往是一致的,但點,方,圓的例子表明,這不是一個普遍的事實。
如果使用繼承,在C++中Circle應該從Point派生,因爲它有額外的字段。但是Circle肯定不是一種特殊類型的Point,反之亦然。在許多實際的圖書館中,Circle將包含一個類Point的對象,持有x和y,而不是從它繼承,繞過了整個問題。
歡迎的設計選擇
你碰到了什麼,世界是一個真正的設計選擇,一個重要問題。仔細思考這樣的事情,就像你在做的一樣,在實踐中嘗試所有這些事情,包括所謂的「錯誤」,都會使你成爲程序員,而不是編碼員。
沒有具體的例子就很難說出任何東西。繼承代表的是一種關係。通常,當你開始談論向對象添加「行爲和更多數據」時,這是否意味着它總是使用行爲和數據,或者它是可選的?如果這樣查看組件和組合而不是繼承和運行時查詢附加行爲。如果你真的覺得你會繼續擴展你的基本類型,這種方法更加模塊化。 –
您可能想了解[純虛方法](http://stackoverflow.com/questions/1306778/c-virtual-pure-virtual-explained) – wasthishelpful
單繼承是相當自然的,以確保整個層次結構將尊重共享概念;多重繼承(沒有虛擬)對於爲同一個對象提供不同的視圖可能很有用。應謹慎使用多個虛擬繼承(dynamic_cast從基礎派生到派生需要),正如您所提到的,複合或裝飾器模式可能是有趣的替代方案。 – Franck