問題描述
使用多重繼承是個好概念還是我可以做其他事情?
多繼承(縮寫為 MI)smells,意思是通常,它是做不好的原因,會在維護(hù)者面前反擊.
總結(jié)
- 考慮組合特征,而不是繼承
- 小心恐懼之鉆
- 考慮繼承多個接口而不是對象
- 有時,多重繼承是正確的.如果是,請使用它.
- 準(zhǔn)備好在代碼審查中捍衛(wèi)您的多重繼承架構(gòu)
1.也許是組合?
這適用于繼承,因此,更適用于多重繼承.
你的對象真的需要從另一個繼承嗎?Car
不需要從 Engine
繼承來工作,也不需要從 Wheel
繼承.一個 Car
有一個 Engine
和四個 Wheel
.
如果您使用多重繼承而不是組合來解決這些問題,那么您就做錯了.
2.恐懼鉆石
通常,你有一個 A
類,然后 B
和 C
都繼承自 A
.并且(不要問我為什么)然后有人決定 D
必須同時繼承 B
和 C
.
我在八八年里遇到過兩次這樣的問題,看到它很有趣,因為:
- 從一開始就犯了多大的錯誤(在這兩種情況下,
D
不應(yīng)該同時繼承B
和C
),因為這是糟糕的架構(gòu)(事實上,C
根本不應(yīng)該存在......) - 維護(hù)者為此付出了多少代價,因為在 C++ 中,父類
A
在其孫子類D
中出現(xiàn)兩次,因此更新了一個父字段A::field
意味著要么更新它兩次(通過B::field
和C::field
),要么讓某些東西默默地出錯并崩潰, 稍后(在B::field
中新建一個指針,并刪除C::field
...)
如果這不是您想要的,則在 C++ 中使用關(guān)鍵字 virtual 來限定繼承可避免上述雙重布局,但無論如何,根據(jù)我的經(jīng)驗,您可能做錯了什么......
在對象層次結(jié)構(gòu)中,您應(yīng)該嘗試將層次結(jié)構(gòu)保持為樹(一個節(jié)點有一個父節(jié)點),而不是圖形.
關(guān)于鉆石的更多信息(編輯 2017-05-03)
C++ 中恐懼鉆石的真正問題(假設(shè)設(shè)計是合理的 - 檢查您的代碼!),是您需要做出選擇:
- 是否希望
A
類在您的布局中出現(xiàn)兩次,這意味著什么?如果是,那么一定要從它繼承兩次. - 如果它應(yīng)該只存在一次,那么虛擬地繼承它.
這種選擇是問題所固有的,而且在 C++ 中,與其他語言不同,您實際上可以做到這一點,而無需教條在語言級別強(qiáng)制您的設(shè)計.
但與所有權(quán)力一樣,權(quán)力也伴隨著責(zé)任:審查您的設(shè)計.
3.接口
零個或一個具體類的多重繼承,以及零個或多個接口通常是可以的,因為您不會遇到上述的恐懼鉆石.事實上,這就是 Java 的工作方式.
通常,當(dāng) C 繼承自 A
和 B
時,您的意思是用戶可以像使用 一樣使用
,和/或好像它是一個 C
AB
.
在 C++ 中,接口是一個抽象類,它具有:
其所有方法聲明為純虛擬(后綴= 0)(刪除了2017-05-03)- 無成員變量
零到一個真實對象的多重繼承,以及零個或多個接口不被認(rèn)為是臭的";(至少,沒有那么多).
有關(guān) C++ 抽象接口的更多信息(編輯 2017-05-03)
首先,NVI 模式可用于生成接口,因為真正的標(biāo)準(zhǔn)是沒有狀態(tài)(即沒有成員變量,除了this
).你的抽象接口的重點是發(fā)布一個契約(你可以這樣稱呼我,這樣稱呼我"),僅此而已.只有抽象虛方法的限制應(yīng)該是一種設(shè)計選擇,而不是一種義務(wù).
其次,在 C++ 中,從抽象接口虛擬繼承是有意義的(即使有額外的成本/間接).如果您不這樣做,并且接口繼承在您的層次結(jié)構(gòu)中多次出現(xiàn),那么您就會有歧義.
第三,面向?qū)ο蠛馨簦皇?C++ 中的唯一的真相TM.使用正確的工具,并始終記住您在 C++ 中還有其他范例可提供不同類型的解決方案.
4.你真的需要多重繼承嗎?
有時是的.
通常,您的 C
類繼承自 A
和 B
,以及 A
和 B
是兩個不相關(guān)的對象(即不在同一個層次結(jié)構(gòu)中、沒有共同點、不同的概念等).
例如,您可以有一個帶有 X、Y、Z 坐標(biāo)的 Nodes
系統(tǒng),能夠進(jìn)行大量幾何計算(可能是一個點,幾何對象的一部分),并且每個 Node 是一個自動代理,能夠與其他代理通信.
也許您已經(jīng)可以訪問兩個庫,每個庫都有自己的命名空間(使用命名空間的另一個原因……但是您使用命名空間,不是嗎?),一個是 geo
和其他是 ai
所以你有自己的 own::Node
派生自 ai::Agent
和 geo::Point
.
此時您應(yīng)該問問自己是否不應(yīng)該使用組合來代替.如果 own::Node
真的既是 ai::Agent
又是 geo::Point
,那么組合就行不通了.>
然后你需要多重繼承,讓你的 own::Node
根據(jù)他們在 3D 空間中的位置與其他代理通信.
(你會注意到 ai::Agent
和 geo::Point
是完全、完全、完全無關(guān)的……這大大降低了危險多重繼承)
其他情況(編輯 2017-05-03)
還有其他情況:
- 使用(希望是私有的)繼承作為實現(xiàn)細(xì)節(jié)
- 一些像策略這樣的 C++ 習(xí)語可以使用多重繼承(當(dāng)每個部分需要通過
this
與其他部分進(jìn)行通信時) - 來自 std::exception 的虛擬繼承(異常是否需要虛擬繼承?)
- 等
有時你可以使用組合,有時 MI 更好.重點是:你有一個選擇.負(fù)責(zé)任地進(jìn)行(并審查您的代碼).
5.那么,我應(yīng)該做多重繼承嗎?
大多數(shù)時候,根據(jù)我的經(jīng)驗,沒有.MI 不是正確的工具,即使它看起來有效,因為它可以被懶惰的人使用而沒有意識到后果(例如使 Car
同時成為 Engine
和 Wheel
).
但有時,是的.那時,沒有什么比 MI 更有效的了.
但是因為 MI 很臭,所以準(zhǔn)備在代碼審查中捍衛(wèi)你的架構(gòu)(捍衛(wèi)它是一件好事,因為如果你無法捍衛(wèi)它,那么你就不應(yīng)該這樣做).
Is it a good concept to use multiple inheritance or can I do other things instead?
Multiple inheritance (abbreviated as MI) smells, which means that usually, it was done for bad reasons, and it will blow back in the face of the maintainer.
Summary
- Consider composition of features, instead of inheritance
- Be wary of the Diamond of Dread
- Consider inheritance of multiple interfaces instead of objects
- Sometimes, Multiple Inheritance is the right thing. If it is, then use it.
- Be prepared to defend your multiple-inherited architecture in code reviews
1. Perhaps composition?
This is true for inheritance, and so, it's even more true for multiple inheritance.
Does your object really needs to inherit from another? A Car
does not need to inherit from an Engine
to work, nor from a Wheel
. A Car
has an Engine
and four Wheel
.
If you use multiple inheritance to resolve these problems instead of composition, then you've done something wrong.
2. The Diamond of Dread
Usually, you have a class A
, then B
and C
both inherit from A
. And (don't ask me why) someone then decides that D
must inherit both from B
and C
.
I've encountered this kind of problem twice in 8 eights years, and it is amusing to see because of:
- How much of a mistake it was from the beginning (In both cases,
D
should not have inherited from bothB
andC
), because this was bad architecture (in fact,C
should not have existed at all...) - How much maintainers were paying for that, because in C++, the parent class
A
was present twice in its grandchild classD
, and thus, updating one parent fieldA::field
meant either updating it twice (throughB::field
andC::field
), or having something go silently wrong and crash, later (new a pointer inB::field
, and deleteC::field
...)
Using the keyword virtual in C++ to qualify the inheritance avoids the double layout described above if this is not what you want, but anyway, in my experience, you're probably doing something wrong...
In Object hierarchy, you should try to keep the hierarchy as a Tree (a node has ONE parent), not as a graph.
More about the Diamond (edit 2017-05-03)
The real problem with the Diamond of Dread in C++ (assuming the design is sound - have your code reviewed!), is that you need to make a choice:
- Is it desirable for the class
A
to exist twice in your layout, and what does it mean? If yes, then by all means inherit from it twice. - if it should exist only once, then inherit from it virtually.
This choice is inherent to the problem, and in C++, unlike other languages, you can actually do it without dogma forcing your design at language level.
But like all powers, with that power comes responsibility: Have your design reviewed.
3. Interfaces
Multiple inheritance of zero or one concrete classes, and zero or more interfaces is usually Okay, because you won't encounter the Diamond of Dread described above. In fact, this is how things are done in Java.
Usually, what you mean when C inherits from A
and B
is that users can use C
as if it was a A
, and/or as if it was a B
.
In C++, an interface is an abstract class which has:
all its method declared pure virtual (suffixed by = 0)(removed the 2017-05-03)- no member variables
The Multiple inheritance of zero to one real object, and zero or more interfaces is not considered "smelly" (at least, not as much).
More about the C++ Abstract Interface (edit 2017-05-03)
First, the NVI pattern can be used to produce an interface, because the real criteria is to have no state (i.e. no member variables, except this
). Your abstract interface's point is to publish a contract ("you can call me this way, and this way"), nothing more, nothing less. The limitation of having only abstract virtual method should be a design choice, not an obligation.
Second, in C++, it makes sense to inherit virtually from abstract interfaces, (even with the additional cost/indirection). If you don't, and the interface inheritance appears multiple time in your hierarchy, then you'll have ambiguities.
Third, object orientation is great, but it is not The Only Truth Out ThereTM in C++. Use the right tools, and always remember you have other paradigms in C++ offering different kind of solutions.
4. Do you really need Multiple Inheritance?
Sometimes, yes.
Usually, your C
class is inheriting from A
and B
, and A
and B
are two unrelated objects (i.e. not in the same hierarchy, nothing in common, different concepts, etc.).
For example, you could have a system of Nodes
with X,Y,Z coordinates, able to do a lot of geometric calculations (perhaps a point, part of geometric objects) and each Node is an Automated Agent, able to communicate with other agents.
Perhaps you already have access to two libraries, each with its own namespace (another reason to use namespaces... But you use namespaces, don't you?), one being geo
and the other being ai
So you have your own own::Node
derive both from ai::Agent
and geo::Point
.
This is the moment when you should ask yourself if you should not use composition instead. If own::Node
is really really both a ai::Agent
and a geo::Point
, then composition will not do.
Then you'll need multiple inheritance, having your own::Node
communicate with other agents according to their position in a 3D space.
(You'll note that ai::Agent
and geo::Point
are completely, totally, fully UNRELATED... This drastically reduces the danger of multiple inheritance)
Other cases (edit 2017-05-03)
There are other cases:
- using (hopefully private) inheritance as implementation detail
- some C++ idioms like policies could use multiple inheritance (when each part needs to communicate with the others through
this
) - the virtual inheritance from std::exception (Is Virtual Inheritance necessary for Exceptions?)
- etc.
Sometimes you can use composition, and sometimes MI is better. The point is: You have a choice. Do it responsibly (and have your code reviewed).
5. So, should I do Multiple Inheritance?
Most of the time, in my experience, no. MI is not the right tool, even if it seems to work, because it can be used by the lazy to pile features together without realizing the consequences (like making a Car
both an Engine
and a Wheel
).
But sometimes, yes. And at that time, nothing will work better than MI.
But because MI is smelly, be prepared to defend your architecture in code reviews (and defending it is a good thing, because if you're not able to defend it, then you should not do it).
這篇關(guān)于為什么我應(yīng)該避免 C++ 中的多重繼承?的文章就介紹到這了,希望我們推薦的答案對大家有所幫助,也希望大家多多支持html5模板網(wǎng)!