du cours d aujourd hui Informatique II : Cours de programmation (C++) Introduire la notion d héritage Construction/destruction Ambiguïté des attributs/méthodes hérités Haroud Laboratoire d Intelligence Artificielle Faculté I&C (Retour sur les collections hétérogènes) Informatique II Cours 8 : 1 / 32 Informatique II Cours 8 : 2 / 32 Où en est-on? Nous connaissons maintenant les aspects essentiels de la POO : Encapsulation et abstraction regroupement traitements et données : class X { séparation interface et détails d implémentation :, protected:, private: class C1 : public C2 { Polymorphisme pouvoir être vu de plusieurs façons, abstraction, généricité 2 ingrédients nécessaires : pointeurs et méthodes classes abstraites et méthodes pures collections hétérogènes Qu est-ce que l héritage? En C++, une sous-classe peut hériter de plusieurs super-classes : Classe A Classe C Classe B Comme pour l héritage simple, la sous-classe hérite des super-classes : tous leurs attributs et méthodes (sauf les constructeurs/destructeurs) leur type Informatique II Cours 8 : 3 / 32 Informatique II Cours 8 : 4 / 32
Exemple Exemple (2) Un exemple zoologique :.. et un exemple informatique : Vivipare Ovipare istream ios ostream Ovovivipare ifstream iostream ofstream fstream Informatique II Cours 8 : 5 / 32 Informatique II Cours 8 : 6 / 32 Syntaxe : class nomsousclasse: [public] nomsuperclasse1, [public] nomsuperclassen { // class Ovovivipare: public Ovipare, public Vivipare { Ovovivipare(unsigned int, unsigned int); virtual ~Ovovivipare(); protected: bool espece_rare; Constructeurs/destructeurs Comme pour l héritage simple, l initialisation des attributs hérités doit être faite par invocation des constructeurs des super-classes : Syntaxe : SousClasse(liste d arguments) : SuperClasse1(arguments1), SuperClasseN(argumentsN), attribut1(valeur1), attributk(valeurk) {} il n y pas de restriction sur le nombre de super-classes dont la sous-classe peut hériter l ordre de déclaration des super-classes est pris en compte lors de l invocation des constructeurs/destructeurs Lorsque l une des super-classes admet un constructeur par défaut, il n est pas nécessaire de l invoquer explicitement. Informatique II Cours 8 : 7 / 32 Informatique II Cours 8 : 8 / 32
Constructeurs/destructeurs (2) Constructeurs/destructeurs (3) Attention! L exécution des constructeurs des super-classes se fait selon l ordre de la déclaration d héritage, et non selon l ordre des appels dans le constructeur! L ordre des appels des destructeurs de super-classes est l inverse de celui des appels de constructeurs class Ovovivipare : public Ovipare, public Vivipare { Ovovivipare(unsigned int, unsigned int); virtual ~Ovovivipare(); protected: bool espece_rare; Ovovivipare::Ovovivipare(unsigned int nb_oeufs, unsigned int duree_gestation) : Vivipare(duree_gestation), Ovipare(nb_oeufs), espece_rare(false) {}} Ordre d invocation des constructeurs : Ovipare(), puis Vivipare() puis le corps de Ovovivipare() Ordre d invocation des destructeurs : Ovovivipare(), puis Vivipare, puis Ovipare Informatique II Cours 8 : 9 / 32 Informatique II Cours 8 : 10 / 32 Accès direct ambigu Accès direct ambigu (2) Comme dans le cas de l héritage simple, une sous-classe peut accéder directement aux attributs et méthodes protégés de ses super-classes et si ces attributs/méthodes portent le même nom dans plusieurs super-classes? class Vivipare { void afficher (ostream&) const; class Ovipare { void afficher (ostream&) const; class Ovovivipare : public Vivipare, public Ovipare {?? int main() { Ovovivipare o; o. afficher(cout); } Attention! l accès o.afficher provoquera une erreur à la compilation même si la méthode afficher n avait pas les mêmes arguments dans les deux classes Ovipare et Vivipare!!! (La raison est qu en C++ il n y a surcharge que dans la même portée. Ici ce n est pas une problème de surcharge, mais un problème de résolution de portée) Première solution : utiliser l opérateur de résolution de portée. int main(){ Ovovivipare o; o.vivipare::afficher(cout); mais Informatique II Cours 8 : 11 / 32 Informatique II Cours 8 : 12 / 32
Accès direct ambigu (3) Accès direct ambigu Solution Une des solutions consiste à lever l ambiguïté en indiquant explicitement quelle(s) méthode(s) on souhaite invoquer mais L utilisation de l opérateur de résolution de portée pour résoudre les ambiguïtés de noms des attributs/méthodes n est pas une bonne solution : Il faut ajouter à la liste des déclarations des méthodes/attributs de la sous-classe, une déclaration spéciale indiquant quel(s) méthode(s)/attribut(s) seront invoqué(s) exactement Syntaxe : c est l utilisateur de la classe Ovovivipare qui décide du fonctionnement correct de cette classe, alors que cette responsabilité doit normalement incomber aux concepteurs de la classe using NomSuperClasse::NomAttributOuMethodeAmbigu class Ovovivipare : public Ovipare, public Vivipare { using Vivipare::afficher; //Comme ceci Attention! pas de parenthèse (ni prototype) derrière les noms de méthodes dans un using! Informatique II Cours 8 : 13 / 32 Informatique II Cours 8 : 14 / 32 Accès direct ambigu Solution (2) Une autre bonne solution consiste à incorporer dans la sous-classe une méthode définissant la bonne interprétation de l invocation ambiguë. class Ovovivipare: public Ovipare, public Vivipare { Ovovivipare(unsigned int, unsigned int); virtual ~Ovovivipare(); Il peut se produire qu une super-classe soit incluse plusieurs fois dans une hiérarchie à héritage : Animal Vivipare Animal Ovipare // Solution: void afficher(ostream& out){ Ovipare::afficher(out); out << " mais aussi "; Vivipare::afficher(out); } protected: bool espece_rare; Ovovivipare Les attributs/méthodes de la super-classe seront inclus plusieurs fois! Chaque objet de la classe Ovovivipare possédera deux copies des attributs de la classe Animal. Informatique II Cours 8 : 15 / 32 Informatique II Cours 8 : 16 / 32
(2) (3) Pour éviter la duplication des attributs d une super-classe plusieurs fois incluse lors d héritages s, il faut déclarer son lien d héritage avec toutes ses sous-classes comme virtuel. Cette super-classe sera alors dite «virtuelle» (à ne pas confondre avec classe abstraite!!) Animal Syntaxe : Vivipare Ovipare class NomSousClasse: [public] virtual NomSuperClasseVirtuelle class Ovipare: public virtual Animal { class Vivipare: public virtual Animal { A noter que c est la classe pouvant être héritée plusieurs fois qui est virtuelle (i.e. ici la super-super-classe) et non pas directement les classes utilisées dans l héritage (i.e. les super-classes). Ovovivipare Un seul objet de la super-classe Animal est hérité par l héritage commun des sous-classes Ovipare et Vivipare. Informatique II Cours 8 : 17 / 32 Informatique II Cours 8 : 18 / 32 Constructeurs et classes Dans une dérivation non-virtuelle, le constructeur d une sous-classe ne fait appel explicitement qu aux constructeurs de ses super-classes immédiates (et ceci récursivement) Dans une dérivation virtuelle, la super-classe virtuelle est initialisée par sa classe la plus dérivée La sous-classe la plus dérivée doit donc faire appel au constructeur de la super-classe virtuelle Ovovivipare::Ovovivipare(string nom, Habitat habitat, Regime regime, unsigned int nb_oeufs, unsigned int gestation, bool espece_rare) : Animal(nom,habitat,regime), //ICI Ovipare(nb_oeufs), Vivipare(gestation), espece_rare(false) {} Constructeurs et (2) Comment sont gérés les appels au constructeur de la super-classe virtuelle? Si le constructeur d un objet de la classe la plus dérivée est invoqué, les appels explicites au constructeur de la super-classe virtuelle dans les classes intermédiaires sont ignorés. Si la super-classe virtuelle a un constructeur par défaut, il n est pas nécessaire de faire appel à ce constructeur explicitement dans sa classe la plus dérivée. Si l appel explicite au constructeur de la super-classe virtuelle est omis de la sous-classe la plus dérivée et si cette super-classe virtuelle n a pas de constructeur par défaut, la compilation signalera une erreur. Exemple Informatique II Cours 8 : 19 / 32 Informatique II Cours 8 : 20 / 32
Constructeurs et (3) Ovovivipare::Ovovivipare(string nom, Habitat habitat, Regime regime, unsigned int nb_oeufs, unsigned int gestation, bool espece_rare) : Animal(nom,habitat,regime), Ovipare(nb_oeufs), Vivipare(gestation), espece_rare(espece_rare) {} les appels explicites au constructeur de la classe Animal dans les constructeurs de Ovipare et Vivipare sont ignorés si Animal avait un constructeur par défaut, il n est pas nécessaire de l invoquer explicitement dans le constructeur de Ovovivipare si Ovovivipare ne fait pas appel explicitement au constructeur de Animal et si Animal n a pas de constructeur pas défaut, il y aura une erreur à la compilation. Ordre des constructeurs/destructeurs Dans une hiérarchie de classes où il existe des super-classes : le soin d initialiser les super-classes incombe à la sous-classe la plus dérivée les constructeurs des super-classes sont invoqués en premier ceux des classes non- le sont ensuite dans l ordre de déclaration de l héritage l ordre d appel des constructeurs de copie est identique l ordre d appel des destructeurs est l inverse de celui des appels de constructeurs Informatique II Cours 8 : 21 / 32 Informatique II Cours 8 : 22 / 32 Ce que j ai appris aujourd hui class nomsousclasse: [public] nomsuperclasse1, [public] nomsuperclassen Collision de noms d attributs/méthodes : c est la sous-classe qui hérite de ces attributs/méthodes qui doit définir le sens de leur utilisation Classe virtuelle : pour éviter qu une sous-classe hérite plusieurs fois d une même super-classe, il faut déclarer les dérivations concernées comme NomSousClasse: [public] virtual NomSuperClasseVirtuelle Constructeur : SousClasse(liste de parametres) : SuperClasse1(arguments1), SuperClasseN(argumentsN), attribut1(valeur1) attributk(valeurk) {} Informatique II Cours 8 : 23 / 32 Qu en C++ une sous-classe peut avoir plusieurs super-classes Comment gérer les collisions de noms d attributs et de méthodes dans une hiérarchie Ce qu est une classe virtuelle et comment l utiliser Informatique II Cours 8 : 24 / 32
Retour (pratique) sur les collections hétérogènes Considérons un programme de modélisation graphique manipulant des dessins, lesquels sont des ensembles de figures géométriques. Classiquement on aura : Figure comme classe abstraite, avec différentes sous-classes concrètes (cercles, rectangles, carrés, ) Dessin comme collection hétérogène de figures. Première question : le dessin est-il ou bien a/possède-t-il un tableau dynamique de figures? Retour sur les collections hétérogènes Le dessin est-il ou bien a/possède-t-il un tableau dynamique de figures? Suivant le conseil de la semaine passée, on va choisir ici l encapsulation (le dessin a un tableau de figures). class Dessin { private: vector< Figure* > contenu; Les figures étant abstraites, on a donc bien une collection hétérogène et donc pas le choix pour le contenu du tableau dynamique : pour avoir du polymorphisme, il faut des pointeurs. La question est : doit-on montrer ces pointeurs dans l interface ou non? Informatique II Cours 8 : 25 / 32 Informatique II Cours 8 : 26 / 32 Retour sur les collections hétérogènes Une autre question, reliée, tout aussi importante est : quel est le statut du contenu : personnel ou partagé? En clair, une figure d un dessin donné est elle universelle (partagée par d autres dessins) ou spécifique à ce dessin précis? Par exemple, si je colorie en rouge le cercle 23 du dessin 18, est-ce que seul ce cercle sera rouge ou bien d autres? du même dessin? d autres dessins? Les réponses à ces questions dépendent du cadre général du programme et de sa conception, et n ont pas de réponse unique. Dans le cadre choisi ici (dessins), il semble naturel que les éléments de la collection (les figures) soient uniques et personnels. Informatique II Cours 8 : 27 / 32 Retour sur les collections hétérogènes Si le contenu d un Dessin est personnel, qu en est-il de la copie d un Dessin copie profonde du Dessin ou, plus simple, interdire la copie à "l ancienne": class Dessin { private : // interdire la copie : on met en private Dessin& operator=(const Dessin&); Dessin(const Dessin&); vector<figure*> contenu; class Dessin { // interdire la copie Dessin& operator=(const Dessin&) = delete; Dessin(const Dessin&) = delete; private: vector<figure*> contenu; Informatique II Cours 8 : 28 / 32
Interface? Montrer les pointeurs Revenons à la première question : montrer ou non les pointeurs? OUI, ou NON Si oui, alors on aura des prototypes du genre void Dessin::ajoute(Figure* nouvelle); MAIS cela supposera : ➀ que l on ajoute à chaque fois une nouvelle instance de figure ; typiquement : mondessin.ajoute(new Cercle(etc.)); ➁ que l utilisateur s occupe de la gestion mémoire (ne pas oublier les delete, ne pas non plus les faire trop tôt). Il faudra que l utilisateur de la classe Dessin y fasse bien attention! risques de mauvaise programmation Informatique II Cours 8 : 29 / 32 Informatique II Cours 8 : 30 / 32 Cacher les pointeurs Si non, alors on aura des prototype du genre void Dessin::ajoute(Figure const& nouvelle); MAIS cela supposera : ➀ que l on s occupe (nous) de la gestion mémoire (ce qui est plutôt bien car plus localisé, donc moins de risque d erreur) ; ➁ que l on fasse à chaque fois une copie de la figure géométrique reçue comme exemple (puisque l on est dans le cadre «unique et personnel») ; typiquement avoir une copie profonde polymorphique au niveau des Figures. class Figure { virtual Figure* copie() const = 0; class Cercle : public Figure { virtual Cercle* copie() const { return new(cercle(*this)); } Synthèse Conseils (repris de la semaine passée) : Tant que faire se peut de donner la propriété à la collection et dans ce cas d utiliser des unique_ptr (hors cours) en ou des pointeurs à la C, mais gérés en interne de la collection sinon (si vous ne pouvez pas donner la propriété à la collection), d utiliser des pointeurs à la C et transférer des adresses d objets existants plus longtemps que la collection elle-même (il faudra donc le garantir!) On parle de «retour covariant» Informatique II Cours 8 : 31 / 32 Informatique II Cours 8 : 32 / 32