Comment C ++ gère-t-il l'héritage multiple avec un ancêtre commun partagé?

13

Je ne suis pas un gars en C ++, mais je suis obligé d'y penser. Pourquoi l'héritage multiple est-il possible en C ++, mais pas en C #? (Je connais le problème du diamant , mais ce n'est pas ce que je demande ici). Comment C ++ résout-il l'ambiguïté des signatures de méthodes identiques héritées de plusieurs classes de base? Et pourquoi le même design n'est-il pas incorporé dans C #?

Sandeep
la source
3
Par souci d'exhaustivité, quel est le "problème du diamant" s'il vous plaît?
jcolebrand
1
@jcolebrand en.wikipedia.org/wiki/Multiple_inheritance voir Diamond problem
l46kok
1
Bien sûr, je l'ai googlé, mais comment puis-je savoir que c'est ce que Sandeep signifie? Si vous allez faire référence à quelque chose par un nom obscur, quand "l'héritage multiple avec un ancêtre commun partagé" est plus direct ...
jcolebrand
1
@jcolebrand Je l'ai édité pour refléter ce que j'ai obtenu de la question. Je suppose qu'il veut dire le problème des diamants référencé sur wikipedia du contexte. La prochaine fois, déposez le commentaire amical ET utilisez le nouvel outil d'édition suggéré brillant :)
Earlz
1
@Earlz qui ne fonctionne que lorsque je comprends la référence. Sinon, je fais un mauvais montage, et c'est juste du mauvais juju.
jcolebrand

Réponses:

24

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.

Jan Hudec
la source
On peut mentionner que l'héritage multiple sera introduit dans Java 8 avec des méthodes d'extension d'interface.
Giorgio
@Giorgio: Non, l'héritage multiple ne sera certainement pas introduit en Java. Les mixins le seront, ce qui est très différent, bien qu'il couvre de nombreuses raisons restantes d'utiliser l'héritage multiple et la plupart des raisons d'utiliser un modèle de modèle curieusement récurrent (CRTP) et fonctionne principalement comme CRTP, pas comme l'héritage multiple du tout.
Jan Hudec
Je pense que l'héritage multiple ne nécessite pas de pointeurs au milieu d'un objet. Si tel était le cas, l'héritage d'interfaces multiples le nécessiterait également.
svick
@svick: Non, ce n'est pas le cas. Mais l'alternative est beaucoup moins efficace car elle nécessite une répartition virtuelle pour l'accès des membres.
Jan Hudec
+1 pour une réponse beaucoup plus complète que la mienne.
Nathan C. Tresch
2

La réponse est qu'elle ne fonctionne pas correctement en C ++ en cas de conflits d'espace de noms. Regardez ça . Pour éviter les conflits d'espace de noms, vous devez faire toutes sortes de girations avec des pointeurs. J'ai travaillé chez MS sur l'équipe Visual Studio et j'ai au moins en partie la raison pour laquelle ils ont développé la délégation était d'éviter la collision d'espace de noms. Avant, j'avais dit qu'ils considéraient également les interfaces comme faisant partie de la solution d'héritage multiple, mais je me trompais. Les interfaces sont réellement incroyables et peuvent être conçues pour fonctionner en C ++, FWIW.

La délégation traite spécifiquement de la collision d'espace de noms: vous pouvez déléguer à 5 classes et les 5 d'entre elles exporteront leurs méthodes vers votre portée en tant que membres de première classe. À l'extérieur, la recherche dans cet héritage multiple est.

Nathan C. Tresch
la source
1
Je trouve très peu probable que ce soit la principale raison de ne pas inclure MI dans C #. En outre, ce message ne répond pas à la question de savoir pourquoi MI fonctionne en C ++ lorsque vous n'avez pas de "conflits d'espace de noms".
Doc Brown
@DocBrown J'ai travaillé chez MS au sein de l'équipe Visual Studio et je vous assure que c'est au moins en partie la raison pour laquelle ils ont développé la délégation et les interfaces, pour éviter complètement les collisions d'espace de noms. Quant à la qualité de la question, meh. Utilisez votre downvote, d'autres semblent penser que c'est utile.
Nathan C. Tresch
1
Je n'ai pas l'intention de dévaloriser votre réponse car je pense qu'elle est correcte. Mais votre réponse prétend que MI est complètement inutilisable en C ++, et c'est la raison pour laquelle ils ne l'ont pas introduit en C #. Bien que je n'aime pas beaucoup MI en C ++, je pense qu'il n'est pas complètement inutilisable.
Doc Brown
1
C'est l'une des raisons, et je ne voulais pas laisser entendre que cela ne fonctionne jamais. Quand quelque chose n'est pas déterministe dans l'informatique, j'ai tendance à dire que c'est "cassé", ou que cela "ne fonctionne pas" quand j'ai l'intention de dire "c'est comme le vaudou, ça peut ou peut ne pas fonctionner, allumez vos bougies et priez". : D
Nathan C. Tresch
1
les «collisions d'espace de noms» ne sont-elles pas déterministes?
Doc Brown