다중 상속의 정확한 문제는 무엇입니까?
여러 상속이 다음 버전의 C # 또는 Java에 포함되어야하는지 항상 묻는 사람들이 있습니다. 이 능력을 가질만큼 운이 좋은 C ++ 사람들은 누군가에게 결국 매달릴 수있는 밧줄을주는 것과 같다고 말합니다.
다중 상속 문제는 무엇입니까? 구체적인 샘플이 있습니까?
가장 명백한 문제는 함수 재정의입니다.
두 개의 클래스가 A
있고 B
둘 다 메소드를 정의 한다고 가정 해 봅시다 doSomething
. 이제 및 C
에서 상속되는 세 번째 클래스를 정의 하지만 메서드를 재정의하지는 않습니다 .A
B
doSomething
컴파일러가이 코드를 시드 할 때 ...
C c = new C();
c.doSomething();
... 어떻게 구현해야합니까? 더 이상의 설명이 없으면 컴파일러가 모호성을 해결할 수 없습니다.
재정의 외에도 다중 상속의 또 다른 큰 문제는 메모리에있는 물리적 객체의 레이아웃입니다.
C ++ 및 Java 및 C #과 같은 언어는 각 유형의 객체에 대해 고정 주소 기반 레이아웃을 만듭니다. 이 같은:
class A:
at offset 0 ... "abc" ... 4 byte int field
at offset 4 ... "xyz" ... 8 byte double field
at offset 12 ... "speak" ... 4 byte function pointer
class B:
at offset 0 ... "foo" ... 2 byte short field
at offset 2 ... 2 bytes of alignment padding
at offset 4 ... "bar" ... 4 byte array pointer
at offset 8 ... "baz" ... 4 byte function pointer
컴파일러는 머신 코드 (또는 바이트 코드)를 생성 할 때 해당 숫자 오프셋을 사용하여 각 메소드 또는 필드에 액세스합니다.
다중 상속은 매우 까다로워집니다.
class C
가 A
and 에서 상속 받으면 B
컴파일러는 데이터를 AB
순서대로 정렬할지 순서대로 레이아웃해야하는지 결정해야합니다 BA
.
그러나 이제 B
객체에서 메소드를 호출한다고 상상해보십시오 . 정말 B
입니까? 아니면 실제로 인터페이스를 C
통해 다형성이라고 불리는 객체 B
입니까? 객체의 실제 아이덴티티에 따라 물리적 레이아웃이 달라지고 호출 사이트에서 호출 할 함수의 오프셋을 알 수 없습니다.
이러한 종류의 시스템을 처리하는 방법은 고정 레이아웃 접근 방식을 버리고 기능을 호출하거나 필드에 액세스 하기 전에 각 객체의 레이아웃을 쿼리 할 수 있습니다.
그래서 ... 긴 이야기가 짧습니다 ... 컴파일러 작성자가 다중 상속을 지원하는 데 어려움이 있습니다. 따라서 Guido van Rossum과 같은 누군가가 파이썬을 디자인하거나 Anders Hejlsberg가 c #을 디자인 할 때 다중 상속을 지원하면 컴파일러 구현이 훨씬 더 복잡해질 것이며 아마도 이점이 비용 가치가 있다고 생각하지 않는다는 것을 알고 있습니다.
당신이 언급 한 문제는 실제로 해결하기가 어렵지 않습니다. 실제로 예를 들어 에펠은 완벽하게 잘합니다! (그리고 임의의 선택이나 다른 것을 소개하지 않고)
예를 들어, foo () 메소드를 가진 A와 B를 상속 받았다면 물론 C와 A와 B를 모두 상속하는 임의의 선택을 원하지 않을 것입니다. c.foo ()가 호출되거나 그렇지 않으면 C에서 메소드 중 하나의 이름을 바꿔야하는 경우에 사용됩니다 (bar ()가 될 수 있음).
또한 다중 상속이 종종 매우 유용하다고 생각합니다. Eiffel의 라이브러리를 보면 그것이 모든 곳에서 사용되고 개인적으로 Java 프로그래밍으로 돌아 가야 할 때 기능을 놓친 것을 알 수 있습니다.
다이아몬드 문제 :
an ambiguity that arises when two classes B and C inherit from A, and class D inherits from both B and C. If there is a method in A that B and C have overridden, and D does not override it, then which version of the method does D inherit: that of B, or that of C?
...It is called the "diamond problem" because of the shape of the class inheritance diagram in this situation. In this case, class A is at the top, both B and C separately beneath it, and D joins the two together at the bottom to form a diamond shape...
Multiple inheritance is one of those things that is not used often, and can be misused, but is sometimes needed.
I never understood not adding a feature, just because it might be misused, when there are no good alternatives. Interfaces are not an alternative to multiple inheritance. For one, they don't let you enforce preconditions or postconditions. Just like any other tool, you need to know when it is appropriate to use, and how to use it.
let's say you have objects A and B which are both inherited by C. A and B both implement foo() and C does not. I call C.foo(). Which implementation gets chosen? There are other issues, but this type of thing is a big one.
The main problem with multiple inheritance is nicely summed up with tloach's example. When inheriting from multiple base classes that implement the same function or field it's the compiler has to make a decision about what implementation to inherit.
This get's worse when you inherit from multiple classes that inherit from the same base class. (diamond inheritance, if you draw the inheritance tree you get a diamond shape)
These problems are not really problematic for a compiler to overcome. But the choice the compiler has to make here are rather arbitrary, this make code far less intuitive.
I find that when doing good OO design I never need multiple inheritance. In cases I do need it I usually find I've been using inheritance to reuse functionality while inheritance is only appropriate for "is-a" relations.
There are other techniques like mixins that solve the same problems and don't have the issues that multiple inheritance has.
I don't think the diamond problem is a problem, I would consider that sophistry, nothing else.
The worst problem, from my point of view, with multiple inheritance is RAD - victims and people who claim to be developers but in reality are stuck with half - knowledge (at best).
Personally, I would be very happy if I could finally do something in Windows Forms like this (it's not correct code, but it should give you the idea):
public sealed class CustomerEditView : Form, MVCView<Customer>
This is the main issue I have with having no multiple inheritance. You CAN do something similar with interfaces, but there is what I call "s*** code", it's this painful repetitive c*** you have to write in each of your classes to get a data context, for example.
In my opinion, there should be absolutely no need, not the slightest, for ANY repetition of code in a modern language.
The Common Lisp Object System (CLOS) is another example of something that supports MI while avoiding the C++-style problems: inheritance is given a sensible default, while still allowing you the freedom to explicitly decide how exactly to, say, call a super's behaviour.
There is nothing wrong in multiple inheritance itself. The problem is to add multiple inheritance to a language that was not designed with multiple inheritance in mind from the start.
The Eiffel language is supporting multiple inheritance without restrictions in a very efficient and productive way but the language was designed from that start to support it.
This feature is complex to implement for compiler developers, but it seems that that drawback could be compensated by the fact that a good multiple inheritance support could avoid the support of other features (i.e. no need for Interface or Extension Method).
I think that supporting multiple inheritance or not is more a matter of choice, a matter of priorities. A more complex feature takes more time to be correctly implemented and operational and may be more controversial. The C++ implementation may be the reason why multiple inheritance was not implemented in C# and Java...
One of the design goals of frameworks like Java and .NET is to make it possible for code which is compiled to work with one version of a pre-compiled library, to work equally well with subsequent versions of that library, even if those subsequent versions add new features. While the normal paradigm in languages like C or C++ is to distribute statically-linked executables that contain all of the libraries they need, the paradigm in .NET and Java is to distribute applications as collections of components that are "linked" at run-time.
The COM model which preceded .NET attempted to use this general approach, but it didn't really have inheritance--instead, each class definition effectively defined both a class and an interface of the same name which contained all its public members. Instances were of the class type, while references were of the interface type. Declared a class as deriving from another was equivalent to declaring a class as implementing the other's interface, and required the new class to re-implement all public members of the classes from which one derived. If Y and Z derive from X, and then W derives from Y and Z, it won't matter if Y and Z implement X's members differently, because Z won't be able to use their implementations--it will have to define its own. W might encapsulate instances of Y and/or Z, and chain its implementations of X's methods through theirs, but there would be no ambiguity as to what X's methods should do--they'd do whatever Z's code explicitly directed them to do.
The difficulty in Java and .NET is that code is allowed to inherit members and have accesses to them implicitly refer to the parent members. Suppose one had classes W-Z related as above:
class X { public virtual void Foo() { Console.WriteLine("XFoo"); }
class Y : X {};
class Z : X {};
class W : Y, Z // Not actually permitted in C#
{
public static void Test()
{
var it = new W();
it.Foo();
}
}
It would seem like W.Test()
should creating an instance of W call the implementation of virtual method Foo
defined in X
. Suppose, however, that Y and Z were actually in a separately-compiled module, and although they were defined as above when X and W were compiled, they were later changed and recompiled:
class Y : X { public override void Foo() { Console.WriteLine("YFoo"); }
class Z : X { public override void Foo() { Console.WriteLine("ZFoo"); }
Now what should be the effect of calling W.Test()
? If the program had to be statically linked before distribution, the static link stage might be able to discern that while the program had no ambiguity before Y and Z were changed, the changes to Y and Z have made things ambiguous and the linker could refuse to build the program unless or until such ambiguity is resolved. On the other hand, it's possible that the person who has both W and the new versions of Y and Z is someone who simply wants to run the program and has no source code for any of it. When W.Test()
runs, it would no longer be clear what W.Test()
should do, but until the user tried to run W with the new version of Y and Z there would be no way any part of the system could recognize there was a problem (unless W was considered illegitimate even before the changes to Y and Z).
The diamond is not a problem, as long as you don’t use anything like C++ virtual inheritance: in normal inheritance each base class resembles a member field (actually they are laid out in RAM this way), giving you some syntactic sugar and an extra ability to override more virtual methods. That may impose some ambiguity at compile-time but that’s usually easy to solve.
On the other hand, with the virtual inheritance it too easily goes out of control (and then becomes a mess). Consider as an example a “heart” diagram:
A A
/ \ / \
B C D E
\ / \ /
F G
\ /
H
In C++ it is entirely impossible: as soon as F
and G
are merged into a single class, their A
s are merged too, period. That means you may never consider base classes opaque in C++ (in this example you have to construct A
in H
so you have to know that it present somewhere in the hierarchy). In other languages it may work, however; for example, F
and G
could explicitly declare A as “internal,” thus forbidding consequent merging and effectively making themselves solid.
Another interesting example (not C++-specific):
A
/ \
B B
| |
C D
\ /
E
Here, only B
uses virtual inheritance. So E
contains two B
s that share the same A
. This way, you can get an A*
pointer that points to E
, but you can’t cast it to a B*
pointer although the object is actually B
as such cast is ambiguous, and this ambiguity can’t be detected at compile time (unless the compiler sees the whole program). Here is the test code:
struct A { virtual ~A() {} /* so that the class is polymorphic */ };
struct B: virtual A {};
struct C: B {};
struct D: B {};
struct E: C, D {};
int main() {
E data;
E *e = &data;
A *a = dynamic_cast<A *>(e); // works, A is unambiguous
// B *b = dynamic_cast<B *>(e); // doesn't compile
B *b = dynamic_cast<B *>(a); // NULL: B is ambiguous
std::cout << "E: " << e << std::endl;
std::cout << "A: " << a << std::endl;
std::cout << "B: " << b << std::endl;
// the next casts work
std::cout << "A::C::B: " << dynamic_cast<B *>(dynamic_cast<C *>(e)) << std::endl;
std::cout << "A::D::B: " << dynamic_cast<B *>(dynamic_cast<D *>(e)) << std::endl;
std::cout << "A=>C=>B: " << dynamic_cast<B *>(dynamic_cast<C *>(a)) << std::endl;
std::cout << "A=>D=>B: " << dynamic_cast<B *>(dynamic_cast<D *>(a)) << std::endl;
return 0;
}
Moreover, the implementation may be very complex (depends on language; see benjismith’s answer).
참고URL : https://stackoverflow.com/questions/225929/what-is-the-exact-problem-with-multiple-inheritance
'program story' 카테고리의 다른 글
Java에서 toString 메소드를 사용하는 방법은 무엇입니까? (0) | 2020.07.25 |
---|---|
여러 빌드 구성에 대해 다른 app.config를 선택하는 방법 (0) | 2020.07.25 |
iFrame jQuery에서 요소 선택 (0) | 2020.07.25 |
Android 앱의 일반적인 .gitignore 파일 (0) | 2020.07.25 |
이미지에서 픽셀의 x, y 좌표 색상을 얻는 방법은 무엇입니까? (0) | 2020.07.25 |