L’héritage multiple

L’héritage multiple

Classes virtuelles

Par le biais de dérivations successives, il est tout à fait possible qu’une classe hérite deux fois d’une même classe. En voici un exemple dans lequel D hérite deux fois de A : class B : public A { ….. } ; class C : public A { ….. } ; class D : public B, public C { ….. } ; Dans ce cas, les membres donnée de la classe en question (A dans notre exemple) apparaissent deux fois dans la classe dérivée de deuxième niveau (ici D). Naturellement, il est nécessaire de faire appel à l’opérateur de résolution de portée (::) pour lever l’ambiguïté. Si l’on souhaite que de tels membres n’apparaissent qu’une seule fois dans la classe dérivée de deuxième niveau, il faut, dans les déclarations des dérivées de premier niveau (ici B et C) déclarer avec l’attribut virtual la classe dont on veut éviter la duplication (ici A). Voici comment on procéderait dans l’exemple précédent (le mot virtual peut être indifféremment placé avant ou après le mot public ou le mot private) : class B : public virtual A { ….. } ; class C : public virtual A { ….. } ; class D : public B, public C { ….. } ; exos_c++.book Page 214 Jeudi, 5. juillet 2007 11:10 11 © Éditions Eyrolles 215 chapitre n° 14 L’héritage multiple Lorsqu’on a ainsi déclaré une classe virtuelle, il est nécessaire que les constructeurs d’éventuelles classes dérivées puissent préciser les informations à transmettre au constructeur de cette classe virtuelle (dans le cas usuel où l’on autorise la duplication, ce problème ne se pose plus ; en effet, chaque constructeur transmet les informations aux classes ascendantes dont les constructeurs transmettent, à leur tour, les informations aux constructeurs de chacune des occurrences de la classe en question – ces informations pouvant éventuellement être différentes). Dans ce cas, on le précise dans l’en-tête du constructeur de la classe dérivée, en plus des arguments destinés aux constructeurs des classes du niveau immédiatement supérieur, comme dans cet exemple : arguments arguments arguments arguments de D pour B pour C pour A De plus, dans ce cas, les constructeurs des classes B et C (qui ont déclaré que A était « virtuelle ») n’auront plus à spécifier d’informations pour un constructeur de A. Enfin, le constructeur d’une classe virtuelle est toujours appelé avant les autres. Exercice 109 Énoncé Quels seront les résultats fournis par ce programme : #include using namespace std ; class A { int n ; float x ; public : A (int p = 2) { n = p ; x = 1 ; cout << « ** construction objet A :  » << n <<  »  » << x << « \n » ; } } ; class B { int n ; float y ; public : B (float v = 0.0) { n = 1 ; y = v ; cout << « ** construction objet B :  » << n <<  »  » << y << « \n » ; } } ; exos_c++.book Page 215 Jeudi, 5. juillet 2007 11:10 11 Exercices en langage C++ 216 © Éditions Eyrolles L’objet c1 est créé par appels successifs des constructeurs de B, puis de A (ordre imposé par la déclaration de la classe C, et non par l’en-tête du constructeur de C !). Le jeu de la transmission des arguments et des arguments par défaut conduit au résultat suivant : ** construction objet B : 1 0 ** construction objet A : 1 1 ** construction objet C : 3 3 ** construction objet B : 1 5 ** construction objet A : 10 1 ** construction objet C : 12 21 Exercice 110 Ici, comme le constructeur de C n’a prévu aucun argument pour un éventuel constructeur de A, il y aura appel d’un constructeur sans argument, c’est-à-dire, en fait, appel du constructeur de A, avec toutes les valeurs prévues par défaut. Voici le résultat obtenu : ** construction objet B : 1 0 ** construction objet A : 2 1 ** construction objet C : 3 3 class C : public B, public A { int n ; int p ; public : C (int n1=1, int n2=2, int n3=3, float v=0.0) : A (n1), B(v) { n = n3 ; p = n1+n2 ; cout << « ** construction objet C :  » << n <<  »  » << p <<« \n » ; } } ; main() { C c1 ; C c2 (10, 11, 12, 5.0) ;

Exercice 114 Énoncé

