Pourquoi devrais-je éviter l'héritage multiple en C ++?

167

Est-ce un bon concept d'utiliser l'héritage multiple ou puis-je faire d'autres choses à la place?

Hai
la source

Réponses:

259

L'héritage multiple (en abrégé MI) sent , ce qui signifie que généralement , il a été fait pour de mauvaises raisons, et il va se retourner contre le responsable.

Résumé

  1. Considérez la composition des fonctionnalités plutôt que l'héritage
  2. Méfiez-vous du diamant de l'effroi
  3. Considérez l'héritage de plusieurs interfaces au lieu d'objets
  4. Parfois, l'héritage multiple est la bonne chose. Si c'est le cas, utilisez-le.
  5. Soyez prêt à défendre votre architecture à héritage multiple dans les revues de code

1. Peut-être composition?

Cela est vrai pour l'héritage, et donc, c'est encore plus vrai pour l'héritage multiple.

Votre objet a-t-il vraiment besoin d'hériter d'un autre? A Carn'a pas besoin d'hériter d'un Engineto work, ni d'un Wheel. A Cara un Engineet quatre Wheel.

Si vous utilisez l'héritage multiple pour résoudre ces problèmes au lieu de la composition, c'est que vous avez fait quelque chose de mal.

2. Le diamant de l'effroi

Habituellement, vous avez une classe A, alors Bet les Cdeux héritent de A. Et (ne me demandez pas pourquoi) quelqu'un décide alors que Ddoit hériter à la fois de Bet C.

J'ai rencontré ce genre de problème deux fois en 8 huit ans, et c'est amusant à voir à cause de:

  1. À quel point c'était une erreur depuis le début (dans les deux cas, cela Dn'aurait pas dû hériter des deux Bet C), car c'était une mauvaise architecture (en fait, elle Cn'aurait pas dû exister du tout ...)
  2. Combien les mainteneurs payaient pour cela, car en C ++, la classe parente Aétait présente deux fois dans sa classe de petit-enfant D, et donc, mettre à jour un champ parent A::fieldsignifiait soit le mettre à jour deux fois (via B::fieldet C::field), soit faire en sorte que quelque chose se passe silencieusement et se bloque plus tard (nouveau un pointeur dans B::field, et supprimer C::field...)

Utiliser le mot-clé virtual en C ++ pour qualifier l'héritage évite la double mise en page décrite ci-dessus si ce n'est pas ce que vous voulez, mais de toute façon, d'après mon expérience, vous faites probablement quelque chose de mal ...

Dans la hiérarchie d'objets, vous devez essayer de conserver la hiérarchie sous forme d'arbre (un nœud a UN parent), et non sous forme de graphique.

En savoir plus sur le diamant (modifier le 03/05/2017)

Le vrai problème avec le Diamond of Dread en C ++ (en supposant que la conception soit saine - faites réviser votre code! ), C'est que vous devez faire un choix :

  • Est-il souhaitable que la classe Aexiste deux fois dans votre mise en page et qu'est-ce que cela signifie? Si oui, alors héritez-en deux fois.
  • s'il doit exister une seule fois, alors héritez-en virtuellement.

Ce choix est inhérent au problème, et en C ++, contrairement à d'autres langages, vous pouvez réellement le faire sans que le dogme ne force votre conception au niveau du langage.

Mais comme tous les pouvoirs, ce pouvoir s'accompagne de responsabilités: faites réviser votre conception.

3. Interfaces

L'héritage multiple de zéro ou une classe concrète et de zéro ou plusieurs interfaces est généralement correct, car vous ne rencontrerez pas le Diamond of Dread décrit ci-dessus. En fait, c'est ainsi que les choses se font en Java.

Habituellement, ce que vous voulez dire quand C hérite de Aet Best que les utilisateurs peuvent utiliser Ccomme s'il s'agissait d'un A, et / ou comme s'il s'agissait d'un fichier B.

