1
Plan – POO
Les concepts de base de la POO
Encapsulation / Classe
Héritage / Sous-Classes / Classes dérivées
Classes abstraites
Héritage multiple
Polymorphisme
2
Classes abstraites
Idée intuitive :
Une classe abstraite est une classe dans laquelle on a défini une (ou des méthodes) pour laquelle on a explicitement dit :
Qu’elle ne sera pas implémentée.
L’implémentation de ces méthodes appelées méthodes virtuelles « pures » est laissée à la charge des classes dérivées.
On ne peut donc pas instancier des objets de cette classe, mais uniquement des classes dérivées qui auront implémentées ces méthodes virtuelles.
3
Classes abstraites
L’objectif d’une classe abstraite est de permettre la définition de profils de méthodes
Qui devront être implémentées dans toutes les classes dérivées.
Et donc de définir un « comportement » commun à un ensemble de classes dérivées.
4
Classes abstraites
Déclaration d’une méthode virtuelle pure :
virtual type nom (liste de paramètres) = 0;
Si une classe contient une méthode abstraite,
aucun objet de cette classe ne peut être manipulé.
Nécessité d'employer le mécanisme d'héritage et de spécialisation du descendant
pour définir le corps de la méthode
Classes abstraites
On considère par exemple la classe CElement qui possède une méthode de dessin qui n’est pas implémentée.
class CElement { …
virtual void Draw() = 0;
… };
Chaque sous-classe doit spécifier cette implémentation.
La déclaration d'un objet de type CElement est incorrecte
CElement elem; // déclaration incorrecte
Classes abstraites
Description d’une classe dérivée CLine qui implémente la méthode abstraite « Draw »
class CLine : public CElement { ...
public : void Draw();
};
void CLine:: Draw () { // dessin de la ligne }
On peut alors déclarer des objets de type CLine
CLine ligne; // déclaration correcte
7
Héritage multiple
Une classe peut dériver d’une ou plusieurs classes.
Supposons que l’on dispose :
d’une classe « CEnseignant »,
et d’une classe « CChercheur »
et l’on souhaite définir une classe dérivée :
« Enseignant_Chercheur »
CChercheur CEnseignant
Enseignant_Chercheur
8
Héritage multiple
On peut définir la classe Enseignant_Chercheur à l’aide du mécanisme de d’héritage multiple :
class Enseignant_Chercheur : public CEnseignant, public CChercheur
La classe Enseignant_Chercheur hérite des attributs et méthodes des classes CEnseignant et CChercheur.
9
Héritage multiple
La possibilité de définir des héritages multiples peut conduire à des ambiguïtés:
class CEnseignant { char grade[MAX];
...
public :
void lire_grade(char *); // méthode lire_grade };
class CChercheur { char grade[MAX];
...
public :
void lire_grade(char *); // méthode lire_garde(homonyne) };
class Enseignant_Chercheur : public CEnseignant, public CChercheur { ...};
10
Héritage multiple
Existence de méthodes des classes mères (« sur-classes ») qui ont des noms identiques.
L’ambiguïté peut être levée en précisant explicitement la classe mère considérée lors de l’appel
opérateur de résolution de portée (::)
Héritage multiple
Enseignant_Chercheur ec;
déclaration d’un “Enseignant_Chercheur”
ec.CEnseignant:: lire_grade(mot);
mot est le grade de l’enseignant
ec.CChercheur:: lire_grade(mot);
mot est le grade du chercheur
Héritage multiple
Une classe dérivée peut avoir dans ses ascendants plusieurs fois la même classe de base.
L’ambiguïté concerne le « partage » de la classe mère.
CChercheur CEnseignant
Enseignant_Chercheur CPersonne
13
Héritage multiple
Y-a-t-il un seul objet de la classe mère ou deux indépendants ?
Pour obtenir un seul objet de la classe « mère », les classes dérivées « CEnseignant » et « CChercheur » peuvent être définies comme virtuelles :
class CEnseignant : public virtual CPersonne
class CChercheur : public virtual CPersonne
lors de la définition de la classe
«Enseignant_Chercheur ».
14
Héritage multiple
Le rôle de “virtual” est de signaler au compilateur
que les classes dérivées de ces classes virtuelles nécessitent un traitement spécial
suppression des classes ancêtres “en double”
Un objet est alors vu comme :
un unique objet de la classe “CPersonne”
et les spécificités des classes “CEnseignant” et
“CChercheur”.
15
Polymorphisme
Le polymorphisme est un mécanisme puissant,
très utile dans la spécification d'une hiérarchie de classes.
L'idée intuitive est de permettre l'application d'une "même" opération à des objets différents.
La mise en oeuvre de cette propriété est réalisée par deux mécanismes en C++ :
la surcharge
l'édition de liens dynamique (ligature dynamique)
16
Polymorphisme
1 er cas : la surcharge
La surcharge offre la possibilité de définir
des méthodes homonynes
(même nom et mêmes paramètres)
dans une classe et dans ses classes dérivées.
Polymorphisme
2 ème cas : la ligature dynamique
Ce mécanisme est encore plus puissant que la surcharge car il permet :
en cas d'ambiguité sur le choix de la méthode homonyne à utiliser,
de différer cette décision jusqu'au moment de l'appel de la méthode
Génération de tous les codes possibles,
et décision lorsqu'on connaît la classe de l'objet à traiter
Polymorphisme
On considère tout d’abord une classe « CPersonnage ».
Un personnage représente un personnage d’un jeu.
Tous les personnages ont des caractéristiques communes comme un nom, des points de vie, une position courante, etc.
Et on dispose de méthodes pour
« dessiner » un personnage,
« effacer » un personnage
et aussi le « déplacer ».
19
Polymorphisme
class CPersonnage { protected :
char nom[30];
int posx, poy;
… public
CPersonnage(); // construteur
void dessiner(); // méthode qui permet de dessiner à l’écran // le personnage à la position courante posx, poy void effacer(); // pour effacer le personnage à l’écran void deplacer(int, int); // pour effacer le personnage dessiné,
//changer sa position et le redessiner };
20
Polymorphisme
On souhaite définir une classe « CMagicien » dérivée de la classe « CPersonnage" .
Un magicien est spécialisé par rapport à un personnage par :
l'ajout de nouveaux attributs comme les
« sortilèges » qu’il possède …
l'ajout/surcharge des méthodes membres et notamment on souhaite pouvoir
« dessiner », « effacer » et « deplacer » un magicien
21
Polymorphisme
class CMagicien : public CPersonnage { private :
char sortileges[10][30];
… public : CMagicien();
void dessiner () ; void effacer ();
}; ….
22
Polymorphisme
Pour dessiner un magicien, on suppose qu’il faut
d’abord dessiner son personnage
puis dessiner les caractéristiques spécifiques du magicien (chapeau, cape, …)
void CMagicien :: dessiner () { CPersonnage:: dessiner();
… // dessin des spécificités du magicien }
L’appel de la méthode de dessin du personnage est faite l’instruction :
CPersonnage ::dessiner();
Polymorphisme
De même pour effacer un magicien, on doit d’abord effacer son personnage puis effacer ses caractéristiques spécifiques (chapeau, cape, …)
void CMagicien :: effacer () {CPersonnage:: effacer();
… // « effacement » des spécificités du magicien }
L’appel de la méthode d’effacement du personnage est faite par l’instruction :
CPersonnage :: effacer();
Polymorphisme
La méthode « deplacer » de la classe CPersonnage effectue le traitement suivant :
Effacement du personnage à sa position courante
Modification de la position courante du personnage
Dessin du personnage à sa nouvelle position void CPersonnage :: deplacer ( int nx, int ny ) { effacer();
posx=nx; posy=ny;
dessiner();
}
25
Polymorphisme
Déplacer un magicien consiste à :
Effacer le magicien à sa position courante
Changer la position
Dessiner le magicien à la nouvelle position
Cette méthode est donc
« similaire » à la méthode définie pour un personnage
excepté qu’il faut utiliser les méthodes « dessiner » et
« effacer » de la classe dérivée CMagicien au lieu de celles de la classe CPersonnage.
La classe CMagicien hérite de la méthode « deplacer ».
26
Polymorphisme
On souhaite récupérer la méthode « deplacer » définie dans la classe CPersonnage
Cette méthode « deplacer » fait appel aux méthodes
« dessiner » et « effacer »
dont il existe deux versions homonymes sur les classes CPersonnage et CMagicien
Nécessité de s'assurer que ce sont bien les méthodes
« dessiner » et « effacer » de la classe CMagicien qui seront utilisées pour un magicien.
27
Polymorphisme
Le compilateur n'est pas en mesure de faire le choix car lors de la compilation,
on ne sait pas encore de façon précise lequel des descendants va demander l'exécution de la méthode « déplacer ».
Nécessité de différer le choix de l'homonyme à appeler
et prévoir tous les cas possibles.
On appelle ligature dynamique cette méthode de résolution d'appels.
28
Polymorphisme
Le choix qui a été fait en C++ est de demander à l'utilisateur :
de signaler par le mot clé virtual toute méthode
pour laquelle la ligature doit être dynamique.
On parle de méthodes virtuelles.
Polymorphisme
class CPersonnage /* deuxième version */
{ protected : char nom[30];
int posx, poy;
public…
CPersonnage();
virtual void dessiner(); // méthode virtuelle virtual void effacer(); // méthode virtuelle void deplacer(int, int);
};
Polymorphisme
On pourra alors de façon transparente, utiliser « deplacer » sur un
« CPersonnage » ou sur un « CMagicien ».
CPersonnage perso;
perso.deplacer();
/* utilise les méthodes « dessiner » et « effacer » de personnage dans la méthode « deplacer » */
CMagicien mage;
mage.deplacer();
/* utilise les méthodes « dessiner » et « effacer » du magicien dans la méthode « deplacer » sans redéfinir cette méthode dans la classe CMagicien*/
Lors des appels, le compilateur va détecter le type d’objet réalisant l’appel et aller sélectionner la méthode adéquate.
31
Programmation avancée en C++
Rappel : passages de paramètres
La généricité (patrons)
Les exceptions
Les flots
32
Langage C++
Rappel : passages de paramètres
Il n’existe en langage C qu’un mode de passage des paramètres qui est
le mode lecture aussi appelé par valeur.
Pour faire une passage en écriture ou lecture/écriture i.e. par adresse, il faut le faire explicitement :
À la charge du programmeur
33
Langage C++
Rappel : passages de paramètres
Exemple 1 : Passage par valeur
On considère la fonction suivante : int addition(int x, int y)
{return x+y;} // les valeurs de x et y sont seulement « lues »
Avec les définitions suivantes :
int u=1, v=2, res;
int *pu, *pv;
pu = new int; *pu = 4; pv=&v;
Les deux appels ci-dessous sont corrects
res = addition(u,v);
res= addition (*pu, *pv);
34
Langage C++
Rappel : passages de paramètres
Exemple 2 : Passage par adresse « explicite »
On considère la fonction suivante : void echange(int *px, int *py)
{int z; z= *px; *px= (*py); *py = z; }
// les valeurs pointées par px et py peuvent être « modifiées »
Avec les définitions suivantes :
int u=1, v=2, res;
int *pu, *pv;
pu = new int; *pu= 4; pv=&v;
Les deux appels ci-dessous sont corrects
echange(&u,&v);
echange (pu, pv);
Langage C++
Rappel : passages de paramètres
Le langage C++ a introduit un mode de passage par référence
void echange(int & x, int & y)
{ int z; z=x; x=y; y=z;}
L’appel peut se faire sous la forme :
int u=4, v=5;
echange(u,v);
Langage C++
Rappel : passages de paramètres
Le langage C++ a conservé le mode de passage par valeur du langage C.
int addition(int x, int y)
{ return x+y;}
L’appel peut se faire sous la forme
int u=4, v=5, w ;
w= addition(u,v);
37
Langage C++
Rappel : passages de paramètres
Lors un paramètre doit être passé en lecture (par valeur) et que son type n’est pas un type de base (int, float, …).
On peut utiliser une autre forme proposée par C++,
Qui consiste à faire une passage par référence mais en précisant que le paramètre effectif ne sera pas modifié
Économie d’espace et gain de vitesse
void echangePerso(const CPersonnage &p1, const CPersonnage &p2);
L’appel peut être fait sous la forme : CPersonnage perso1, perso2;
…
echange (perso1, perso2);
38
Généricité
Définir un modèle
de fonctionnement d'une fonction,
de définition d'une classe
qui puisse s'appliquer à différents types de données.
Par exemple, pour une pile d'objets
Les opérations de manipulation de la pile sont
"indépendantes" du type des objets manipulés
empiler, dépiler, est_vide, sommet
39
Généricité
Le type des objets peut être
simple : entier, caractère, …
ou complexe : classe
sans influence sur le mode de fonctionnement de la pile
Généricité : avoir des outils qui permettent de manipuler des objets "formels"
choix du type "effectif" lors de la déclaration de la pile
instanciation du type générique
40
Généricité
Autre généricité souhaitée :
possibilité de définir le fonctionnement d'une fonction
sans utiliser le mécanisme de surcharge pour chaque type possible
Mais en la décrivant de façon "formelle"
Instanciation au moment de l'appel en fonction du type des paramètres effectifs utilisés
Généricité des fonctions
Exemple :
On souhaite définir une fonction qui retourne la valeur maximum de deux valeurs passées en paramètres.
utilisable sur tous les types de base int, char, float, double, etc...
1ère solution :
Surcharger la fonction pour tous les types possibles
int max (int x, int y);
float max (float x, float y);
Solution lourde et peu satisfaisante
Généricité des fonctions
2ème solution :
Définir "max" sur un type formel
Spécification d'un nom de type formel T
Utilisation de ce type formel pour décrire la fonction template <class T>
T max (T x, T y) { if (x > y)
return (x);
else return(y);
}
43
Généricité des fonctions
Utilisation de cette fonction paramétrée void main (void)
{ int u=5, v=6, w;
float s=5.7, f=7.8, g;
/* appel de max avec les entiers */
w= max (u,v);
cout << "max entier = " << w;
/* appel de max avec les réels */
g= max(s,f);
cout << "max réel = " << g;
}
44
Généricité des fonctions
Remarques
Le type T peut être un nom de type (ici int, float) ou de classe
T peut être un type prédéfini pour lequel l'opérateur de comparaison ">" est prédéfini
ou bien T peut être un type utilisateur pour lequel l'opérateur ">" a été surchargé
pas de limite grâce au mécanisme de surcharge des opérateurs
45
Généricité : les modèles de classe
Modèles de classe = classes génériques = patrons
Utilisation de la notion de type formel pour définir une classe générique.
Supposons par exemple définir une classe CEnsemble qui peut contenir n’importe quels types d’objets avec des fonctionnalités classiques
Ajout d’un élément
Suppression d’un élément
Test si un élément est dans l’ensemble
Union de 2 ensembles
Etc.
46
Généricité : les modèles de classe
Définition de la classe générique
T est le nom du type formel utilisé pour la définition de la classe template <class T>
class CEnsemble { private :
T tab[MAX];
// L’ensemble est géré par un tableau de MAX éléments de type T int nbe;
public : CEnsemble();
void ajout(T);
void supprime(T);
bool appartient(T);
… };
Généricité : les modèles de classe
Définitions des différentes méthodes
rappel de la forme générale :
type_retour nom_classe :: nom_fonction (paramètres)
Ici, la classe est une classe générique il faut donc
Préciser le nom du paramètre formel utilisé pour la description de la méthode
template <class T>
Donner le nom de la classe générique
CEnsemble<T>
Généricité : les modèles de classe
Définition du constructeur : template <class T>
CEnsemble<T> :: CEnsemble() { nbe=0;}
Définition de la méthode « ajout » template <class T>
void CEnsemble<T> :: ajout (T elem) { if (nbe < MAX && ! appartient(elem))
tab[nbe++]=elem;
}
49
Généricité : les modèles de classe
Définition de la méthode « appartient » template <class T>
bool CEnsemble<T> :: appartient (T elem) { bool trouve = false;
int i=0;
while (i < nbe && !trouve)
if (tab[i] == elem) trouve=true;
else i++;
return trouve;
}
Cette méthode utilise l’opérateur == pour comparer 2 objets du type formel T
Il faudra que cet opérateur soit défini pour le type effectif utilisé pour instancier la classe CEnsemble, i.e.
Type de base (int, float, …)
Classe définie par l’utilisateur mais avec une surcharge de l’opérateur ==
50
Généricité : les modèles de classe
Utilisation de cette classe générique.
Instanciation du type formel par un type effectif à la déclaration d'un objet de cette classe
CEnsemble<int> ensEntier;
Instanciation du type T par le type int,
Déclaration de la variable ensEntier comme étant un ensemble d’entiers
Le type int possède l’opérateur de comparaison ==
51
Généricité : les modèles de classe
Censemble<CPersonnage> ensPerso;
Instanciation du type T par le type CPersonnage,
Déclaration de la variable ensPerso.
Cette instanciation est correcte seulement si l’opérateur == a été défini dans la classe CPersonnage.
bool operator==(CPersonnage p);
52
Généricité : les modèles de classe
Le nombre de paramètres formels n'est pas limité à 1,
on peut en avoir plusieurs.
template <class X, class Y>
class Compose { private :
X attribut1;
Y attribut2;
public : ...
};
Généricité : les modèles de classe
Une classe peut être paramétrée
par "autre chose" qu'un type formel
par exemple une valeur qui représente une taille, ...
Généricité
Définition d'un buffer d'objets
Généricité sur le type des objets
Généricité sur la taille du buffer template <class T, int taille = 64>
/* 64 est la valeur par défaut de l'élément taille */
class Buffer { T *buf;
public : /* constructeur */
Buffer();
...
};
55
Généricité
Définition d'un buffer d'objets
Définition du constructeur qui alloue dynamiquement le buffer.
template <class T, int taille=64>
Buffer<T,taille> :: Buffer() { buf= new T [taille];
}
Le nom de la classe générique est Buffer<T,taille>
L’instruction buf= new T [taille] alloue un tableau de taille « taille » d’éléments de type T.
56
Généricité
Définition d'un buffer d'objets
Déclaration d'un objet de la classe
b est une variable de type Buffer avec une instanciation du type formel avec des entiers et une taille fixée à la valeur 100.
Buffer <int,100> b;
Tableau de 100 entiers,
réservation dynamique des éléments par appel du constructeur de la classe générique
57
Généricité - Conclusion
La généricité est un mécanisme qui permet :
De définir des méthodes ou des classes formelles manipulant des types d'objets
« généraux ».