Pourquoi l'héritage multiple est-il possible en C ++, mais pas en C #?
Je pense (sans référence précise), qu'en Java, ils voulaient limiter l'expressivité du langage pour le rendre plus facile à apprendre et parce que le code utilisant l'héritage multiple est plus souvent trop complexe pour son propre bien qu'improbable. Et parce que l'héritage multiple complet est beaucoup plus compliqué à implémenter, il a donc beaucoup simplifié la machine virtuelle aussi (l'héritage multiple interagit particulièrement mal avec le ramasse-miettes, car il nécessite de garder des pointeurs au milieu de l'objet (au début de la base) )
Et lors de la conception de C #, je pense qu'ils ont regardé Java, ont vu que l'héritage multiple complet n'était en effet pas beaucoup manqué et ont choisi de garder les choses simples aussi.
Comment C ++ résout-il l'ambiguïté des signatures de méthodes identiques héritées de plusieurs classes de base?
Ce n'est pas le cas . Il existe une syntaxe pour appeler explicitement la méthode de la classe de base à partir d'une base spécifique, mais il n'y a aucun moyen de remplacer une seule des méthodes virtuelles et si vous ne remplacez pas la méthode dans la sous-classe, il n'est pas possible de l'appeler sans spécifier la base classe.
Et pourquoi le même design n'est-il pas incorporé dans C #?
Il n'y a rien à incorporer.
Puisque Giorgio a mentionné les méthodes d'extension d'interface dans les commentaires, je vais expliquer ce que sont les mixins et comment ils sont implémentés dans différentes langues.
Les interfaces en Java et C # se limitent à déclarer des méthodes uniquement. Mais les méthodes doivent être implémentées dans chaque classe qui hérite de l'interface. Il existe cependant une grande classe d'interfaces, où il serait utile de fournir des implémentations par défaut de certaines méthodes en termes d'autres. Un exemple courant est comparable (en pseudo-langage):
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; }
};
La différence avec la classe complète est qu'elle ne peut contenir aucun membre de données. Il existe plusieurs options pour l'implémenter. De toute évidence, l'héritage multiple en est un. Mais l'héritage multiple est plutôt compliqué à mettre en œuvre. Mais ce n'est pas vraiment nécessaire ici. Au lieu de cela, de nombreux langages implémentent cela en divisant le mixin dans une interface, qui est implémentée par la classe et un référentiel d'implémentations de méthodes, qui sont soit injectées dans la classe elle-même, soit une classe de base intermédiaire est générée et y est placée. Ceci est implémenté dans Ruby et D , sera implémenté dans Java 8 et peut être implémenté manuellement en C ++ en utilisant le modèle de modèle curieusement récurrent . Ce qui précède, sous forme de CRTP, ressemble à:
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(); }
...
};
et s'utilise comme:
class Concrete : public IComparable<Concrete> { ... };
Cela ne nécessite rien d'être déclaré virtuel comme le ferait une classe de base normale, donc si l'interface est utilisée dans des modèles, les options d'optimisation utiles restent ouvertes. Notez qu'en C ++, cela serait probablement encore hérité en tant que deuxième parent, mais dans les langages qui n'autorisent pas l'héritage multiple, il est inséré dans la chaîne d'héritage unique, donc c'est plus comme
template <typename Derived, typename Base>
class IComparable : public Base { ... };
class Concrete : public IComparable<Concrete, Base> { ... };
L'implémentation du compilateur peut ou non éviter la répartition virtuelle.
Une implémentation différente a été sélectionnée en C #. En C #, les implémentations sont des méthodes statiques de classe complètement distincte et la syntaxe d'appel de méthode est correctement interprétée par le compilateur si une méthode de nom donné n'existe pas, mais une "méthode d'extension" est définie. Cela a l'avantage que des méthodes d'extension peuvent être ajoutées à une classe déjà compilée et l'inconvénient que ces méthodes ne peuvent pas être remplacées, par exemple pour fournir une version optimisée.