Eines der Entwurfsziele von Frameworks wie Java und .NET besteht darin, dass Code, der für eine Version einer vorkompilierten Bibliothek kompiliert wurde, ebenso gut mit nachfolgenden Versionen dieser Bibliothek funktioniert, selbst wenn diese nachfolgenden Versionen neue Funktionen hinzufügen. Während das normale Paradigma in Sprachen wie C oder C++ darin besteht, statisch gelinkte ausführbare Dateien zu verteilen, die alle benötigten Bibliotheken enthalten, besteht das Paradigma in .NET und Java darin, Anwendungen als Sammlungen von Komponenten zu verteilen, die zur Laufzeit "gelinkt" werden.
Das COM-Modell, das .NET vorausging, versuchte, diesen allgemeinen Ansatz zu verwenden, aber es gab keine wirkliche Vererbung - stattdessen definierte jede Klassendefinition effektiv sowohl eine Klasse als auch eine gleichnamige Schnittstelle, die alle ihre öffentlichen Mitglieder enthielt. Instanzen waren vom Typ der Klasse, während Referenzen vom Typ der Schnittstelle waren. Die Deklaration einer Klasse als Ableitung von einer anderen war gleichbedeutend mit der Deklaration einer Klasse als Implementierung der Schnittstelle der anderen und erforderte, dass die neue Klasse alle öffentlichen Mitglieder der Klassen, von denen sie abgeleitet war, neu implementierte. Wenn Y und Z von X abgeleitet sind und W von Y und Z abgeleitet ist, spielt es keine Rolle, ob Y und Z die Mitglieder von X unterschiedlich implementieren, da Z nicht in der Lage ist, deren Implementierungen zu verwenden - es muss seine eigenen definieren. W könnte Instanzen von Y und/oder Z kapseln und seine Implementierungen von X's Methoden durch ihre verketten, aber es gäbe keine Unklarheit darüber, was X's Methoden tun sollten - sie würden tun, was auch immer der Code von Z ihnen explizit vorgibt.
Die Schwierigkeit in Java und .NET besteht darin, dass es dem Code erlaubt ist, Mitglieder zu erben und auf sie zuzugreifen implizit beziehen sich auf die übergeordneten Mitglieder. Angenommen, die Klassen W-Z sind wie oben beschrieben miteinander verbunden:
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();
}
}
Es scheint, als ob W.Test()
sollte bei der Erzeugung einer Instanz von W die Implementierung der virtuellen Methode Foo
definiert in X
. Nehmen wir jedoch an, Y und Z befänden sich in einem separat kompilierten Modul, und obwohl sie wie oben definiert wurden, als X und W kompiliert wurden, wurden sie später geändert und neu kompiliert:
class Y : X { public override void Foo() { Console.WriteLine("YFoo"); }
class Z : X { public override void Foo() { Console.WriteLine("ZFoo"); }
Was soll nun der Aufruf von W.Test()
? Wenn das Programm vor der Verteilung statisch gelinkt werden muss, könnte die statische Linkstufe erkennen, dass das Programm vor der Änderung von Y und Z keine Mehrdeutigkeit aufwies, dass aber die Änderungen an Y und Z die Dinge mehrdeutig gemacht haben, und der Linker könnte sich weigern, das Programm zu erstellen, bis diese Mehrdeutigkeit behoben ist. Andererseits ist es möglich, dass die Person, die sowohl W als auch die neuen Versionen von Y und Z hat, jemand ist, der das Programm einfach nur ausführen will und keinen Quellcode dafür hat. Wenn W.Test()
läuft, wäre es nicht mehr klar, was W.Test()
tun sollte, aber bis der Benutzer versucht, W mit der neuen Version von Y und Z auszuführen, gäbe es keine Möglichkeit für irgendeinen Teil des Systems, zu erkennen, dass es ein Problem gibt (es sei denn, W wurde schon vor den Änderungen an Y und Z als unzulässig angesehen).
60 Stimmen
Ich möchte nur erwähnen, dass C++ großartig ist, um Ihnen genug Seil zu geben, um sich selbst aufzuhängen.
1 Stimmen
Eine Alternative zur Mehrfachvererbung, die viele der gleichen Probleme angeht (und, IMHO, auch löst), sind Traits ( iam.unibe.ch/~scg/Forschung/Traits )
54 Stimmen
Ich dachte, C++ gibt Ihnen genug Spielraum, um sich selbst in den Fuß zu schießen.
7 Stimmen
Diese Frage scheint davon auszugehen, dass es ein Problem mit MI im Allgemeinen gibt, während ich viele Sprachen gefunden habe, in denen MI gelegentlich verwendet wird. Es gibt sicherlich Probleme mit der Handhabung von MI in bestimmten Sprachen, aber ich bin mir nicht bewusst, dass MI im Allgemeinen signifikante Probleme hat.