133 Stimmen

Was genau ist das Problem mit der Mehrfachvererbung?

Ich sehe immer wieder, wie Leute fragen, ob Mehrfachvererbung in die nächste Version von C# oder Java aufgenommen werden soll. C++-Leute, die das Glück haben, diese Fähigkeit zu besitzen, sagen, dass dies so ist, als würde man jemandem einen Strick geben, an dem er sich aufhängen kann.

Was hat es mit der Mehrfachvererbung auf sich? Gibt es konkrete Beispiele?

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.

95voto

benjismith Punkte 16223

Das offensichtlichste Problem ist die Überschreibung von Funktionen.

Nehmen wir an, Sie haben zwei Klassen A y B , die beide eine Methode definieren doSomething . Nun definieren Sie eine dritte Klasse C , die sowohl von A y B aber Sie überschreiben nicht die doSomething Methode.

Wenn der Compiler diesen Code erzeugt...

C c = new C();
c.doSomething();

...welche Implementierung der Methode sollte sie verwenden? Ohne weitere Klärung ist es für den Compiler unmöglich, die Mehrdeutigkeit aufzulösen.

Neben dem Overriding ist das andere große Problem bei der Mehrfachvererbung die Anordnung der physischen Objekte im Speicher.

Sprachen wie C++, Java und C# erstellen ein festes adressbasiertes Layout für jede Art von Objekt. Etwa so:

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

Wenn der Compiler Maschinencode (oder Bytecode) erzeugt, verwendet er diese numerischen Offsets für den Zugriff auf die einzelnen Methoden oder Felder.

Die Mehrfachvererbung macht die Sache sehr kompliziert.

Wenn Klasse C erbt sowohl von A y B muss der Compiler entscheiden, ob er die Daten in AB Auftrag oder in BA bestellen.

Stellen Sie sich nun aber vor, Sie rufen Methoden auf einer B Gegenstand. Ist es wirklich nur ein B ? Oder ist es tatsächlich ein C Objekt, das polymorph aufgerufen wird, durch seine B Schnittstelle? Je nach der tatsächlichen Identität des Objekts wird das physische Layout unterschiedlich sein, und es ist unmöglich, den Offset der aufzurufenden Funktion am Aufrufort zu kennen.

Der Weg, mit dieser Art von System umzugehen, besteht darin, den Ansatz des festen Layouts aufzugeben und jedem Objekt zu erlauben, nach seinem Layout gefragt zu werden vor beim Versuch, die Funktionen aufzurufen oder auf ihre Felder zuzugreifen.

Also... lange Rede kurzer Sinn... es ist ein Schmerz im Nacken für Compiler-Autoren zur Unterstützung der Mehrfachvererbung. Wenn also jemand wie Guido van Rossum Python oder Anders Hejlsberg c# entwickelt, wissen sie, dass die Unterstützung von Mehrfachvererbung die Compiler-Implementierungen erheblich komplexer machen wird, und vermutlich sind sie der Meinung, dass der Nutzen die Kosten nicht wert ist.

0 Stimmen

Wenn Sie Ihre Antwort mit einer laienhaften Beschreibung des offensichtlichen Problems mit der Überschreibung aktualisieren, würde ich diese Antwort gerne als akzeptiert auswählen.

30 Stimmen

