1. Chapitre X
Héritage multiple
• On parle d'héritage multiple lorsqu'une classe dérivée admet plusieurs classes de base
directes.
• L'héritage multiple est une généralisation de l'héritage simple. La plupart des résultats
et les notions mentionnés dans l'héritage simple restent valables pour l'héritage
multiple (En effet si T dérive de deux classes A et B, alors T est une classe dérivée de
A (resp de B) dans le sens de l'héritage simple). Cependant, quelques problèmes qui
n'existaient pas dans l'héritage simple apparaissent :
♦ Conflits entre identificateurs des membres hérités
♦ Duplication des membres
♦ Conflits d'initialisations des constructeurs des différentes classes de base
X.1 Un exemple d'héritage multiple
• Notons premièrement que si une classe T hérite de plusieurs classes A, B, …
class T : mode_a A, mode_b B,…
{… …};
où mode_a (mode_b, …) désigne le mode d'héritage (public, protégé ou
public)
2. Héritage multiple 100
le constructeur de T doit fournir les paramètres nécessaires à tous les constructeurs de
ses classes de base. Pour cela on utilise la syntaxe suivante:
T::T(… …) : A(…), B(…),…
De plus, avant tout appel du constructeur de T, il y aura appel des constructeurs de
classes de base suivant l'ordre de la liste des classes de base qui figure dans l'entête de
T. L'appel des destructeurs se fait dans l'ordre inverse.
Exemple 10.1 : (EXP10_01.CPP)
L'exemple suivant montre le cas d'un héritage multiple. On y définit deux classes point et
couleur, et une classe pointcol qui dérive de ces deux classes :
#include <iostream>
using namespace std;
// ------------------- Classes de base
class point{
protected:
int x;
int y;
public:
point(int, int);
~point();
void affiche_point();
};
point::point(int abs, int ord){
x = abs; y = ord;
cout << "--- constr de point" << endl;
}
point::~point(){
cout << "--- destr de point " << endl;
}
void point::affiche_point(){
cout << "(" << x << "," << y << ")";
}
class couleur{
protected:
unsigned int _couleur;
public:
couleur(unsigned int);
~couleur();
void affiche_couleur();
};
couleur::couleur(unsigned int c){
_couleur = c;
cout << "--- constr de couleur" << endl;
}
couleur::~couleur(){
cout << "--- destr de couleur " << endl;
}
void couleur::affiche_couleur(){
cout << "(" << _couleur << ")";
}
// ------------------- Classe derivée
class pointcol: public point, public couleur
{
public:
pointcol(int, int, unsigned int);
3. Héritage multiple 101
~pointcol();
};
pointcol::pointcol(int abs, int ord, unsigned int c):point(abs,ord),
couleur(c)
{
cout << "--- constr de pointcol" << endl;
}
pointcol::~pointcol(){
cout << "--- destr de pointcol" << endl;
}
//--------------------- TEST
int main()
{
pointcol a(1,2,3);
a.affiche_point();
cout << " - ";
a.affiche_couleur();
cout << endl;
return 0;
}
►Sortie du programme
--- constr de point
--- constr de couleur
--- constr de pointcol
(1,2) - (3)
--- destr de pointcol
--- destr de couleur
--- destr de point
Notez l'ordre d'appel des constructeurs et des destructeurs.
X.2 Résolution des conflits entre identificateurs
• Etant donné une classe T qui hérite de deux classes A et B. Si un membre de B possède
le même identificateur que celui d'un membre de A, alors T possèdera deux membres
différents qui portent le même nom et tout accès à l'un de ces deux membres sera
ambiguë. Pour résoudre cette ambiguïté il faut utiliser l'opérateur de résolution de
portée :
class A {… float x; …}
class B {… int x; …}
class T: public A, public B {… …}
T u;
u.A::x; // fait référence au réel x défini dans A
u.B::x; // fait référence à l'entier x défini dans B
u.x // rejeté, ambiguïté
Exemple 10.2 : (EXP10_02.CPP)
Cet exemple reprend l'exemple précédent dans lequel on renomme les fonctions membres
affiche_point() et affiche_couleur() en affiche(). Dans ce cas le
programme test devient :
4. Héritage multiple 102
int main()
{
pointcol a(1,2,3);
a.point::affiche();
cout << " - ";
a.couleur::affiche();
cout << endl;
return 0;
}
X.3 Classes virtuelles
• Supposons qu'une classe T hérite de deux classes A et B et que ces deux classes
héritent d'une autre classe C :
T hérite donc deux fois de C par l'intermédiaire de A et de B. Autrement dit, si A et B
hérite de C un membre nommé x, la classe T aura deux membres nommés x, (un
hérité de A et l'autre hérité de B) qui désignent la même donnée. Dans ce cas pour
accéder à ce membre dans T, on doit spécifier le chemin complet suivant
l'arborescence de l'héritage :
T u;
u.A::C::x; // fait référence à x hérité de C via A
u.B::C::x; // fait référence à x hérité de C via B
Or ceci n'est pratique ni efficace, en plus généralement une seule copie du membre x
est suffisante.
• Pour résoudre ce problème, on déclare virtuelle la classe de base commune C dans la
spécification de l'héritage de A et B comme suit :
class C {… …};
class A : public virtual C
{… …};
class B : public virtual C
{… …};
class T: public A, public B {… …}
dans ce cas, le compilateur n'incorpore qu'une seule copie des membres hérités de C
dans T et l'accès à ces membres se fera son l'utilisation de l'opérateur de résolution de
portée.
• Cependant, il faut noter que :
♦ Le constructeur d'une classe qui hérite directement ou indirectement d'une
classe virtuelle doit fournir les paramètres nécessaires au constructeur de cette
classe virtuelle. Dans le cas de notre exemple le constructeur de T doit fournir
les paramètres nécessaires au constructeur de C comme suit :
C
A
T
B
5. Héritage multiple 103
T::T(… …) : C(…), A(…), B(…)
♦ Si un membre de la classe de base virtuelle commune est redéfini dans une des
classes dérivées, c'est la définition de la classe dérivée qui prédomine. Si par
exemple fct() est une fonction membre de C qui est redéfinie dans B, alors
tout appel de cette fonction dans T ou avec une instance de T invoquera
B::fct().