On souhaite créer une classe liste permettant de manipuler des « listes chaînées » dans lesquelles la nature de l’information associée à chaque « nœud » de la liste n’est pas connue (par la classe). Une telle liste correspondra au schéma suivant : La déclaration de la classe liste se présentera ainsi : struct element // structure d’un élément de liste { element * suivant ; // pointeur sur l’élément suivant void * contenu ; // pointeur sur un objet quelconque } ; class liste { element * debut ; // pointeur sur premier élément // autres membres données éventuels public : liste () ; // constructeur ~liste () ; // destructeur void ajoute (void *) ; // ajoute un élément en début de liste void * premier () ; // positionne sur premier élément void * prochain () ; // positionne sur prochain élément int fini () ; } ; La fonction ajoute devra ajouter, en début de liste, un élément pointant sur l’information dont l’adresse est fournie en argument (void *). Pour « explorer » la liste, on a prévu trois fonctions : • premier, qui fournira l’adresse de l’information associée au premier nœud de la liste et qui, en même temps, préparera le processus de parcours de la liste ; Début Informations 1 Informations 2 Informations 3 exos_c++.book Page 220 Jeudi, 5. juillet 2007 11:10 11 © Éditions Eyrolles 221 chapitre n° 14 L’héritage multiple 1. Manifestement, les fonctions premier et prochain nécessitent un « pointeur sur un élément courant ». Il sera membre donnée de la classe liste. Nous conviendrons (classiquement) que la fin de liste est matérialisée par un nœud comportant un pointeur « nul ». La classe liste devra disposer d’un constructeur dont le rôle se limitera à l’initialiser à une « liste vide », ce qui s’obtiendra simplement en plaçant un pointeur nul comme adresse de début de liste (cette façon de procéder simplifie grandement l’algorithme d’ajout d’un élément en début de liste, puisqu’elle évite d’avoir à distinguer des autres le cas de la liste vide). Comme un objet de type liste est amené à créer différents emplacements dynamiques, il est nécessaire de prévoir la libération de ces emplacements lorsque l’objet est détruit. Il faudra donc prévoir un destructeur, chargé de détruire les différents nœuds de la liste. À ce propos, notez qu’il n’est pas possible ici de demander au destructeur de détruire également les informations associées ; en effet, ce n’est pas l’objet de type liste qui a alloué ces emplacements : ils sont sous la responsabilité de l’utilisateur de la classe liste. • prochain, qui fournira l’adresse de l’information associée au « prochain nœud » ; des appels successifs de prochain devront permettre de parcourir la liste (sans qu’il soit nécessaire d’appeler une autre fonction) ; • fini, qui permettra de savoir si la fin de liste est atteinte ou non. 1. Compléter la déclaration précédente de la classe liste et en fournir la définition de manière qu’elle fonctionne comme demandé. 2. Soit la classe point suivante : class point { int x, y ; public : point (int abs=0, int ord=0) { x=abs ; y=ord ; } void affiche () { cout << « Coordonnées :  » << x <<  »  » << y << « \n » ; } } ; Créer une classe liste_points, dérivée à la fois de liste et de point, pour qu’elle puisse permettre de manipuler des listes chaînées de points, c’est-à-dire des listes comparables à celles présentées ci-dessus, et dans lesquelles l’information associée est de type point. On devra pouvoir, notamment : • ajouter un point en début d’une telle liste ; • disposer d’une fonction membre affiche affichant les informations associées à chacun des points de la liste de points. 3. Écrire un petit programme d’essai. exos_c++.book Page 221 Jeudi, 5. juillet 2007 11:10 11 Exercices en langage C++ 222 © Éditions Eyrolles Voici ce que pourrait être notre classe liste complète : #include // pour NULL struct element // structure d’un élément de liste { element * suivant ; // pointeur sur l’élément suivant void * contenu ; // pointeur sur un objet quelconque } ; class liste { element * debut ; // pointeur sur premier élément element * courant ; // pointeur sur élément courant public : liste () // constructeur { debut = NULL ; courant = debut ; // par sécurité } ~liste () ; // destructeur void ajoute (void *) ; // ajoute un élément en début de liste void * premier () // positionne sur premier élément { courant = debut ; if (courant != NULL) return (courant->contenu) ; else return NULL ; } void * prochain () // positionne sur prochain élément { if (courant != NULL) { courant = courant->suivant ; if (courant != NULL) return (courant->contenu) ; } return NULL ; } int fini () { return (courant == NULL) ; } } ; liste::~liste () { element * suiv ; courant = debut ; while (courant != NULL ) { suiv = courant->suivant ; delete courant ; courant = suiv ; } } void liste::ajoute (void * chose) { element * adel = new element ; adel->suivant = debut ; adel->contenu = chose ; debut = adel ; }

Formation et coursTélécharger le document complet

Télécharger aussi :

Laisser un commentaire

Votre adresse e-mail ne sera pas publiée. Les champs obligatoires sont indiqués avec *