Un des apports de la programmation Objet est le concept d'inclusion de type.
La possibilité de définir des inclusions de type a une grande incidence sur la stratégie de programmation, de conception et d'analyse.
Grâce à ce mécanisme nous allons écrire une seule fois le concept associé à la classe A et l'utiliser dans les classes B et les classes C et récursivement dans toutes les classes inclues.
- quelle est l'importance de la relation type / sous-type
?
- quels sont les problèmes de programmation
qui sont aisément traités par cette syntaxe / ce mécanisme
?
- Comment appliquer les idées de la Poo et
le mécanisme d'héritage ou C++ ?
La classe " dérivée " hérite des potentialités de la classe de base, tout en lui en ajoutant de nouvelles
- sans changer la classe de base
- sans avoir la source de l'implémentation
de la classe de Base (le fichier ".h" c'est à dire, l'interface
suffit).
Un exemple classique de dérivation nous vient de la librairie X qui utilise (peut-être abusivement) ce mécanisme pour simplifier la construction et la compréhension de l'ensemble des fonctionnalités de la librairie.
- position core - affichable - Classe "Racine" - taille composite - container Shell - interaction avec le Window Manadger
Il faut donc utiliser une " Dérivation " ad hoc par exemple :
class mon nouveau Super Shell {Maintenant je veux utiliser ma nouvelle class comme l'ancienne...
private :
Mon super Shell Base ;
} ;
Pour utiliser la nouvelle classe.
Mon nouveau super Shell MNSS ;
soit MNSS. Base. Clear () ; // si mon nouveau... //est une classe friend//de mon Super Shell // soit réécrire clear () inline Mon nouveau Super Shell :: clear () {Base-Clear () ;} et utiliser directement MNSS. Clear () ; mais maintenant, MNSS. Base. Clear () et MNSS. Clear () peuvent appeler des fonctions différentes...
En C++ la dérivation permet exactement cette approche. La dérivation d'une classe X à partir d'une classe Y, nous donne des instances de X contenant tous les membres de la class Y.
La portée de ces différents membres hérités, dépend de la portée définie sur le membre et du mode de dérivation.
Pour un héritage multiple :
Class dérivée : Public Base { // ajout de la class dérivée }; : opérateur de dérivation public, private modes de dérivation protected
La dérivation répétée est possible :
Class dérivée : Public Base 1 private Base 2 public Base 3 { // corps de la class dérivée };
Le C a un mécanisme de transformation de type la coercition. L'inclusion de type en C++ est réalisée par des cast standard : un type de class dérivé est en standard transformable en un type de ses classes de Base.
Ex : Base - 1* p1 ;Ceci permet d'avoir des fonctions polymorphes :
Base - 2 * p2 ;
Dérivée d,* p,
p1 = & d ; // ok conversion
p2 = & d ; // ok standard ?
p = p1 ; // non une instance de Base 1 n'est pas toujours une instance de Dérivée
Attention : les conversions sont réelles au sens du C ; l'opération de conversion de Dérivée en Base -1 ou en Base -2 n'est pas la même !
void f _ générique (core * truc)Les défauts de ce code sont nombreux, le plus grave est qu'il faut l'éditer, le compiler, le tester pour chaque nouvelle classe qui hérite de core ! Avec les fonctions virtuelles et l'édition de liens dynamiques, la fonction s'écrit :
{
switch (truc ® class - id)
{case SHELL : (( SHELL*) truc) ®doit () ; break ;
case Button : ((Button *) truc) doit ®() ; break ;
...
}
}
Void f _ générique (core * truc)Mieux, non ?
{ truc ® doit ( ) ;}
Une fonction membre ne sera déclarée virtual, que si les conditions de conception suivantes sont vérifiées :
Class core { --- public --- virtual void doit () ; } ;
- la classe va être l'objet de dérivation
- l'implémentation de la fonction varie suivant
le type effectif de l'instance.
En X le contenu (Image) dépend de la classe de l'objet d'où une virtual.
La relation entre les types s'appelle la hiérarchie d'héritage.
Le passage d'une technique à une méthodologie a eu lieu avec small Talk, où non seulement c'est une méthode, mais aussi une modélisation du monde qui aide dans les phases d'analyse et de conception.
L'héritage doit permettre non seulement de minimiser la quantité de code mais surtout de simplifier la compréhension et l'utilisation de nouveaux types.
Un Zoo
Nous pouvons écrire le squelette d'implémentation suivant :
- la classe de Base Principale :
class AnimalDeZoo {...} ;
Nous ne manipulerons que des instances de sous-classes
de Animal de Zoo.
- les autres classes de Bases :
class enVoied'Extinction {...} ;
class Herbivore {...} ;
class Carnivore {...} ;
---
Ces classes apportent des possibilités qui
ne sont pas communes à tous les animaux du zoo.
- les classes Dérivées, classes opératoires :
Les classes dont nous allons effectivement manipuler des instances.
class ours : public animaldezoo {...} class félins : public AnimaldeZoo, public carnivore {...} ; class Panda : public ours, private herbivore, private envoied'extinction {...}
Par défaut la classe de Base est private.
Class AnimalDeZoo { public : AnimalDeZoo Virtual a AnimalDeZoo (char*,char*, short); Virtual void dessine (); Void informations () ; Short localisation () ; protected char*name ; char*infos ; short location ; short nombre ; } ; class ours : public AnimalDeZoo { public : ours (char*,char*,short*,char,char*) ; void locate (int) int Est affiché () ; protected : char nourrit, dangereux, affiché, date ; };
Dérivation public :
La portée des membres hérités
ne change pas.
Dérivation protected :
Les membres publics de la classe de Base deviennent
protected dans la classe dérivée ; (les autres ne changent
pas de portée).
Dérivation private :Tous les membres de la classe de Base sont privés dans la classe dérivée.
Attention : une fonction de même nom dans la classe dérivée cache la fonction de la classe de Base, même si elles n'ont pas la même signature ce n'est pas considéré comme de la surcharge.
Dans le cas où l'on veut cacher le membre hérité, mais l'appeler dans le membre dérivé, on utilisera l'opérateur :: de révolution de portée dans le membre dérivé.
cache les fonctions fluo des sous-classes, ainsi les classes dérivées de panda n'ont pas à choisir.
Class herbivore { public : void fluo () ; } Class en voie d'extinction {public : void fluo () ; } Class panda : public ours, public en voie d'extinction public herbivore { public : void fluo () ; public : void truc () }; void panda :: fluo () { herbivore :: fluo () en voie d'extinction:: fluo () ; } void panda :: truc () { fluo () ; // erreur ambiguité: } panda :: fluo ()
il n'est pas possible d'écrire
Panda :: (char * nomi, short loc, char sex) : en voie d'extinction ("Chine"), Herbivore (Bambou), Ours ("BlaBlaBla") "panda", o, Panda, Miocene {name = new char [strlen (nomi)+ i ] ; strepy (name, nomi) ; tell = loi ; // membre de Animaldezoo --- }
AnimaldeZoo, Ours, En voie d'Extinction, Herbivore.
2. chaque membre de type class dans l'ordre de déclaration.
3. le corp du constructeur de la classe.
Base * p ;
p = new dérivée ; // dérivé sous class de Base
p ® non virtual (); // Base :: non virtual () ;
p ® virtuelle () ; // dérivée :: virtuelle () ;
L'intérêt de cette encapsulation est énorme car la gestion manuelle ce de polymorphisme est toujours délicate surtout quand elle se conjugue avec des difficultés de conception ou de maintenance.
Alors pourquoi ne pas tout définir en virtual ?
- Pour une raison d'efficacité, les fonctions non virtuelles peuvent être "inlinées", ce qui est faux pour les virtual ! la différence de vitesse d'exécution est très importante. (Par contre la ¹ de vitesse entre édition de liens statiques et dynamiques est peu importante.)
Cette classe Base est une classe ABSTRAITE car nous avons déclarer une fonction membre pure virtual, la classe Base ne peut être instanciée. Toute classe dérivée qui ne définit pas la fonction void Kboom (int) sera aussi abstraite.
Class Base {--- public : virtual void Boom () {cout << "Boom" ;} // virtual virtual void k Boom (int i) = 0 // pure virtual --- };
Z la signature et la valeur de retour d'une fonction virtuelle doit être exactement les mêmes dans toutes les classes.
Class Dérivée : public Base { public : void Boom () {cout <<"Boom dérivée";}//.virtual void Boom (int) {x---}//non virtuelle, surcharge locale //Int Boom () {---} // n'est pas la fonction virtuelle Void k Boom (int i) {cout << "hi=" << i << ende ; // obligatoire pour instancier // dérivée --- };
Les non ¹ pour chaque destructeur ne posent pas de problème.
Base* p = new dérivée ; p®boom () ; // virtuel Dérivée :: Boom () p® Base :: Boom (); // non virtuel // mais si Boom fait un appel a une virtuelle (ex : kBoom (int)) l'appel sera virtuel
class véhicule {---} ;Que fait on avec :
class Avion : public véhicule {---} ;
class Bateau : public véhicule {---} ;
class hydravion : public Avion, public Bateau {---}
Cette interprètation avec double héritage des membres de véhicule peut être la bonne si l'on considère que véhicule contient un accord des mines et que l'accord des mines pour un hydravion contient un accord pour un avion et un accord pour un bateau, cet accord étant stocké dans véhicule.
De même à une instance d'hydravion on demandera l'accord des mines soit pour un avion, soit pour un bateau.
Trois appels possibles :
Hydravion H ; // H. accord () ; ambiguë d'où l'écriture de :
void hydravion :: accord (){ Avion :: accord () ; Bateau :: accord () ; }
Attention l'appel suivant reste ambiguë.H. accord () ;
H. avion :: accord () ;
H. bateau :: accord () ;
H. véhicule :: accord () ;
Cette duplication est coûteuse et parfois inutile, il nous faut une syntaxe permettant la nom duplication. C'est le rôle de l'héritage virtuel.
Dans le cas d'une dérivation virtuelle, il
est possible d'appeler le constructeur de la classe virtuelle.
Hydravion :: hydravion () : véhicule (3), avion (), bateau
() {}
Une classe virtuelle est initialisée par
la classe la plus dérivée.
Que se passe-t-il avec une fonction membre de la classe véhicule redéfinie dans avion ?
Héritage privé et virtuel ?
Class avion : private virtual véhicule {---} ;
Class bateau : public virtual véhicule {---} ;
Class hydravion : public avion, public bateau {} H ;H. accord () ; // non ambiguë seul l'héritage par
// bateau est visible.
L'instance la plus dérivée du membre domine la chaine d'héritage.
AX370. Localization () : // Avion :: localization ()
1) Les virtuelles immédiates : ici jouet ()
Class personnage {---} ; class personnage de livre: public personnage {---} ; class ours en peluche : PublicPersonnageDeLivre, public ours, public virtual jouet {--} ;
2) Les virtuelles des classes de Base dans l'ordre d'héritage des classesdeBase:
AnimalDeZoo () // dans ours.
3) Les classes de Base dans l'ordre d'héritage :
Personnage () ; // class de Base PersonnageDeLivre
PersonnageDeLivre () ; // ? class de Base
Ours () ;
Ours en peluche () ;
class pile : private tableau < int > { } ,Une classe template définie à partir d'une classe quelconque.
template < class types >Extension d'un template :
class dérivée : public Base ;
{
type vol ;
} ;
template < class type >
class TableauBorné : public virtual Tableau < type > {
};
// le type
// doit être précisé
// sinon erreur