C ++에서 다중 상속을 피해야하는 이유는 무엇입니까?
다중 상속을 사용하는 것이 좋은 개념입니까, 아니면 다른 작업을 대신 할 수 있습니까?
다중 상속 (MI로 약칭 됨)은 냄새가납니다 . 이는 일반적으로 나쁜 이유 때문에 수행되었으며 관리자의 얼굴로 날아갈 것입니다.
요약
- 상속 대신 기능의 구성을 고려하십시오.
- 공포의 다이아몬드에주의하세요
- 객체 대신 여러 인터페이스의 상속을 고려하십시오
- 때때로 다중 상속이 옳은 경우가 있습니다. 그렇다면 사용하십시오.
- 코드 검토에서 다중 상속 아키텍처를 방어 할 수 있도록 준비
1. 아마도 구성?
상속의 경우에도 마찬가지이므로 다중 상속의 경우에는 더욱 그렇습니다.
객체가 실제로 다른 객체를 상속해야합니까? A Car
는 Engine
직장에서 또는에서 상속받을 필요가 없습니다 Wheel
. A Car
는 Engine
4 개 Wheel
입니다.
컴포지션 대신 이러한 문제를 해결하기 위해 다중 상속을 사용하는 경우 뭔가 잘못한 것입니다.
2. 공포의 다이아몬드
일반적으로, 당신은 클래스가 A
다음 B
과 C
에서 모두 상속을 A
. 그리고 (왜 나에게 묻지 말고) 누군가 D
가 B
와 에서 상속해야한다고 결정 합니다 C
.
8 8 년 만에 이런 종류의 문제가 두 번 발생했으며 다음과 같은 이유로 인해 재미 있습니다.
- 아키텍처가 잘못 되었기 때문에 (실제로 전혀 존재해서는 안 됨 ) 처음부터 실수가 얼마나 되었습니까 (두 경우
D
모두B
와 에서 모두 상속 해서는 안 됨).C
C
- C ++에서 부모 클래스
A
가 손자 클래스에 두 번 존재 했기 때문에 유지 보수 담당자가 지불 한 금액으로 인해D
하나의 부모 필드A::field
를 업데이트하면 두 번 업데이트하고 (통과B::field
및C::field
) 무언가 잘못되고 충돌이 발생했습니다. (에서 포인터를 새로 작성B::field
하고 삭제C::field
...)
상속을 제한하기 위해 C ++에서 virtual 키워드를 사용하면 이것이 원하는 것이 아니라면 위에서 설명한 이중 레이아웃을 피할 수 있지만 어쨌든 내 경험상 아마도 뭔가 잘못했을 것입니다 ...
개체 계층 구조에서는 계층 구조를 그래프가 아닌 트리 (노드에 하나의 부모가 있음)로 유지해야합니다.
다이아몬드에 대한 자세한 내용 (edit 2017-05-03)
C ++에서 Dread Diamond의 실제 문제는 ( 디자인이 사운드라고 가정하고 코드를 검토했다는 가정하에 ) 선택해야합니다 .
- 클래스
A
가 레이아웃에 두 번 존재 하는 것이 바람직 합니까? 그것은 무엇을 의미합니까? 그렇다면 반드시 두 번 상속하십시오. - 한 번만 존재해야한다면 사실상 상속해야합니다.
이 선택은 문제에 내재되어 있으며 C ++에서는 다른 언어와 달리 언어 수준에서 디자인을 강요하지 않고 실제로 할 수 있습니다.
그러나 모든 힘과 마찬가지로 그 힘도 책임이 있습니다. 설계를 검토하십시오.
3. 인터페이스
위에서 언급 한 Dread of Diamond를 만나지 않기 때문에 0 개 또는 1 개의 구체적인 클래스와 0 개 이상의 인터페이스를 여러 번 상속하는 것이 좋습니다. 사실, 이것은 자바에서 일이 이루어지는 방식입니다.
C의에서 상속 할 때 일반적으로, 당신은 무엇을 의미 A
하고 B
사용자가 사용할 수 있다는 것입니다 C
그것이 인 것처럼 A
, 및 / 또는 것처럼이 있었다 B
.
C ++에서 인터페이스는 다음과 같은 추상 클래스입니다.
모든 메소드가 순수 가상으로 선언 됨(= 0으로 접미어)(2017-05-03 제거)- 멤버 변수 없음
하나의 실제 객체에 대한 다중 상속과 0 개 이상의 인터페이스는 "적은"것으로 간주되지 않습니다 (적어도 많지 않음).
C ++ 추상 인터페이스에 대한 추가 정보 (편집 2017-05-03)
첫째, 실제 기준은 상태가 없어야합니다 (즉,를 제외하고 멤버 변수 가 없기 때문에). NVI 패턴을 사용하여 인터페이스를 생성 할 수 있습니다 this
. 귀하의 추상 인터페이스의 요점은 계약을 게시하는 것입니다 ( "당신은 이런 식으로 저에게 전화 할 수 있습니다"). 추상적 가상 방법 만 갖는 한계는 의무가 아니라 디자인 선택이어야합니다.
둘째, C ++에서는 추가 비용 / 간접에도 불구하고 추상 인터페이스에서 사실상 상속하는 것이 합리적입니다. 그렇지 않으면 인터페이스 상속이 계층 구조에서 여러 번 나타나는 경우 모호성이 있습니다.
셋째, 객체 지향은 매우 중요하지만, 그렇지 않은 유일한 진실 아웃이 TM 에서 C ++. 올바른 도구를 사용하고 C ++에서 다른 종류의 솔루션을 제공하는 다른 패러다임을 항상 기억하십시오.
4. 다중 상속이 정말로 필요합니까?
어쩔 땐 그래.
일반적으로, 당신의 C
클래스에서 상속되지 A
및 B
및 A
및 B
(즉,하지 동일한 계층 구조에서 일반적으로, 다른 개념 등의 아무것도)이 관련이없는 개체가 있습니다.
예를 들어, Nodes
X, Y, Z 좌표 시스템을 사용하여 많은 기하학적 계산 (아마도 점, 기하학적 객체의 일부)을 수행 할 수 있으며 각 노드는 다른 에이전트와 통신 할 수있는 자동 에이전트입니다.
아마 당신은 이미 두 라이브러리 자체 네임 스페이스 각각에 액세스 할 수 있습니다 (네임 스페이스를 사용하는 또 다른 이유를 ...하지만 당신은, 당신을 네임 스페이스를하지 사용할 수 있습니까?) 한 존재 geo
와 다른 존재를ai
자신이 그래서 own::Node
에서 모두 파생를 ai::Agent
하고 geo::Point
.
이 때 컴포지션을 대신 사용하지 말아야하는지 스스로에게 물어봐야 할 순간입니다. 경우 own::Node
정말 정말 둘 다 ai::Agent
하고 geo::Point
, 다음 구성은하지 않습니다.
그런 다음 own::Node
3D 공간에서의 위치에 따라 다른 상담원과 의사 소통 할 수있는 다중 상속이 필요 합니다.
(당신은 점에 유의거야 ai::Agent
와 geo::Point
완전히, 완전히, 완전히 관련이없는 ...이 크게 다중 상속의 위험을 감소)
다른 경우 (편집 2017-05-03)
다른 경우가 있습니다 :
- 구현 세부 사항으로 (희망적으로 개인) 상속 사용
- 정책과 같은 일부 C ++ 관용구는 다중 상속을 사용할 수 있습니다 (각 부분이를 통해 다른 부분과 통신해야 할 때
this
) - std :: exception의 가상 상속 (예외에는 가상 상속이 필요합니까? )
- 기타
때로는 컴포지션을 사용할 수 있으며 때로는 MI가 더 좋습니다. 요점은 : 당신은 선택이있다. 책임감있게 코드를 검토하십시오.
5. 다중 상속을해야합니까?
대부분의 경우 내 경험으로는 그렇지 않습니다. MI는이 더미에 게으른에 의해 사용될 수 있기 때문에 (A 만들기 같은 결과를 실현하지 않고 함께 기능, 작동하는 것 같다 경우에도 올바른 도구가 아닙니다 Car
둘 모두 Engine
와를 Wheel
).
그러나 때때로 그렇습니다. 그리고 그 당시에는 MI보다 나은 것이 없습니다.
그러나 MI는 냄새가 나기 때문에 코드 검토에서 아키텍처를 방어 할 수 있도록 준비하십시오. 방어 할 수없는 경우 방어해서는 안됩니다.
사람들은 다중 상속이 필요하지 않다고 말합니다. 다중 상속으로 할 수있는 것은 단일 상속으로도 할 수 있기 때문입니다. 내가 언급 한 위임 트릭을 사용하면됩니다. 또한 단일 상속으로 수행하는 작업은 클래스를 통해 전달하여 상속하지 않고도 수행 할 수 있으므로 상속이 전혀 필요하지 않습니다. 실제로 포인터와 데이터 구조로 모두 할 수 있기 때문에 클래스가 필요하지 않습니다. 그러나 왜 그렇게 하시겠습니까? 언어 시설을 사용하는 것이 언제 편리합니까? 언제 해결 방법을 선호하십니까? 다중 상속이 유용한 경우를 보았고 복잡한 다중 상속이 유용한 경우도 보았습니다. 일반적으로, 나는 해결책으로 언어가 제공하는 기능을 사용하는 것을 선호합니다
There's no reason to avoid it and it can be very useful in situations. You need to be aware of the potential issues though.
The biggest one being the diamond of death:
class GrandParent;
class Parent1 : public GrandParent;
class Parent2 : public GrandParent;
class Child : public Parent1, public Parent2;
You now have two "copies" of GrandParent within Child.
C++ has thought of this though and lets you do virtual inheritence to get around the issues.
class GrandParent;
class Parent1 : public virtual GrandParent;
class Parent2 : public virtual GrandParent;
class Child : public Parent1, public Parent2;
Always review your design, ensure you are not using inheritance to save on data reuse. If you can represent the same thing with composition (and typically you can) this is a far better approach.
See w:Multiple Inheritance.
Multiple inheritance has received criticism and as such, is not implemented in many languages. Criticisms includes:
- Increased complexity
- Semantic ambiguity often summarized as the diamond problem.
- Not being able to explicitly inherit multiple times from a single class
- Order of inheritance changing class semantics.
Multiple inheritance in languages with C++/Java style constructors exacerbates the inheritance problem of constructors and constructor chaining, thereby creating maintenance and extensibility problems in these languages. Objects in inheritance relationships with greatly varying construction methods are hard to implement under the constructor chaining paradigm.
Modern way of resolving this to use interface (pure abstract class) like COM and Java interface.
I can do other things in place of this?
Yes, you can. I am going to steal from GoF.
- Program to an Interface, not an Implementation
- Prefer composition over inheritance
Public inheritance is an IS-A relationship, and sometimes a class will be an type of several different classes, and sometimes it's important to reflect this.
"Mixins" are also sometimes useful. They are generally small classes, usually not inheriting from anything, providing useful functionality.
As long as the inheritance hierarchy is fairly shallow (as it should almost always be), and well managed, you're unlikely to get the dreaded diamond inheritance. The diamond isn't a problem with all languages that use multiple inheritance, but C++'s treatment of it is frequently awkward and sometimes puzzling.
While I've run into cases where multiple inheritance is very handy, they're actually fairly rare. This is likely because I prefer to use other design methods when I don't really need multiple inheritance. I do prefer to avoid confusing language constructs, and it's easy to construct inheritance cases where you have to read the manual really well to figure out what's going on.
You shouldn't "avoid" multiple inheritance but you should be aware of problems that can arise such as the 'diamond problem' ( http://en.wikipedia.org/wiki/Diamond_problem ) and treat the power given to you with care, as you should with all powers.
Every programming language has a slightly different treatment of object-oriented programming with pros and cons. C++'s version places the emphasis squarely on performance and has the accompanying downside that it is disturbingly easy to write invalid code - and this is true of multiple inheritance. As a consequence there is a tendency to steer programmers away from this feature.
Other people have addressed the question of what multiple inheritance isn't good for. But we have seen quite a few comments that more-or-less imply that the reason to avoid it is because it's not safe. Well, yes and no.
As is often true in C++, if you follow a basic guideline you can use it safely without having to "look over your shoulder" constantly. The key idea is that you distinguish a special kind of class definition called a "mix-in"; class is a mix-in if all its member functions are virtual (or pure virtual). Then you are allowed to inherit from a single main class and as many "mix-ins" as you like - but you should inherit mixins with the keyword "virtual". e.g.
class CounterMixin {
int count;
public:
CounterMixin() : count( 0 ) {}
virtual ~CounterMixin() {}
virtual void increment() { count += 1; }
virtual int getCount() { return count; }
};
class Foo : public Bar, virtual public CounterMixin { ..... };
My suggestion is that if you intend to use a class as a mix-in class you also adopt a naming convention to make it easy for anyone reading the code to see what's happening & to verify you're playing by the rules of the basic guideline. And you'll find it works much better if your mix-ins have default constructors too, just because of the way virtual base classes work. And remember to make all the destructors virtual too.
Note that my use of the word "mix-in" here isn't the same as the parameterised template class (see this link for a good explanation) but I think it is a fair use of the terminology.
Now I don't want to give the impression that this is the only way to use multiple inheritance safely. It's just one way that is fairly easy to check.
At the risk of getting a bit abstract, I find it illuminating to think about inheritance within the frame of category theory.
If we think of all our classes and arrows between them denoting inheritance relations, then something like this
A --> B
means that class B
derives from class A
. Note that, given
A --> B, B --> C
we say C derives from B which derives from A, so C is also said to derive from A, thus
A --> C
Furthermore, we say that for every class A
that trivially A
derives from A
, thus our inheritance model fulfills the definition of a category. In more traditional language, we have a category Class
with objects all classes and morphisms the inheritance relations.
That's a bit of setup, but with that let's take a look at our Diamond of Doom:
C --> D
^ ^
| |
A --> B
It's a shady looking diagram, but it'll do. So D
inherits from all of A
, B
, and C
. Furthermore, and getting closer to addressing OP's question, D
also inherits from any superclass of A
. We can draw a diagram
C --> D --> R
^ ^
| |
A --> B
^
|
Q
Now, problems associated with the Diamond of Death here are when C
and B
share some property/method names and things get ambiguous; however, if we move any shared behavior into A
then the ambiguity disappears.
Put in categorical terms, we want A
, B
and C
to be such that if B
and C
inherit from Q
then A
can be rewritten as as subclass of Q
. This makes A
something called a pushout.
There is also a symmetric construction on D
called a pullback. This is essentially the most general useful class you can construct which inherits from both B
and C
. That is, if you have any other class R
multiply inheriting from B
and C
, then D
is a class where R
can be rewritten as as subclass of D
.
Making sure your tips of the diamond are pullbacks and pushouts gives us a nice way to generically handle name-clashing or maintenance issues which might arise otherwise.
Note Paercebal's answer inspired this as his admonitions are implied by the above model given that we work in the full category Class of all possible classes.
I wanted to generalize his argument to something which shows how complicated multiple inheritance relationships can be both powerful and non-problematic.
TL;DR Think of the inheritance relationships in your program as forming a category. Then you can avoid Diamond of Doom problems by making multiply-inherited classes pushouts and symmetrically, making a common parent class which is a pullback.
We use Eiffel. We have excellent MI. No worries. No issues. Easily managed. There are times to NOT use MI. However, it useful more than people realize because they are: A) in a dangerous language that does not manage it well -OR- B) satisfied with how they've worked around MI for years and years -OR- C) other reasons (too numerous to list I am quite sure--see answers above).
For us, using Eiffel, MI is as natural as anything else and another fine tool in the toolbox. Frankly, we're quite unconcerned that no one else is using Eiffel. No worries. We are happy with what we have and invite you to have a look.
While you're looking: Take special note of Void-safety and the eradication of Null pointer dereferencing. While we're all dancing around MI, your pointers are getting lost! :-)
You should use it carefully, there are some cases, like the Diamond Problem, when things can go complicated.
(source: learncpp.com)
Uses and Abuses of Inheritance.
The article does a great job of explaining inheritance, and it's dangers.
Beyond the diamond pattern, multiple inheritance tends to make the object model harder to understand, which in turn increases maintenance costs.
Composition is intrinsically easy to understand, comprehend, and explain. It can get tedious to write code for, but a good IDE (it's been a few years since I've worked with Visual Studio, but certainly the Java IDEs all have great composition shortcut automating tools) should get you over that hurdle.
Also, in terms of maintenance, the "diamond problem" comes up in non-literal inheritance instances as well. For instance, if you have A and B and your class C extends them both, and A has a 'makeJuice' method which makes orange juice and you extend that to make orange juice with a twist of lime: what happens when the designer for 'B' adds a 'makeJuice' method which generates and electrical current? 'A' and 'B' may be compatible "parents" right now, but that doesn't mean they will always be so!
Overall, the maxim of tending to avoid inheritance, and especially multiple inheritance, is sound. As all maxims, there are exceptions, but you need to make sure that there is a flashing green neon sign pointing at any exceptions you code (and train your brain so that any time you see such inheritance trees you draw in your own flashing green neon sign), and that you check to make sure it all makes sense every once in a while.
The key issue with MI of concrete objects is that rarely do you have an object that legitimately should "Be an A AND be a B", so it is rarely the correct solution on logical grounds. Far more often, you have an object C that obeys "C can act as an A or a B", which you can achieve via interface inheritance & composition. But make no mistake- inheritance of multiple interfaces is still MI, just a subset of it.
For C++ in particular, the key weakness of the feature isn't the actual EXISTENCE of Multiple Inheritance, but some constructs it allows that are almost always malformed. For example, inheriting multiple copies of the same object like:
class B : public A, public A {};
is malformed BY DEFINITION. Translated into English this is "B is an A and an A". So, even in human language there's a severe ambiguity. Did you mean "B has 2 As" or just "B is an A"?. Allowing such pathological code, and worse making it a usage example, did C++ no favors when it came to making a case for keeping the feature in successor languages.
You can use composition in preference to inheritance.
The general feeling is that composition is better, and it's very well discussed.
it takes 4/8 bytes per class involved. (One this pointer per class).
This might never be a concern, but if one day you have a micro data structure which is instanced billions of time it will be.
참고URL : https://stackoverflow.com/questions/406081/why-should-i-avoid-multiple-inheritance-in-c
'IT' 카테고리의 다른 글
Javascript로 스크롤 막대 위치를 얻는 방법은 무엇입니까? (0) | 2020.06.04 |
---|---|
Bash에서 길이가 0이 아닌 문자열을 테스트하십시오. [-n "$ var"] 또는 [ "$ var"] (0) | 2020.06.04 |
NoSuchMethodError를 어떻게 해결합니까? (0) | 2020.06.03 |
Django 템플릿에서 배열 요소에 액세스하는 방법은 무엇입니까? (0) | 2020.06.03 |
원인 : buildOutput.apkData가 널이 아니어야합니다. (0) | 2020.06.03 |