8.6. Classes virtuelles

Supposons à présent qu'une classe D hérite de deux classes mères, les classes B et C. Supposons également que ces deux classes héritent d'une classe mère commune appelée classe A. On a l'arbre « généalogique » suivant :

On sait que B et C héritent des données et des méthodes publiques et protégées de A. De même, D hérite des données de B et C, et par leur intermédiaire des données de A. Il se pose donc le problème suivant : quelles sont les données que l'on doit utiliser quand on référence les champs de A ? Celles de B ou celles de C ? On peut accéder aux deux sous-objets de classe A en spécifiant le chemin à suivre dans l'arbre généalogique à l'aide de l'opérateur de résolution de portée. Cependant, cela n'est ni pratique ni efficace, et en général, on s'attend à ce qu'une seule copie de A apparaisse dans D.

Le problème est résolu en déclarant virtuelle la classe de base commune dans la spécification de l'héritage pour les classes filles. Les données de la classe de base ne seront alors plus dupliquées. Pour déclarer une classe mère comme une classe virtuelle, il faut faire précéder son nom du mot clé virtual dans l'héritage des classes filles.

Exemple 8-7. Classes virtuelles

class A
{
protected:
    int Donnee;        // La donnée de la classe de base.
};

// Héritage de la classe A, virtuelle :
class B : virtual public A
{
protected:
    int Valeur_B;      // Autre donnée que "Donnee" (héritée).
};

// A est toujours virtuelle :
class C : virtual public A
{
protected:
    int valeur_C;      // Autre donnée
                       // ("Donnee" est acquise par héritage).
};

class D : public B, public C   // Ici, Donnee n'est pas dupliqué.
{
    /* Définition de la classe D. */
};

Note : Normalement, l'héritage est réalisé par le compilateur par aggrégation de la structure de données des classes de base dans la structure de données de la classe dérivée. Pour les classes virtuelles, ce n'est en général pas le cas, puisque le compilateur doit assurer l'unicité des données héritées de ces classes, même en cas d'héritage multiple. Par conséquent, certaines restrictions d'usage s'appliquent sur les classes virtuelles.

Premièrement, il est impossible de transtyper directement un pointeur sur un objet d'une classe de base virtuelle en un pointeur sur un objet d'une de ses classes dérivées. Il faut impérativement utiliser l'opérateur de transtypage dynamique dynamic_cast. Cet opérateur sera décrit dans le Chapitre 10.

Deuxièmement, chaque classe dérivée directement ou indirectement d'une classe virtuelle doit en appeler le constructeur explicitement dans son constructeur si celui-ci prend des paramètres. En effet, elle ne peut pas se fier au fait qu'une autre de ses classes de base, elle-même dérivée de la classe de base virtuelle, appelle un constructeur spécifique, car il est possible que plusieurs classes de base cherchent à initialiser différemment chacune un objet commun hérité de la classe virtuelle. Pour reprendre l'exemple donné ci-dessus, si les classes B et C appellaient toutes les deux un constructeur non trivial de la classe virtuelle A, et que la classe D appellait elle-même les constructeurs de B et C, le sous-objet hérité de A serait construit plusieurs fois. Pour éviter cela, le compilateur ignore purement et simplement les appels au constructeur des classes de bases virtuelles dans les classes de base dérivées. Il faut donc systématiquement le spécifier, à chaque niveau de la hiérarchie de classe. La notion de constructeur sera vue dans la Section 8.8