1 Département Informatique
Département Informatique
Les classes assistantes
Programmation objet
Cours n°4
Département Informatique Département Informatique
Classes assistantes
Introduction
Une classe (un objet) représente souvent un concept, une entité du programme.
Fréquemment, un de ces objets a besoin d'une entité extérieure, qui peut être une simple fonction (si le langage le permet),
mais qui souvent doit être représenté par une classe.
De telles classes, n'existant que pour être utilisées par un autre objet, sont nommées classes assistantes.
Elles sont souvent déclarées à l'intérieur de la classe assistée.
3 Département Informatique
Département Informatique
Exemple de classe assistante : les itérateurs
Dans un objet conteneur (par ex. la liste de la STL), il est souvent nécessaire de fournir un moyen pour accéder aux éléments, parcourir le conteneur, etc...
Un simple pointeur est souvent inefficace (risqué, bas niveau).
Une possibilité est de faire appel à une classe itérateur pour assister la collection.
Cette classe va encapsuler le code, parfois complexe, nécessaire pour parcourir la collection et accéder aux éléments.
Département Informatique Département Informatique
template <class T> class vecteur {
private:
T *p_tab;
int nb_elt;
public:
vecteur(int _taille):nb_elt(_taille) { p_tab=new T[nb_elt];
}
~vecteur()
{delete[] p_tab;}
T& operator[] (int _indice) {
return p_tab[_indice];
attributs :
adresse du tableau nombre d'éléments
5 Département Informatique
Département Informatique class iterator {
private:
int Indice;
vecteur& parent;
public:
iterator(int i=0, vecteur& v):Indice(i):parent(v){}
iterator& operator++() {
if(Indice<parent.size()) ++Indice;
return *this;
}
iterator& operator--(){...}
bool operator== const (const iterator&){...}
bool operator!= const (const iterator&){...}
T& operator*(){return parent[Indice];}
};
classe assistante interne
encapsulation de l'indice
permet une avancée sécurisée dans
la collection accès à la valeur
contenue dans la collection
Département Informatique Département Informatique
iterator begin() {
return iterator(0, *this);
}
iterator end() {
return iterator(nb_elt, *this);
}
iterator find(const T& _val){...}
};
Les méthodes de iterator se contentent d'encapsuler la notion réelle utilisée ici (un simple indice) tout en vérifiant un éventuel dépassement.
renvoie un itérateur sur le 1er élément
renvoie un itérateur sur un élément fictif
suivant le dernier élément de la collection
7 Département Informatique
Département Informatique
Exemple de classe assistante : pointeur intelligent
Dans de nombreux objets, on doit utiliser un pointeur au lieu d'une valeur : utilisation du polymorphisme, allocation dynamique, etc...
Mais dans ce cas, la responsabilité de la libération mémoire incombe au programme : risques de fuites mémoire, libération trop précoce...
Or le principe est toujours le même dans ce cas : un objet alloue de la mémoire, en cas de recopie de celui-ci « passe la responsabilité » à un autre objet (une autre instance), le dernier objet présent en mémoire devant absolument libérer la mémoire.
On a donc tout interet à confier ce travail à une classe particulière : Un pointeur intelligent (smart pointer)
Département Informatique Département Informatique
La bibliothèque standard fournit un pointeur intelligent : la classe std::auto_ptr<>
Cette classe permet d'utiliser l'idiome RAII.
Elle se contente néanmoins de peu et ne permet pas la recopie : son opérateur = se contente d'un transfert de propriété.
Cette classe n'est pas adaptée à un certain nombre d'utilisations,
par exemple être contenu dans une collection (comme std::vector)
9 Département Informatique
Département Informatique
Présentation de notre classe de pointeur intelligent : Doit fournir une sémantique de valeur à un objet dynamique.
Doit pouvoir se comporter comme un « vrai » pointeur.
Doit pouvoir gérer le partage de l'objet (plusieurs propriétaires)
Doit gérer automatiquement la libération mémoire, au bon moment.
Doit permettre le polymorphisme dynamique.
Pour tout ceci, la classe devra donc contenir un pointeur et un compteur de références, regroupées dans une même sous-classe, privée pour éviter leur manipulation par l'utilisateur.
Ne doit pas dépendre du type de données pointées.
Département Informatique Département Informatique
Opérations fournies par la classe :
Construction à partir d'un « vrai » pointeur. La classe prend alors la « responsabilité » du pointeur : elle s'occupera de sa libération.
Accès à la valeur pointée : définition de l'opérateur * et de l'opérateur ->
Gestion de la recopie : l'objet ayant une sémantique de valeur, il
est amené à être recopié. Définition donc d'un constructeur par recopie et d'un opérateur = qui devront incrémenter le compteur de références.
Plusieurs instances de cette classe devront référencer le même objet en mémoire : définition d'une sous-classe assistante, associant l'adresse réelle de l'objet et un compteur.
11 Département Informatique
Département Informatique
iut::shared_ptr
Classe de pointeur intelligent de ty pe "partagé"
Projet : Workspace
Auteur : A.G. - IUT DIJON - IQ
Version : 1 Créé le : 25/9/2006 Modif ié le : 25/9/2006
<< auxiliary >>
- shared_ptr_ref + ptr
+ count + Prend + Libere + Prend + Libere
<< metaclass >>
+ shared_ptr + ptr_ref
Département Informatique Département Informatique
3
xxxx
valeur T shared_ptr_ref<T>
xxxx
xxxx
xxxx
shared_ptr<T>
13 Département Informatique
Département Informatique Construction normale d'un shared_ptr
En entrée : un « vrai » pointeur sur T (alloué)
Action : création (allocation) d'un shared_ptr_ref avec 1 référence Construction normale d'un shared_ptr_ref Action : simple initialisation des champs
En entrée : pointeur sur T
Département Informatique
Département Informatique Copie d'un shared_ptr
En entrée : un autre shared_ptr
Action : libère une référence du shared_ptr_ref puis ajoute une référence au shared_ptr_ref contenu dans le paramètre, le recopie.
Libération d'un shared_ptr
Décrémente le compteur de référence. S'il est nul, détruit le pointeur.
15 Département Informatique
Département Informatique
Exemple de classe assistante : optimisation de calcul matriciel Imaginons l'utilisation d'une classe MATRICE avec la redéfinition des opérateurs classiques (+, -, etc...) pour les calculs matriciels.
Les opérateurs comme + posent un problème : de par leur nature, ils doivent renvoyer une valeur.
MATRICE operator+(const MATRICE& m1, const MATRICE& m2) {
MATRICE retour;
// calcul de retour return retour;
}
Il y a donc passage obligatoire vers une (au moins) valeur temporaire, avec recopie, etc...
Département Informatique Département Informatique
L'utilisation de références ne peut pas être faite ici, car on ne peut pas référencer une valeur temporaire...
Examinez ce code :
MATRICE m1, m2, m3;
m1.Saisir();
m2.Saisir();
m3 = m1 + m2;
Une quatrième matrice
est créée ici, dans l'opérateur d'addition, puis recopiée dans m3...
Il serait donc plus intéressant d'effectuer le calcul au moment de l'affectation...
Ici aussi, une classe assistante (appelée dans ce cas proxy) peut nous aider.
17 Département Informatique
Département Informatique
class ADDMAT {
MATRICE& m1;
MATRICE& m2;
public:
ADDMAT(MATRICE& _m1, MATRICE& _m2);
};
Cette classe contenant deux références, elle ne recopie aucune matrice et donc n'occupe pratiquement pas d'espace mémoire, même avec
de « grosses » matrices.
Nous pourrions libeller alors l'opérateur + différemment :
Département Informatique Département Informatique
ADDMAT operator+(const MATRICE& m1, const MATRICE& m2) {
return ADDMAT(m1,m2);
}
Celui-ci n'effectue plus de calculs, mais la valeur renvoyée, bien qu'elle aussi temporaire, se réduit à deux références...
sa recopie n'est pas coûteuse.
Modifions alors l'opérateur = de la matrice : MATRICE& operator=(const ADDMAT& _am);
L'opérateur reçoit en fait la référence des deux matrices à ajouter : il peut directement faire le calcul, sans aucun temporaire !
19 Département Informatique
Département Informatique
De cette manière, le code :
M3 = m1 + m2;
Correspondra en fait à :
M3.operator=( operator+(m1,m2) );
Et pourra effectuer l'addition sans aucun temporaire.
Attention : le code m1+m2+m3 ne pourra pas fonctionner ! On pourrait de même effectuer l'opération m3 = m1*m2 + m
Sans aucun temporaire, grâce à quelques classes assistantes simples, tout le calcul étant délocalisé dans l'opérateur =.
Département Informatique Département Informatique
Prochain cours :
Structures de données – partie 1 tableaux, listes chaînées piles, files d'attente
A la semaine prochaine !