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.

6voto

Turing Complete Punkte 929

Ich denke nicht, dass das Diamantenproblem ein Problem ist, ich würde das als Spitzfindigkeit betrachten, nichts anderes.

Das schlimmste Problem bei der Mehrfachvererbung sind meiner Meinung nach RAD-Opfer und Leute, die sich als Entwickler ausgeben, in Wirklichkeit aber bestenfalls Halbwissen haben.

Ich persönlich wäre sehr froh, wenn ich endlich so etwas in Windows Forms machen könnte (es ist kein korrekter Code, aber es sollte Ihnen eine Vorstellung davon geben):

public sealed class CustomerEditView : Form, MVCView<Customer>

Dies ist das Hauptproblem, das ich damit habe, dass es keine Mehrfachvererbung gibt. Sie KÖNNEN etwas Ähnliches mit Schnittstellen machen, aber es gibt etwas, das ich "s*** Code" nenne, es ist dieser schmerzhafte, sich wiederholende c***, den Sie in jeder Ihrer Klassen schreiben müssen, um z.B. einen Datenkontext zu erhalten.

Meiner Meinung nach sollte es in einer modernen Sprache absolut keine Notwendigkeit, nicht die geringste, für JEDE Wiederholung von Code geben.

0 Stimmen

Ich neige dazu, zuzustimmen, aber nur tendenziell: In jeder Sprache ist eine gewisse Redundanz erforderlich, um Fehler zu erkennen. Auf jeden Fall sollten Sie sich dem Felix-Entwicklerteam anschließen, denn das ist ein Kernziel. Zum Beispiel sind alle Deklarationen gegenseitig rekursiv, und man kann sowohl vorwärts als auch rückwärts sehen, so dass man keine Vorwärtsdeklarationen braucht (der Gültigkeitsbereich ist mengenmäßig, wie bei C goto labels).

0 Stimmen

Ich stimme dem voll und ganz zu - ich bin gerade auf ein ähnliches Problem gestoßen aquí . Die Leute reden über das Diamantenproblem, sie zitieren es religiös, aber meiner Meinung nach ist es so einfach vermieden. (Wir müssen nicht alle unsere Programme so schreiben, wie sie die iostream-Bibliothek geschrieben haben). Mehrfachvererbung sollte logischerweise verwendet werden, wenn Sie ein Objekt haben, das die Funktionalität von zwei verschiedenen Basisklassen benötigt, die keine überlappenden Funktionen oder Funktionsnamen haben. In den richtigen Händen ist es ein Werkzeug.

3 Stimmen

@Turing Complete: Was die Vermeidung von Codewiederholungen angeht: Das ist eine schöne Idee, aber sie ist falsch und unmöglich. Es gibt eine riesige Anzahl von Verwendungsmustern, und wir möchten gängige Muster in die Bibliothek abstrahieren, aber es ist Wahnsinn, sie alle zu abstrahieren, denn selbst wenn wir das könnten, wäre die semantische Last, sich alle Namen zu merken, zu hoch. Was Sie wollen, ist ein gutes Gleichgewicht. Vergessen Sie nicht, dass Wiederholungen den Dingen Struktur geben (Muster impliziert Redundanz).

5voto

Mendelt Punkte 35649

Das Hauptproblem bei der Mehrfachvererbung wird durch das Beispiel von tloach sehr schön auf den Punkt gebracht. Wenn man von mehreren Basisklassen erbt, die dieselbe Funktion oder dasselbe Feld implementieren, muss der Compiler eine Entscheidung darüber treffen, welche Implementierung er erben soll.

Dies wird noch schlimmer, wenn Sie von mehreren Klassen erben, die von derselben Basisklasse erben. (Diamantvererbung, wenn Sie den Vererbungsbaum zeichnen, erhalten Sie eine Rautenform)

Diese Probleme sind für einen Compiler nicht wirklich problematisch zu lösen. Aber die Entscheidungen, die der Compiler hier treffen muss, sind ziemlich willkürlich, was den Code weit weniger intuitiv macht.

Ich finde, dass ich bei einem guten OO-Design niemals Mehrfachvererbung brauche. In den Fällen, in denen ich sie benötige, stelle ich in der Regel fest, dass ich die Vererbung zur Wiederverwendung von Funktionalität verwendet habe, während die Vererbung nur für "ist-a"-Beziehungen geeignet ist.

Es gibt andere Techniken wie Mixins, die die gleichen Probleme lösen und nicht die Probleme haben, die die Mehrfachvererbung hat.

4 Stimmen

Die zusammengestellten nicht eine willkürliche Auswahl treffen müssen - es kann einfach ein Fehler auftreten. In C#, was ist der Typ von ([..bool..]? "test": 1) ?

5 Stimmen

In C++ trifft der Compiler niemals solche willkürlichen Entscheidungen: Es ist ein Fehler, eine Klasse zu definieren, bei der der Compiler eine willkürliche Entscheidung treffen müsste.

4voto

Frank Shearar Punkte 16759

Das Common Lisp Object System (CLOS) ist ein weiteres Beispiel für etwas, das MI unterstützt und gleichzeitig die Probleme im Stil von C++ vermeidet: Die Vererbung wird mit einem vernünftige Vorgabe und lässt Ihnen dennoch die Freiheit, explizit zu entscheiden, wie genau Sie beispielsweise das Verhalten eines Hausmeisters aufrufen wollen.

0 Stimmen

Ja, CLOS ist eines der besten Objektsysteme seit den Anfängen der modernen Computertechnik, vielleicht sogar schon lange vorher :)

3voto

Christian Lemer Punkte 863

