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.