En C ++, une interface est une classe abstraite qui a:

  1. toute sa méthode déclarée pure virtual (suffixée par = 0) (supprimée le 03/05/2017)
  2. aucune variable membre

L'héritage multiple de zéro à un objet réel, et zéro ou plusieurs interfaces n'est pas considéré comme "malodorant" (du moins, pas autant).

En savoir plus sur l'interface abstraite C ++ (modifier le 03/05/2017)

Premièrement, le modèle NVI peut être utilisé pour produire une interface, car le vrai critère est de ne pas avoir d'état (c'est-à-dire pas de variables membres, sauf this). Le but de votre interface abstraite est de publier un contrat ("vous pouvez m'appeler ainsi, et ainsi"), ni plus, ni moins. La limitation de n'avoir qu'une méthode virtuelle abstraite devrait être un choix de conception, pas une obligation.

Deuxièmement, en C ++, il est logique d'hériter virtuellement d'interfaces abstraites (même avec le coût supplémentaire / l'indirection). Si vous ne le faites pas et que l'héritage d'interface apparaît plusieurs fois dans votre hiérarchie, vous aurez des ambiguïtés.

Troisièmement, l'orientation objet est excellente, mais ce n'est pas The Only Truth Out There TM en C ++. Utilisez les bons outils et souvenez-vous toujours que vous avez d'autres paradigmes en C ++ offrant différents types de solutions.

4. Avez-vous vraiment besoin d'héritage multiple?

Parfois oui.

Habituellement, votre Cclasse hérite de Aet B, et Aet Bsont deux objets indépendants (c'est-à-dire pas dans la même hiérarchie, rien en commun, des concepts différents, etc.).

Par exemple, vous pourriez avoir un système Nodesavec des coordonnées X, Y, Z, capable de faire beaucoup de calculs géométriques (peut-être un point, une partie d'objets géométriques) et chaque nœud est un agent automatisé, capable de communiquer avec d'autres agents.

Peut-être avez-vous déjà accès à deux bibliothèques, chacune avec son propre espace de noms (une autre raison d'utiliser des espaces de noms ... Mais vous utilisez des espaces de noms, n'est-ce pas?), L'une étant geoet l'autre étantai

Vous avez donc votre propre own::Nodedérivé à la fois de ai::Agentet geo::Point.

C'est le moment où vous devriez vous demander si vous ne devriez pas utiliser la composition à la place. Si own::Nodec'est vraiment à la fois un ai::Agentet un geo::Point, alors la composition ne fera pas l'affaire.

Ensuite, vous aurez besoin d'héritage multiple, en vous own::Nodepermettant de communiquer avec d'autres agents en fonction de leur position dans un espace 3D.

(Vous noterez que ai::Agentet geo::Pointsont complètement, totalement, totalement SANS LIEN ... Cela réduit considérablement le danger d'héritage multiple)

Autres cas (modifier le 03/05/2017)

Il existe d'autres cas:

  • en utilisant l'héritage (espérons-le privé) comme détail d'implémentation
  • certains idiomes C ++ comme les politiques pourraient utiliser l'héritage multiple (lorsque chaque partie a besoin de communiquer avec les autres via this)
  • l'héritage virtuel de std :: exception (l' héritage virtuel est-il nécessaire pour les exceptions? )
  • etc.

Parfois, vous pouvez utiliser la composition, et parfois MI est meilleur. Le fait est que vous avez le choix. Faites-le de manière responsable (et faites réviser votre code).

5. Alors, dois-je faire l'héritage multiple?

La plupart du temps, d'après mon expérience, non. MI n'est pas le bon outil, même s'il semble fonctionner, car il peut être utilisé par les paresseux pour empiler des fonctionnalités sans se rendre compte des conséquences (comme créer à la Carfois un an Engineet un a Wheel).

Mais parfois, oui. Et à ce moment-là, rien ne fonctionnera mieux que MI.

Mais comme MI a une odeur, soyez prêt à défendre votre architecture dans les revues de code (et la défendre est une bonne chose, car si vous n'êtes pas en mesure de la défendre, vous ne devriez pas le faire).

Paercebal
la source
4
Je dirais que ce n'est jamais nécessaire et si rarement utile que même si cela convient parfaitement, les économies sur la délégation ne compenseront pas la confusion des programmeurs qui ne sont pas habitués à l'utiliser, mais sinon un bon résumé.
Bill K
2
J'ajouterais au point 5, que le code qui utilise MI devrait avoir un commentaire juste à côté expliquant la raison, afin que vous n'ayez pas à l'expliquer verbalement dans une révision de code. Sans cela, quelqu'un qui n'est pas votre réviseur peut remettre en question votre bon jugement quand il voit votre code et vous n'aurez peut-être pas la possibilité de le défendre.
Tim Abell
" parce que vous ne rencontrerez pas le diamant de l'effroi décrit ci-dessus. " Pourquoi pas?
curiousguy
1
J'utilise MI pour le modèle d'observateur.
Calmarius
13
@BillK: Bien sûr, ce n'est pas nécessaire. Si vous disposez d'un langage Turning complet sans MI, vous pouvez faire tout ce qu'un langage avec MI peut faire. Alors oui, ce n'est pas nécessaire. Déjà. Cela dit, cela peut être un outil très utile, et le soi-disant diamant redouté ... Je n'ai jamais vraiment compris pourquoi le mot «redouté» est même là. C'est en fait assez facile à raisonner et ce n'est généralement pas un problème.
Thomas Eding
145

Extrait d'un entretien avec Bjarne Stroustrup :

Les gens disent à juste titre que vous n'avez pas besoin d'héritage multiple, car tout ce que vous pouvez faire avec l'héritage multiple, vous pouvez également le faire avec un héritage unique. Utilisez simplement le truc de délégation que j'ai mentionné. De plus, vous n'avez pas besoin d'héritage du tout, car tout ce que vous faites avec un héritage unique, vous pouvez également le faire sans héritage en transférant via une classe. En fait, vous n'avez pas non plus besoin de classes, car vous pouvez tout faire avec des pointeurs et des structures de données. Mais pourquoi tu veux faire ça? Quand est-il pratique d'utiliser les services linguistiques? Quand préféreriez-vous une solution de contournement? J'ai vu des cas où l'héritage multiple est utile, et j'ai même vu des cas où l'héritage multiple assez compliqué est utile. En général, je préfère utiliser les fonctionnalités offertes par la langue pour faire des solutions de contournement

Nemanja Trifunovic
la source
6
Il y a certaines fonctionnalités de C ++ que j'ai manquées dans C # et Java, bien que les avoir rendrait la conception plus difficile / compliquée. Par exemple, les garanties d'immuabilité de const- j'ai dû écrire des solutions de contournement maladroites (en utilisant généralement des interfaces et une composition) lorsqu'une classe avait vraiment besoin d'avoir des variantes mutables et immuables. Cependant, je n'ai jamais manqué une seule fois l' héritage multiple et je n'ai jamais senti que je devais écrire une solution de contournement en raison de l'absence de cette fonctionnalité. Voilà la différence. Dans tous les cas que j'ai jamais vu, ne pas utiliser MI est le meilleur choix de conception, pas une solution de contournement.
BlueRaja - Danny Pflughoeft
24
+1: Réponse parfaite: si vous ne voulez pas l'utiliser, ne l'utilisez pas.
Thomas Eding
38

Il n'y a aucune raison de l'éviter et cela peut être très utile dans certaines situations. Vous devez cependant être conscient des problèmes potentiels.

Le plus gros étant le diamant de la mort:

class GrandParent;
class Parent1 : public GrandParent;
class Parent2 : public GrandParent;
class Child : public Parent1, public Parent2;

Vous avez maintenant deux "copies" de GrandParent dans Child.

C ++ a pensé à cela et vous permet de faire de l'héritage virtuel pour contourner les problèmes.

class GrandParent;
class Parent1 : public virtual GrandParent;
class Parent2 : public virtual GrandParent;
class Child : public Parent1, public Parent2;

Vérifiez toujours votre conception, assurez-vous que vous n'utilisez pas l'héritage pour économiser sur la réutilisation des données. Si vous pouvez représenter la même chose avec la composition (et généralement vous le pouvez), c'est une bien meilleure approche.

billybob
la source
21
Et si vous interdisez l' héritage et la composition d'utilisation seulement au lieu, vous avez toujours deux GrandParentdans Child. Il y a une peur de l'IM, car les gens pensent simplement qu'ils pourraient ne pas comprendre les règles linguistiques. Mais quiconque ne peut pas obtenir ces règles simples ne peut pas non plus écrire un programme non trivial.
curiousguy
1
Cela semble dur, les règles C ++ sont très élaborées. OK, donc même si les règles individuelles sont simples, il y en a beaucoup, ce qui rend le tout pas simple, du moins pour un humain à suivre.
Zebrafish
11

Voir w: héritage multiple .

L'héritage multiple a fait l'objet de critiques et, en tant que tel, n'est pas implémenté dans de nombreuses langues. Les critiques comprennent:

  • Complexité accrue
  • L'ambiguïté sémantique est souvent résumée comme le problème du diamant .
  • Ne pas pouvoir hériter explicitement plusieurs fois d'une seule classe
  • L'ordre d'héritage change la sémantique des classes.

L'héritage multiple dans les langages avec des constructeurs de style C ++ / Java exacerbe le problème d'héritage des constructeurs et du chaînage des constructeurs, créant ainsi des problèmes de maintenance et d'extensibilité dans ces langages. Les objets dans les relations d'héritage avec des méthodes de construction très variables sont difficiles à implémenter sous le paradigme de chaînage de constructeur.

Moyen moderne de résoudre ce problème en utilisant une interface (pure classe abstraite) comme l'interface COM et Java.

Je peux faire d'autres choses à la place de cela?

Oui, vous pouvez. Je vais voler le GoF .

  • Programmez vers une interface, pas une implémentation
  • Préférez la composition à l'héritage
Eugene Yokota
la source
8

L'héritage public est une relation IS-A, et parfois une classe sera un type de plusieurs classes différentes, et parfois il est important de refléter cela.

Les "mixins" sont également parfois utiles. Ce sont généralement de petites classes, n'héritant généralement de rien, fournissant des fonctionnalités utiles.

Tant que la hiérarchie d'héritage est assez superficielle (comme elle devrait presque toujours l'être) et bien gérée, il est peu probable que vous obteniez l'héritage redouté de diamants. Le diamant n'est pas un problème avec tous les langages qui utilisent l'héritage multiple, mais son traitement par C ++ est souvent maladroit et parfois déroutant.

Bien que j'aie rencontré des cas où l'héritage multiple est très pratique, ils sont en fait assez rares. C'est probablement parce que je préfère utiliser d'autres méthodes de conception lorsque je n'ai pas vraiment besoin d'héritage multiple. Je préfère éviter les constructions de langage confuses, et il est facile de construire des cas d'héritage où vous devez très bien lire le manuel pour comprendre ce qui se passe.

David Thornley
la source
6

Vous ne devez pas "éviter" l'héritage multiple, mais vous devez être conscient des problèmes qui peuvent survenir tels que le "problème du diamant" ( http://en.wikipedia.org/wiki/Diamond_problem ) et traiter le pouvoir qui vous est donné avec soin , comme vous devriez avec tous les pouvoirs.

jwpfox
la source
1
+1: Tout comme vous ne devriez pas éviter les pointeurs; vous devez juste être conscient de leurs ramifications. Les gens doivent arrêter de répéter des phrases (telles que «MI est mauvais», «n'optimise pas», «goto est mauvais») qu'ils ont apprises sur Internet simplement parce que certains hippies ont dit que c'était mauvais pour leur hygiène. Le pire, c'est qu'ils n'ont même jamais essayé d'utiliser de telles choses et de les prendre comme si elles provenaient de la Bible.
Thomas Eding
1
Je suis d'accord. Ne «l'évitez» pas, mais connaissez la meilleure façon de gérer le problème. Je préfère peut-être utiliser des traits de composition, mais C ++ ne l'a pas. Peut-être que je préférerais utiliser la délégation automatisée «gère», mais C ++ ne l'a pas. J'utilise donc l'outil général et émoussé de MI à plusieurs fins différentes, peut-être simultanément.
JDługosz
3

Au risque de devenir un peu abstrait, je trouve éclairant de penser l'héritage dans le cadre de la théorie des catégories.

Si nous pensons à toutes nos classes et aux flèches entre elles indiquant des relations d'héritage, alors quelque chose comme ça

A --> B

signifie que class Bdérive de class A. Notez que, étant donné

A --> B, B --> C

on dit que C dérive de B qui dérive de A, donc on dit aussi que C dérive de A, donc

A --> C

De plus, nous disons que pour chaque classe Adont Adérive trivialement A, notre modèle d'héritage remplit donc la définition d'une catégorie. Dans un langage plus traditionnel, nous avons une catégorie Classavec des objets toutes classes et morphismes les relations d'héritage.

C'est un peu de configuration, mais avec cela, jetons un coup d'œil à notre Diamond of Doom:

C --> D
^     ^
|     |
A --> B

C'est un diagramme d'apparence douteuse, mais ça fera l'affaire. Ainsi , Dhérite de tous A, Bet C. En outre, et se rapprocher de la question de OP, Dhérite également de toute superclasse de A. On peut dessiner un diagramme

C --> D --> R
^     ^
|     |
A --> B
^ 
|
Q

Maintenant, les problèmes associés au diamant de la mort ici sont quand Cet Bpartager certains noms de propriété / méthode et les choses deviennent ambiguës; cependant, si nous déplaçons un comportement partagé, Al'ambiguïté disparaît.

En termes catégoriques, nous voulons A, Bet Cêtre tels que si Bet Chériter de Qalors Apeut être réécrit comme une sous-classe de Q. Cela fait Aquelque chose appelé un pushout .

Il existe également une construction symétrique Dappelée pullback . Il s'agit essentiellement de la classe utile la plus générale que vous puissiez construire et qui hérite à la fois de Bet C. Autrement dit, si vous avez une autre classe qui Rhérite de Bet C, alors Dest une classe où Rpeut être réécrite en tant que sous-classe de D.

S'assurer que vos pointes du diamant sont des pullbacks et des pushouts nous donne un bon moyen de gérer de manière générique les problèmes de conflit de noms ou de maintenance qui pourraient survenir autrement.

Remarque La réponse de Paercebal a inspiré ceci car ses avertissements sont impliqués par le modèle ci-dessus étant donné que nous travaillons dans la catégorie complète Classe de toutes les classes possibles.

Je voulais généraliser son argument à quelque chose qui montre à quel point les relations d'héritage multiples complexes peuvent être à la fois puissantes et non problématiques.

TL; DR Pensez aux relations d'héritage dans votre programme comme formant une catégorie. Ensuite, vous pouvez éviter les problèmes de Diamond of Doom en créant des pushouts de classes à héritage multiple et symétriquement, en créant une classe parente commune qui est un pullback.

Ncat
la source
3

Nous utilisons Eiffel. Nous avons un excellent MI. Pas de soucis. Pas d'issues. Facilement géré. Il y a des moments pour NE PAS utiliser MI. Cependant, il est plus utile que les gens ne le pensent parce qu'ils sont: A) dans un langage dangereux qui ne le gère pas bien -OU- B) satisfaits de la façon dont ils ont travaillé autour de l'IM pendant des années et des années -OU- C) d'autres raisons ( trop nombreux pour les énumérer, j'en suis sûr - voir les réponses ci-dessus)

Pour nous, en utilisant Eiffel, MI est aussi naturel que toute autre chose et un autre bel outil dans la boîte à outils. Franchement, nous ne sommes pas du tout préoccupés par le fait que personne d'autre n'utilise Eiffel. Pas de soucis. Nous sommes satisfaits de ce que nous avons et vous invitons à y jeter un œil.

Pendant que vous regardez: prenez note de la sécurité du vide et de l'éradication du déréférencement du pointeur nul. Pendant que nous dansons tous autour de MI, vos pointeurs se perdent! :-)

Larry
la source
2

Chaque langage de programmation a un traitement légèrement différent de la programmation orientée objet avec des avantages et des inconvénients. La version de C ++ met carrément l'accent sur les performances et présente l'inconvénient qu'il est extrêmement facile d'écrire du code invalide - et cela est vrai de l'héritage multiple. En conséquence, il y a une tendance à éloigner les programmeurs de cette fonctionnalité.

D'autres personnes ont abordé la question de savoir à quoi l'héritage multiple n'est pas bon. Mais nous avons vu pas mal de commentaires qui impliquent plus ou moins que la raison de l'éviter est parce que ce n'est pas sûr. Eh bien, oui et non.

Comme c'est souvent le cas en C ++, si vous suivez une directive de base, vous pouvez l'utiliser en toute sécurité sans avoir à «regarder par-dessus votre épaule» constamment. L'idée clé est que vous distinguez un type spécial de définition de classe appelé "mix-in"; class est un mix-in si toutes ses fonctions membres sont virtuelles (ou purement virtuelles). Ensuite, vous êtes autorisé à hériter d'une seule classe principale et d'autant de "mix-ins" que vous le souhaitez - mais vous devez hériter des mixins avec le mot-clé "virtual". par exemple

class CounterMixin {
    int count;
public:
    CounterMixin() : count( 0 ) {}
    virtual ~CounterMixin() {}
    virtual void increment() { count += 1; }
    virtual int getCount() { return count; }
};

class Foo : public Bar, virtual public CounterMixin { ..... };

Ma suggestion est que si vous avez l'intention d'utiliser une classe en tant que classe mixte, vous adoptez également une convention de dénomination pour permettre à quiconque lisant le code de voir ce qui se passe et de vérifier que vous jouez selon les règles de la ligne directrice de base . Et vous constaterez que cela fonctionne beaucoup mieux si vos mix-ins ont également des constructeurs par défaut, simplement à cause du fonctionnement des classes de base virtuelles. Et n'oubliez pas de rendre virtuels tous les destructeurs.

Notez que mon utilisation du mot "mix-in" ici n'est pas la même que la classe de modèle paramétrée (voir ce lien pour une bonne explication) mais je pense que c'est une utilisation juste de la terminologie.

Maintenant, je ne veux pas donner l'impression que c'est la seule façon d'utiliser l'héritage multiple en toute sécurité. C'est juste un moyen assez facile à vérifier.

sfkleach
la source
2

Vous devez l'utiliser avec précaution, dans certains cas, comme le problème du diamant , les choses peuvent se compliquer.

texte alternatif
(source: learncpp.com )

CMS
la source
12
C'est un mauvais exemple, car un copieur doit être composé d'un scanner et d'une imprimante et non pas en hériter.
Svante
2
Quoi qu'il en soit, un Printerne devrait même pas être un PoweredDevice. A Printerest pour l'impression, pas pour la gestion de l'alimentation. L'implémentation d'une imprimante particulière peut nécessiter une gestion de l'alimentation, mais ces commandes de gestion de l'alimentation ne doivent pas être exposées directement aux utilisateurs de l'imprimante. Je ne peux imaginer aucune utilisation réelle de cette hiérarchie.
curiousguy
1

Au-delà du motif en losange, l'héritage multiple a tendance à rendre le modèle objet plus difficile à comprendre, ce qui à son tour augmente les coûts de maintenance.

La composition est intrinsèquement facile à comprendre, à comprendre et à expliquer. L'écriture de code peut devenir fastidieuse, mais un bon IDE (cela fait quelques années que je n'ai pas travaillé avec Visual Studio, mais les IDE Java ont tous d'excellents outils d'automatisation de raccourcis de composition) devrait vous aider à surmonter cet obstacle.

De plus, en termes de maintenance, le «problème du diamant» se pose également dans les instances d'héritage non littéral. Par exemple, si vous avez A et B et que votre classe C les étend tous les deux, et A a une méthode 'makeJuice' qui fait du jus d'orange et que vous étendez cela pour faire du jus d'orange avec une touche de citron vert: que se passe-t-il lorsque le concepteur pour ' B 'ajoute une méthode' makeJuice 'qui génère un courant électrique? «A» et «B» peuvent être des «parents» compatibles en ce moment , mais cela ne veut pas dire qu'ils le seront toujours!

Dans l'ensemble, la maxime de tendance à éviter l'héritage, et en particulier l'héritage multiple, est bonne. Comme toutes les maximes, il y a des exceptions, mais vous devez vous assurer qu'il y a un néon vert clignotant pointant sur toutes les exceptions que vous codez (et entraînez votre cerveau pour que chaque fois que vous voyez de tels arbres d'héritage, vous dessinez votre propre néon vert clignotant. sign), et que vous vérifiez pour vous assurer que tout a du sens de temps en temps.

Tom Dibble
la source
what happens when the designer for 'B' adds a 'makeJuice' method which generates and electrical current?Euhhh, vous obtenez une erreur de compilation bien sûr (si l'utilisation est ambiguë).
Thomas Eding
1

Le problème clé avec MI des objets concrets est que rarement vous avez un objet qui devrait légitimement "être un A ET être un B", donc c'est rarement la bonne solution pour des raisons logiques. Beaucoup plus souvent, vous avez un objet C qui obéit à "C peut agir comme un A ou un B", ce que vous pouvez réaliser via l'héritage et la composition d'interface. Mais ne vous y trompez pas, l'héritage de plusieurs interfaces est toujours MI, juste un sous-ensemble de celui-ci.

Pour C ++ en particulier, la principale faiblesse de la fonctionnalité n'est pas la réelle EXISTENCE de l'héritage multiple, mais certaines constructions qu'elle autorise sont presque toujours mal formées. Par exemple, hériter de plusieurs copies du même objet comme:

class B : public A, public A {};

est mal formé PAR DEFINITION. Traduit en anglais, c'est "B est un A et un A". Donc, même dans le langage humain, il y a une grave ambiguïté. Vouliez-vous dire "B a 2 As" ou simplement "B est un A" ?. Autoriser un tel code pathologique et, pire encore, en faire un exemple d'utilisation, n'a pas aidé C ++ lorsqu'il s'agissait de justifier le maintien de la fonctionnalité dans les langages successeurs.

Zack Yezek
la source
0

Vous pouvez utiliser la composition de préférence à l'héritage.

Le sentiment général est que la composition est meilleure, et c'est très bien discuté.

Ali Afshar
la source
4
-1: The general feeling is that composition is better, and it's very well discussed.Cela ne veut pas dire que la composition est meilleure.
Thomas Eding
Sauf que c'est, en fait, mieux.
Ali Afshar
12
Sauf pour les cas où ce n'est pas mieux.
Thomas Eding
0

cela prend 4/8 octets par classe impliquée. (Un ce pointeur par classe).

Cela pourrait ne jamais être un problème, mais si un jour vous avez une structure de micro-données instanciée des milliards de fois, ce sera le cas.

Ronny Brendel
la source
1
Sûr de ça? En règle générale, tout héritage sérieux nécessitera un pointeur dans chaque membre de classe, mais cela évolue-t-il nécessairement avec les classes héritées? Plus précisément, combien de fois avez-vous eu des milliards de structures de données minuscules?
David Thornley le
3
@DavidThornley " combien de fois avez-vous eu des milliards de structures de données minuscules? " Lorsque vous faites des arguments contre MI.
curiousguy