An der Mehrfachvererbung selbst gibt es nichts auszusetzen. Das Problem besteht darin, die Mehrfachvererbung einer Sprache hinzuzufügen, die nicht von Anfang an mit Blick auf die Mehrfachvererbung entwickelt wurde.

Die Eiffel-Sprache unterstützt Mehrfachvererbung ohne Einschränkungen in einer sehr effizienten und produktiven Art und Weise, aber die Sprache wurde von Anfang an so konzipiert, dass sie dies unterstützt.

Dieses Feature ist für Compiler-Entwickler komplex zu implementieren, aber es scheint, dass dieser Nachteil durch die Tatsache kompensiert werden könnte, dass eine gute Unterstützung der Mehrfachvererbung die Unterstützung anderer Features vermeiden könnte (d.h. keine Notwendigkeit für Interface oder Extension Method).

Ich denke, dass die Unterstützung der Mehrfachvererbung eher eine Frage der Entscheidung, eine Frage der Prioritäten ist. Eine komplexere Funktion braucht mehr Zeit, um korrekt implementiert und funktionsfähig zu sein, und ist möglicherweise umstrittener. Die C++-Implementierung könnte der Grund dafür sein, dass Mehrfachvererbung nicht in C# und Java implementiert wurde...

1 Stimmen

C++-Unterstützung für MI ist nicht " sehr effizient und produktiv "?

1 Stimmen

Eigentlich ist es etwas kaputt in dem Sinne, dass es nicht mit anderen Funktionen von C++ zusammenpasst. Zuweisung funktioniert nicht richtig mit Vererbung, geschweige denn mit Mehrfachvererbung (sehen Sie sich die wirklich schlechten Regeln an). Die korrekte Erstellung von Diamanten ist so schwierig, dass das Standardisierungskomitee die Ausnahmehierarchie vermasselt hat, um sie einfach und effizient zu halten, anstatt sie korrekt zu gestalten. Auf einem älteren Compiler, den ich zu der Zeit verwendete, als ich dies testete, kosteten ein paar MI-Mixins und Implementierungen grundlegender Ausnahmen über ein Megabyte Code und brauchten 10 Minuten zum Kompilieren nur die Definitionen.

1 Stimmen

Diamonds ist ein gutes Beispiel. In Eiffel wird der Diamant explizit aufgelöst. Stellen Sie sich zum Beispiel vor, dass Student und Teacher beide von Person erben. Person hat einen Kalender, so dass sowohl Student als auch Teacher diesen Kalender erben werden. Wenn Sie einen Diamanten erstellen, indem Sie ein TeachingStudent erstellen, das sowohl von Teacher als auch von Student erbt, können Sie sich entscheiden, einen der geerbten Kalender umzubenennen, um beide Kalender separat verfügbar zu halten, oder sie zu verschmelzen, so dass es sich mehr wie Person verhält. Mehrfachvererbung kann gut implementiert werden, erfordert aber einen sorgfältigen Entwurf und vorzugsweise von Anfang an...

3voto

number Zero Punkte 56

Der Diamant ist kein Problem, solange Sie nicht so etwas wie die virtuelle Vererbung in C++ zu verwenden: Bei der normalen Vererbung ähnelt jede Basisklasse einem Mitgliedsfeld (tatsächlich sind sie in RAM auf diese Weise angeordnet), was Ihnen einen gewissen syntaktischen Zucker und eine zusätzliche Möglichkeit bietet, weitere virtuelle Methoden zu überschreiben. Das kann zur Kompilierzeit zu einer gewissen Mehrdeutigkeit führen, aber das ist normalerweise leicht zu lösen.

Andererseits gerät das virtuelle Erbe allzu leicht außer Kontrolle (und wird dann zum Chaos). Nehmen wir als Beispiel ein "Herz"-Diagramm:

  A       A
 / \     / \
B   C   D   E
 \ /     \ /
  F       G
    \   /
      H

In C++ ist das völlig unmöglich: Sobald F y G zu einer einzigen Klasse verschmolzen werden, deren A s werden ebenfalls zusammengeführt, Punkt. Das bedeutet, dass Sie Basisklassen in C++ niemals als undurchsichtig betrachten dürfen (in diesem Beispiel müssen Sie A en H Sie müssen also wissen, dass es irgendwo in der Hierarchie vorhanden ist). In anderen Sprachen kann es jedoch funktionieren, zum Beispiel, F y G könnten A ausdrücklich als "intern" deklarieren, was eine konsequente Verschmelzung verbietet und sich selbst zu einer festen Größe macht.

Ein weiteres interessantes Beispiel ( pas C++-spezifisch):

  A
 / \
B   B
|   |
C   D
 \ /
  E

Hier wird nur B verwendet virtuelle Vererbung. Also E enthält zwei B s, die die gleiche A . Auf diese Weise können Sie eine A* Zeiger, der auf E aber man kann es nicht auf eine B* Zeiger, obwohl das Objekt ist eigentlich B ist mehrdeutig, und diese Mehrdeutigkeit kann zur Kompilierzeit nicht erkannt werden (es sei denn, der Compiler sieht das gesamte Programm). Hier ist der Testcode:

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;
}

Außerdem kann die Implementierung sehr komplex sein (je nach Sprache; siehe die Antwort von benjismith).

0 Stimmen

Das ist das eigentliche Problem von MI. Programmierer können innerhalb einer Klasse unterschiedliche Auflösungen benötigen. Eine sprachenweite Lösung würde die Möglichkeiten einschränken und die Programmierer dazu zwingen, Tricks zu entwickeln, damit das Programm korrekt funktioniert.

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