I. Problèmes▲
I-A. Question Junior▲
Qu'est-ce que l'héritage multiple et quelles possibilités ou complications l'héritage multiple introduit-il dans C++ ?
I-B. Question Guru▲
L'héritage multiple est-il nécessaire ? Si oui, montrez autant de situations différentes que vous pouvez et donnez des arguments pour que l'héritage multiple soit présent dans un langage. Sinon, dites pourquoi l'héritage simple (éventuellement combiné à des interfaces de type Java) vaut au moins aussi bien et pourquoi l'héritage multiple ne devrait pas être présent dans un langage.
I-C. Remarque▲
Ce GotW n'a pas pour intention de ranimer les discussions closes depuis longtemps sur ce que C++ devrait faire. Il convient néanmoins de considérer maintenant le problème de l'héritage multiple, parce qu'un autre langage populaire fait actuellement face à la même décision : il y a moins de deux semaines(1) à Portland, Oregon, le comité ANSI SQL3 a voté pour éliminer l'héritage multiple en tant que caractéristique de l'OO/ORDBMS standard. Le comité ISO correspondant, qui se réunit plus tard ce mois-ci en Australie, envisagera la même publication et il est probable qu'il prenne la suite. Si cela se passe comme à notre réunion ANSI, SQL3 n'inclura que l'héritage simple (sans même le compromis des interfaces de type Java).
II. Solutions▲
II-A. Question Junior▲
Qu'est-ce que l'héritage multiple et quelles possibilités ou complications l'héritage multiple introduit-il dans C++ ?
Très brièvement : l'héritage multiple signifie la possibilité d'hériter de plusieurs classes de base directes. Par exemple :
class
Derived : public
Base1, private
Base2
{
//...
}
;
Permettre l'héritage multiple introduit la possibilité qu'une classe ayant la même classe de base (directe ou indirecte) apparaisse plus d'une fois comme ancêtre. Voici un exemple de cette forme de « diamant de la mort » :
B
/
\
C1 C2
\ /
D
Ici, B est deux fois une classe de base indirecte de D : une fois par C1 et une fois par C2.
Cette situation introduit le besoin d'une caractéristique supplémentaire dans C++ : l'héritage virtuel. Le développeur veut-il que D ait un sous-objet B ou deux ? Si c'est un, B devrait être une classe de base virtuelle ; si c'est deux, B devrait être une classe de base normale (non virtuelle).
Finalement, la principale complication des classes de base virtuelles est qu'elles doivent être initialisées directement par la classe la plus dérivée. Pour plus d'informations là-dessus et sur d'autres aspects de l'héritage multiple, reportez-vous à un bon texte comme les ouvrages Le langage C++ de Bjarne Stroustrup ou Effective C++ de Scott Meyer.
II-B. Question Guru▲
L'héritage multiple est-il nécessaire ?
Brièvement : aucune caractéristique n'est strictement « nécessaire » dans la mesure où tout programme peut être écrit en assembleur. Toutefois, comme la plupart des gens préféreraient ne pas coder leur propre mécanisme de fonction virtuelle en C, dans certains cas, ne pas disposer de l'héritage multiple implique de pénibles travaux alternatifs.
Si oui, montrez autant de situations différentes que vous pouvez et donnez des arguments pour que l'héritage multiple soit présent dans un langage. Sinon, dites pourquoi l'héritage simple (éventuellement combiné à des interfaces de type Java) vaut au moins aussi bien et pourquoi l'héritage multiple ne devrait pas être présent dans un langage.
Brièvement : Oui et non.
Comme n'importe quel outil, l'héritage multiple doit être utilisé avec précautions. Utiliser l'héritage multiple ajoute de la complexité (cf. Question Junior ci-dessus), mais il y a des situations où cela reste plus simple et plus facile à corriger que les solutions alternatives. Comme certains ont pu le dire : « on n'a pas souvent besoin de l'héritage multiple, mais quand on en a besoin, on en a VRAIMENT besoin. »
Il existe de nombreuses situations où l'héritage multiple est un outil pratique et approprié. J'en couvrirai seulement trois (en fait, la plupart des utilisations entrent dans l'une de ces trois catégories).
II-B-1. Classes d'interface (classes de base abstraites pures)▲
En C++, la meilleure et la plus sûre des utilisations de l'héritage multiple consiste à définir des classes d'interface, c'est-à-dire des classes composées de rien d'autre que des fonctions virtuelles pures. En particulier, c'est l'absence de données membres dans la classe de base qui évite les célèbres complexités de l'héritage multiple.
De façon intéressante, différents langages/modèles supportent ce type « d'héritage multiple » via des mécanismes de non-héritage. Deux exemples sont donnés par Java et COM : Java n'a que l'héritage simple, mais il admet la notion qu'une classe puisse implémenter de multiples « interfaces » dans lesquelles les interfaces sont très similaires aux classes de base abstraites pures de C++. COM n'a pas d'héritage, mais de façon similaire, il admet une notion de composition d'interface et ce modèle est similaire à une combinaison d'interfaces Java et de templates C++.
II-B-2. Combiner les modules/bibliothèques▲
Beaucoup de classes sont conçues pour être des classes de base ; c'est-à-dire que pour les utiliser, vous êtes censé en hériter. Conséquence naturelle : que faire si vous voulez écrire une classe qui prolonge deux bibliothèques et s'il vous faut hériter d'une classe dans chacune des deux ? Puisque habituellement vous n'avez pas la possibilité de changer le code d'une bibliothèque (si vous avez acheté la bibliothèque auprès d'une tierce partie ou si c'est un module produit par une autre équipe dans votre entreprise), l'héritage multiple est nécessaire.
II-B-3. Facilité d'utilisation (polymorphe)▲
Il y a des exemples où permettre l'héritage multiple simplifie grandement l'utilisation du même objet de différentes façons. On trouve un bon exemple dans Le langage C++ 14.2.2 qui montre une conception basée sur l'héritage multiple pour des classes d'exceptions, où la classe d'exceptions la plus dérivée peut avoir une relation polymorphe EST-UN avec plusieurs classes de base directes.
Notez que le cas n°1 recoupe largement le cas n°3.
II-B-4. Conclusion▲
Finalement, n'oubliez pas que « l'héritage public polymorphe LSP EST-UN »(2) n'est pas le seul jeu auquel vous pouvez jouer ; il y a beaucoup d'autres raisons possibles pour utiliser l'héritage. Le résultat est que parfois, il est non seulement nécessaire d'hériter de plusieurs classes de base, mais de le faire pour différentes raisons pour chacune d'elles. Par exemple, une classe peut avoir besoin d'hériter de façon privée d'une classe de base A pour avoir accès aux membres protégés de la classe A, mais en même temps d'hériter de façon publique d'une classe de base B pour mettre en action de façon polymorphe une fonction virtuelle de la classe B.
III. Remerciements▲
Cet article est une traduction en français par l'équipe de la rubrique C++ de l'article de Herb Sutter publié sur Guru of the Week. Vous pouvez retrouver cet article dans sa version originale sur le site de Guru of the Week : Multiple Inheritance - Part IMultiple Inheritance - Part I.
Merci à Flob90 et à Luc Hermitte pour leur relecture technique et à ClaudeLELOUP pour sa relecture orthographique.