Das sind keine sehr überzeugenden Argumente - die Sache mit dem festen Layout ist in den meisten Sprachen überhaupt nicht schwierig; in C++ ist es schwierig, weil der Speicher nicht undurchsichtig ist und man daher Schwierigkeiten mit den Annahmen der Zeigerarithmetik bekommen kann. In Sprachen, in denen die Klasse Definitionen statisch sind (wie in Java, C# und C++), können Namenskonflikte bei der Mehrfachvererbung zur Kompilierungszeit verboten werden (und C# tut dies ohnehin mit Schnittstellen!).

11 Stimmen

Der Fragesteller wollte nur die Probleme verstehen, und ich habe sie erklärt, ohne mich persönlich in die Sache einzumischen. Ich habe nur gesagt, dass die Sprachentwickler und Compiler-Implementierer "vermutlich der Meinung sind, dass der Nutzen die Kosten nicht wert ist".

50voto

Die Probleme, die ihr erwähnt, sind nicht wirklich schwer zu lösen. In der Tat macht das z.B. Eiffel sehr gut! (und ohne willkürliche Entscheidungen oder ähnliches einzuführen)

Wenn Sie z.B. von A und B erben, die beide die Methode foo() haben, dann wollen Sie natürlich nicht, dass Ihre Klasse C willkürlich von A und B erbt. Sie müssen entweder foo umdefinieren, damit klar ist, was verwendet wird, wenn c.foo() aufgerufen wird, oder Sie müssen eine der Methoden in C umbenennen (sie könnte zu bar() werden).

Ich denke auch, dass Mehrfachvererbung oft recht nützlich ist. Wenn Sie sich die Eiffel-Bibliotheken ansehen, werden Sie sehen, dass sie überall verwendet wird, und ich persönlich habe diese Funktion vermisst, als ich zur Programmierung in Java zurückkehren musste.

29 Stimmen

Ich bin einverstanden. Der Hauptgrund, warum die Leute MI hassen, ist derselbe wie bei JavaScript oder der statischen Typisierung: Die meisten Leute haben nur sehr schlechte Implementierungen davon verwendet - oder sie haben sie sehr schlecht verwendet. MI anhand von C++ zu beurteilen ist wie OOP anhand von PHP oder Autos anhand von Pintos zu beurteilen.

1 Stimmen

Nur so aus Neugier: Was sind Ihrer Meinung nach gute Sprachen, um MI zu beurteilen?

1 Stimmen

Das Common Lisp Object System kann gut mit MI umgehen (auch wenn es in seiner Herangehensweise an OO recht eigenwillig ist). Wenn Sie sich für Textadventures interessieren, die heute oft als interaktive Fiktion bezeichnet werden, finden Sie auf ifarchive.org Sprachen, die stark auf MI basieren.

29voto

J Francis Punkte 1358

Das Diamantenproblem :

eine Mehrdeutigkeit, die entsteht, wenn zwei Klassen B und C von A erben und die Klasse D sowohl von B als auch von C erbt. Wenn es eine Methode in A gibt, die B und C haben außer Kraft gesetzt und D sie nicht überschreibt, welche Version der Methode erbt dann D: die von B oder die von C?

...Wegen der Form des Klassenvererbungsdiagramms in dieser Situation nennt man es das "Rautenproblem". In diesem Fall befindet sich die Klasse A oben, die beiden Klassen B und C getrennt darunter, und die Klasse D verbindet die beiden Klassen unten, so dass sie eine Rautenform bilden...

6 Stimmen

Die eine Lösung hat, die als virtuelle Vererbung bekannt ist. Es ist nur ein Problem, wenn man es falsch macht.

1 Stimmen

@IanGoldby: Die virtuelle Vererbung ist ein Mechanismus, um einen Teil des Problems zu lösen, wenn es nicht notwendig ist, identitätserhaltende Up- und Downcasts zwischen allen Typen zuzulassen, von denen eine Instanz abgeleitet ist oder für die sie substituierbar ist . Bei X:B; Y:B; und Z:X,Y; nehmen wir an, someZ ist eine Instanz von Z. Bei virtueller Vererbung sind (B)(X)someZ und (B)(Y)someZ unterschiedliche Objekte; wenn man eines von beiden hat, kann man das andere über einen Downcast und Upcast erhalten, aber was ist, wenn man einen someZ und möchte es an Object und dann an B ? Welche B wird es bekommen?

2 Stimmen

@supercat Vielleicht, aber solche Probleme sind weitgehend theoretisch und können auf jeden Fall vom Compiler gemeldet werden. Wichtig ist, dass man sich darüber im Klaren ist, welches Problem man zu lösen versucht, und dass man dann das beste Werkzeug einsetzt und das Dogma von Leuten ignoriert, die sich lieber nicht mit dem Verständnis des "Warum?" beschäftigen wollen.

21voto

KeithB Punkte 15939

Mehrfachvererbung gehört zu den Dingen, die nicht oft verwendet werden und missbraucht werden können, aber manchmal notwendig sind.

Ich habe es nie verstanden, eine Funktion nicht hinzuzufügen, nur weil sie missbraucht werden könnte, wenn es keine guten Alternativen gibt. Schnittstellen sind keine Alternative zur Mehrfachvererbung. Zum einen können Sie mit ihnen keine Vor- oder Nachbedingungen erzwingen. Wie bei jedem anderen Werkzeug müssen Sie wissen, wann und wie Sie es einsetzen sollten.

0 Stimmen

Können Sie erklären, warum Sie keine Vor- und Nachbedingungen durchsetzen können?

2 Stimmen

@Yttrill, weil Schnittstellen keine Methodenimplementierungen haben können. Wo setzen Sie die assert ?

1 Stimmen

@curiousguy: Sie verwenden eine Sprache mit einer geeigneten Syntax, die es Ihnen erlaubt, die Vor- und Nachbedingungen direkt in die Schnittstelle zu stellen: kein "assert" erforderlich. Beispiel von Felix: fun div(num: int, den: int when den != 0) : int expect result == 0 implies num == 0;

17voto

tloach Punkte 8001

Angenommen, Sie haben die Objekte A und B, die beide von C geerbt werden. A und B implementieren beide foo(), C nicht. Ich rufe C.foo() auf. Welche Implementierung wird gewählt? Es gibt noch andere Probleme, aber diese Art von Problemen ist eines der wichtigsten.

2 Stimmen

Aber das ist nicht wirklich ein konkretes Beispiel. Wenn sowohl A als auch B eine Funktion haben, ist es sehr wahrscheinlich, dass C auch eine eigene Implementierung benötigt. Ansonsten kann es immer noch A::foo() in seiner eigenen foo()-Funktion aufrufen.

0 Stimmen

@Quantum: Und wenn nicht? Es ist einfach, das Problem mit einer Ebene der Vererbung zu sehen, aber wenn Sie viele Ebenen haben und Sie haben einige zufällige Funktion, die irgendwo zweimal ist dies ein sehr schwieriges Problem wird.

0 Stimmen

Es geht auch nicht darum, dass man die Methode A oder B nicht aufrufen kann, indem man angibt, welche man will, sondern darum, dass es keine gute Möglichkeit gibt, eine zu wählen, wenn man sie nicht angibt. Ich bin mir nicht sicher, wie C++ das handhabt, aber wenn es jemand weiß, könnte er es erwähnen?

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X