Wie behandelt C ++ die Mehrfachvererbung mit einem gemeinsamen Vorfahren?

13

Ich bin kein C ++ - Typ, aber ich bin gezwungen, darüber nachzudenken. Warum ist Mehrfachvererbung in C ++ möglich, aber nicht in C #? (Ich kenne das Diamantenproblem , aber das frage ich hier nicht). Wie löst C ++ die Mehrdeutigkeit identischer Methodensignaturen auf, die von mehreren Basisklassen geerbt wurden? Und warum wird dasselbe Design nicht in C # integriert?

Sandeep
quelle
3
Was ist der Vollständigkeit halber bitte das "Diamantproblem"?
Jcolebrand
1
@jcolebrand en.wikipedia.org/wiki/Multiple_inheritance siehe Diamond Problem
l46kok
1
Klar, ich habe gegoogelt, aber woher weiß ich, dass Sandeep das bedeutet? Wenn Sie etwas mit einem obskuren Namen referenzieren wollen, wenn "Mehrfachvererbung mit einem gemeinsamen Vorfahren" direkter ist ...
jcolebrand
1
@jcolebrand Ich habe es bearbeitet, um zu reflektieren, was ich von der Frage bekommen habe. Ich nehme an, er meint das Diamantproblem, auf das in Wikipedia aus dem Kontext verwiesen wird.
Lassen Sie das
1
@Earlz das funktioniert nur wenn ich die referenz verstehe. Ansonsten mache ich einen schlechten Schnitt, und das ist nur schlechter Juju.
Jcolebrand

Antworten:

24

Warum ist Mehrfachvererbung in C ++ möglich, aber nicht in C #?

Ich denke (ohne einen genauen Bezug zu haben), dass sie in Java die Ausdruckskraft der Sprache einschränken wollten, um das Erlernen der Sprache zu vereinfachen, und weil Code, der Mehrfachvererbung verwendet, häufig zu komplex für das eigene Wohl ist als nicht. Und weil eine vollständige Mehrfachvererbung viel komplizierter zu implementieren ist, hat dies auch die virtuelle Maschine erheblich vereinfacht (die Mehrfachvererbung interagiert besonders schlecht mit dem Garbage Collector, da Zeiger in der Mitte des Objekts (am Anfang der Basis) aufbewahrt werden müssen). )

Und beim Entwerfen von C # haben sie, glaube ich, Java betrachtet und festgestellt, dass die vollständige Mehrfachvererbung in der Tat nicht sehr verfehlt wurde, und beschlossen, die Dinge auch einfach zu halten.

Wie löst C ++ die Mehrdeutigkeit identischer Methodensignaturen auf, die von mehreren Basisklassen geerbt wurden?

Das tut es nicht . Es gibt eine Syntax zum expliziten Aufrufen der Basisklassenmethode von einer bestimmten Basis, aber es gibt keine Möglichkeit, nur eine der virtuellen Methoden zu überschreiben. Wenn Sie die Methode in der Unterklasse nicht überschreiben, ist es nicht möglich, sie ohne Angabe der Basis aufzurufen Klasse.

Und warum wird dasselbe Design nicht in C # integriert?

Es gibt nichts zu integrieren.


Da Giorgio in Kommentaren die Erweiterungsmethoden der Benutzeroberfläche erwähnt hat, erkläre ich, was Mixins sind und wie sie in verschiedenen Sprachen implementiert sind.

Schnittstellen in Java und C # dürfen nur Methoden deklarieren. Die Methoden müssen jedoch in jeder Klasse implementiert werden, die die Schnittstelle erbt. Es gibt jedoch eine große Klasse von Schnittstellen, bei denen es nützlich wäre, Standardimplementierungen einiger Methoden in Bezug auf andere bereitzustellen. Allgemeines Beispiel ist vergleichbar (in Pseudo-Sprache):

mixin IComparable {
    public bool operator<(IComparable r) = 0;
    public bool operator>(IComparable r) { return r < this; }
    public bool operator<=(IComparable r) { return !(r < this); }
    public bool operator>=(IComparable r) { return !(r > this); }
    public bool operator==(IComparable r) { return !(r < this) && !(r > this); }
    public bool operator!=(IComparable r) { return r < this || r > this; }
};

Der Unterschied zur vollständigen Klasse besteht darin, dass diese keine Datenelemente enthalten kann. Es gibt verschiedene Möglichkeiten, dies umzusetzen. Offensichtlich ist Mehrfachvererbung eine. Die Implementierung der Mehrfachvererbung ist jedoch ziemlich kompliziert. Aber hier wird es nicht wirklich gebraucht. Stattdessen implementieren viele Sprachen dies, indem sie das Mixin in eine Schnittstelle aufteilen, die von der Klasse implementiert wird, und ein Repository von Methodenimplementierungen, die entweder in die Klasse selbst injiziert werden oder eine Zwischenbasisklasse generiert und dort platziert werden. Dies ist in Ruby und D implementiert, wird in Java 8 implementiert und kann manuell in C ++ unter Verwendung des seltsamerweise wiederkehrenden Schablonenmusters implementiert werden . Das obige sieht in CRTP-Form so aus:

