• Aucun résultat trouvé

Deuxième version de la classe personne

4 L ES CLASSES D ' OBJETS

Exemple 24. Deuxième version de la classe personne

Commentaires

En comparant à la première version de la classe, la définition de la classe personne a ici véritablement changé. La structure de chacun des objets instanciés à partir de cette classe est résolument différente.

En effet, là où nous avions auparavant un tableau de caractères appelé nom dans la première version de la classe, nous n’avons plus qu’un pointeur sur une chaîne de caractères.

Où sera stockée la chaîne ? Le tableau de caractères nécessaire au stockage des caractères du nom de la personne est désormais alloué dynamiquement par le constructeur de l’objet.

Le constructeur de cette classe est donc:

personne::personne ( char *nom_p, unsigned age_p) {

ptrNom=new char[strlen(nom_p)+1]; // demande de la mémoire pour le nom if(ptrNom==NULL)

{

cout << "Mémoire insuffisante ... Abandon ...\n"; exit(1);

}

strcpy(ptrNom,nom_p); // initialise la chaîne pointée par ptrNom age=age_p; // initialise age

cout << "Le constructeur de la personne (" << ptrNom << "," << age << ") a été appelé\n";

}

Avant d'affecter un nom à l'objet de la classe personne, le constructeur demande de l'espace pour loger ce nom et fait pointer le champ ptrNom dessus. Un test d'insuffisance mémoire est également fait. Le destructeur évolue alors comme suit :

personne::~personne () {

cout << "Le destructeur de la personne (" << ptrNom << "," << age << ") a été appelé\n";

delete ptrNom; // restitue la mémoire pointée par ptrNom }

Résultats écran

Le constructeur de la personne (Jacques,20) a été appelé Le constructeur de la personne (Louise,18) a été appelé Le destructeur de la personne (Jacques,20) a été appelé Le destructeur de la personne (Louise,18) a été appelé

Rappelons-nous que le destructeur est appelé avant la destruction définitive de l'objet. Ici, il prend soin de restituer l'espace mémoire pointé par le champ ptrNom devenu inutile puisque l'objet va être détruit. Le constructeur et le destructeur participent à la sûreté d'utilisation d'un objet. Leur présence assure que l'objet est toujours initialisé et détruit proprement.

4.8 Objets avec des données en profondeur

On discute ici les différences fondamentales entre les deux versions de la classe personne présentées précédemment. Le lecteur est donc invité à se reporter aux deux définitions de la classe personne faites respectivement p.49 et p.52. Les exemples précédents mettent en évidence deux techniques très différentes pour gérer des données liées à un objet. On va ici illustrer et comparer ces méthodes.

Première version de la classe personne

class personne {

private :

char nom[20]; // tableau de 20 caractères

unsigned age;

public:

// …

};

En déclarant un objet automatique de cette première version de la classe, personne p1("martin",25);

on obtient la structure suivante :

On peut en effet voir un objet comme une structure avec plusieurs champs de données. Avec la première version de la classe, chaque objet contient 20 caractères + 1 entier non signé. Toutes ces données sont stockées au sein même de l’objet.

Inconvénients de cette version de la classe :

. les noms stockés sont forcément limités à la taille du tableau. Ici, les noms doivent faire au plus 19 caractères. . en outre, si le nom stocké est très court, une grande partie du tableau n’est pas utilisée. Dans le cas de l’objet p1, 13 caractères sont réservés inutilement.

Avec cette technique de stockage des données, soit on manque de place dans l’objet, soit on gaspille des mots mémoire inutilement. nom age Méthodes Données m a r t i n \0 25 objet p1 de la classe personne

Seconde version de la classe personne avec données en profondeur

class personne {

private:

char * ptrNom; // pointeur sur une chaîne de caractères

unsigned age; public:

// … };

Les objets instanciés à partir de la seconde version de la classe ont la structure suivante :

Un objet de cette version de la classe personne ne contient plus directement les données liées au nom d’une personne. Chaque objet de cette classe ne contient que l’adresse où est stockée la chaîne de caractères en mémoire. La zone de stockage de ces caractères est allouée par le constructeur de l’objet et désallouée par le destructeur de l’objet. On dit que la chaîne de caractère est stockée en profondeur. En effet, à la différence de la première version, on voit bien que les caractères ne sont plus vraiment dans l’objet, mais dans une zone mémoire distante. On retrouve néanmoins sans problème les données en profondeur par l’intermédiaire du pointeur qui, lui, est tout de même dans l’objet (ptrNom).

Intérêt de cette gestion des données

En stockant les données en profondeur (c’est-à-dire dynamiquement), la zone mémoire réservée pour l’objet correspond effectivement précisément au besoin de l’objet. Il n’y a donc plus de limitation de la taille des noms stockés et, à l’inverse, un nom court ne gaspille pas de la mémoire.

Au vu de cette comparaison entre les deux versions de la classe, il apparaît que la première version de la classe personne n’est pas une bonne solution, bien que plus simple à mettre en oœuvre. Aussi, dès que les objets d’une classe n’auront pas tous des données de même taille, il conviendra de stocker ces données en profondeur, et donc d’utiliser la technique présentée dans l’exemple 3 de ce chapitre.

ptrNom age Méthodes Données

zone de stockage

"en profondeur"

m a r t i n \0 25

4.9 Les méthodes constantes

Nous allons ici analyser un nouveau motif d’emploi du modificateur const. Réexaminons la classe personne à laquelle nous avons ajouté une méthode de modification de l’âge setAge().

//personne_v3.h – version 3 de la classe personne #ifndef __CLS_PERSONNE_V3_ #define __CLS_PERSONNE_V3_ #include <iostream.h> #include <string.h> #include <process.h> class personne { private: char * ptrNom; unsigned age; public: personne(char *,unsigned ); ~personne(void);

void identifie() const; void setAge(unsigned);

unsigned getAge() const; };

void personne::identifie(void) const {

cout << "Je m'appelle "<< ptrNom << " et j'ai " << age << " ans\n"; }

void personne::setAge(unsigned a) {

age=a; }

unsigned personne::getAge() const {

return age ; }

personne::personne ( char *nom_p, unsigned age_p) {

ptrNom=new char[strlen(nom_p)+1]; if(ptrNom==NULL)

{

cout << "Mémoire insuffisante ... Abandon ...\n"; exit(1);

}

strcpy(ptrNom,nom_p); age=age_p;

cout << "Le constructeur de la personne (" << ptrNom << "," << age << ") a été appelé\n";

}

Cette méthode, qui n’est pas constante, ne peut pas être appelée sur un objet constant.

Le const doit figurer ici également. Il fait partie de la signature d’une méthode. Ce const indique qu’il s’agit d’une méthode constante. Une méthode constante peut être appelée sur un objet variable ou constant.

personne::~personne() {

cout << "Le destructeur de la personne (" << ptrNom << "," << age << ") a été appelé\n"; delete ptrNom;

} #endif

Documents relatifs