template <typename Derived>
class IComparable {
    const Derived &_d() const { return static_cast<const Derived &>(*this); }
public:
    bool operator>(const IComparable &r) const { r._d() < _d(); }
    bool operator<=(const IComparable &r) const { !(r._d() < _d(); }
    ...
};

und wird verwendet wie:

class Concrete : public IComparable<Concrete> { ... };

Hierfür muss nichts als virtuelle Basisklasse deklariert werden. Wenn also die Schnittstelle in Vorlagen verwendet wird, bleiben nützliche Optimierungsoptionen offen. Beachten Sie, dass dies in C ++ wahrscheinlich immer noch als zweites übergeordnetes Element vererbt wird. In Sprachen, die keine Mehrfachvererbung zulassen, wird es jedoch in die einzelne Vererbungskette eingefügt

template <typename Derived, typename Base>
class IComparable : public Base { ... };
class Concrete : public IComparable<Concrete, Base> { ... };

Die Compiler-Implementierung kann den virtuellen Versand möglicherweise vermeiden oder nicht.

In C # wurde eine andere Implementierung ausgewählt. In C # sind die Implementierungen statische Methoden mit vollständig separater Klasse, und die Methodenaufrufsyntax wird vom Compiler entsprechend interpretiert, wenn keine Methode mit dem angegebenen Namen vorhanden ist, aber eine "Erweiterungsmethode" definiert ist. Dies hat den Vorteil, dass Erweiterungsmethoden zu bereits kompilierten Klassen hinzugefügt werden können und den Nachteil, dass solche Methoden nicht überschrieben werden können, um beispielsweise eine optimierte Version bereitzustellen.

Jan Hudec
quelle
Man kann erwähnen, dass die Mehrfachvererbung mit Schnittstellenerweiterungsmethoden in Java 8 eingeführt wird.
Giorgio
@ Giorgio: Nein, Mehrfachvererbung wird in Java definitiv nicht eingeführt. Mixins werden es sein, was sehr unterschiedlich ist, obwohl es viele verbleibende Gründe für die Verwendung der Mehrfachvererbung und die meisten Gründe für die Verwendung des seltsam wiederkehrenden Vorlagenmusters (CRTP) abdeckt und meistens wie CRTP und überhaupt nicht wie Mehrfachvererbung funktioniert.
Jan Hudec
Ich denke , dass die Mehrfachvererbung nicht erforderlich Zeiger in Mitte eines Objekts. In diesem Fall wäre dies auch für die Vererbung mehrerer Schnittstellen erforderlich.
Svick
@svick: Nein, das tut es nicht. Die Alternative ist jedoch weitaus weniger effizient, da für den Mitgliederzugriff ein virtueller Versand erforderlich ist.
Jan Hudec
+1 für eine viel vollständigere Antwort als meine.
Nathan C. Tresch
2

Die Antwort ist, dass es in C ++ bei Namespace-Konflikten nicht richtig funktioniert. Sieh das . Um das Aufeinandertreffen von Namespaces zu vermeiden, müssen Sie alle möglichen Bewegungen mit Zeigern ausführen. Ich habe bei MS im Visual Studio-Team gearbeitet und zumindest teilweise die Delegation entwickelt, um die Kollision von Namespaces insgesamt zu vermeiden. Vorweg hatte ich gesagt, dass sie Schnittstellen auch als Teil der Mehrfachvererbungslösung betrachteten, aber ich habe mich geirrt. Interfaces sind erstaunlich und können in C ++, FWIW zum Laufen gebracht werden.

Die Delegierung befasst sich speziell mit Namespace-Kollisionen: Sie können an 5 Klassen delegieren, und alle 5 von ihnen exportieren ihre Methoden als First-Class-Mitglieder in Ihren Bereich. Äußerlich betrachtet ist dies Mehrfachvererbung.

Nathan C. Tresch
quelle
1
Ich halte es für sehr unwahrscheinlich, dass dies der Hauptgrund dafür war, MI nicht in C # aufzunehmen. Außerdem beantwortet dieser Beitrag nicht die Frage, warum MI in C ++ funktioniert, wenn Sie keine "Namespace-Konflikte" haben.
Doc Brown
@DocBrown Ich habe bei MS im Visual Studio-Team gearbeitet und ich versichere Ihnen, dass dies zumindest teilweise der Grund ist, warum sie Delegierungen und Schnittstellen entwickelt haben, um eine Namensraumkollision insgesamt zu vermeiden. Was die Qualität der Frage betrifft, meh. Verwenden Sie Ihre Downvote, andere scheinen zu denken, dass es nützlich ist.
Nathan C. Tresch
1
Ich habe nicht die Absicht, Ihre Antwort abzulehnen, da ich sie für richtig halte. Aber Ihre Antwort gibt vor, dass MI in C ++ völlig unbrauchbar ist, und das ist der Grund, warum sie es nicht in C # eingeführt haben. Obwohl ich MI in C ++ nicht sehr mag, denke ich, dass es nicht völlig unlösbar ist.
Doc Brown
1
Das ist einer der Gründe, und ich wollte nicht implizieren, dass es nie funktioniert. Wenn etwas nicht deterministisch im Rechnen ist, neige ich dazu zu sagen, dass es "kaputt" ist oder dass es "nicht funktioniert", wenn ich damit sagen will: "Es ist wie Voodoo, es kann funktionieren oder nicht, zünde deine Kerzen an und bete." : D
Nathan C. Tresch
1
Sind "Namespace-Kollisionen" nicht deterministisch?
Doc Brown