• Aucun résultat trouvé

Conteneur d’objets polymorphiques

#include <iostream>

using namespace std;

/************* LA CLASSE ABSTRAITE DE BASE *****************/

class Object {

unsigned long int new_handle(void);

protected:

unsigned long int h; // Identifiant de l’objet.

public:

Object(void); // Le constructeur.

virtual ~Object(void); // Le destructeur virtuel.

virtual void print(void) =0; // Fonction virtuelle pure.

unsigned long int handle(void) const; // Fonction renvoyant // le numéro d’identification // de l’objet.

};

// Cette fonction n’est appelable que par la classe Object : unsigned long int Object::new_handle(void)

{

static unsigned long int hc = 0;

return hc = hc + 1; // hc est l’identifiant courant.

// Il est incrémenté

} // à chaque appel de new_handle.

// Le constructeur de Object doit être appelé par les classes dérivées : Object::Object(void)

{

h = new_handle(); // Trouve un nouvel identifiant.

Chapitre 8. C++ : la couche objet

unsigned long int Object::handle(void) const {

return h; // Renvoie le numéro de l’objet.

}

/******************** LA CLASSE SAC ******************/

class Bag : public Object // La classe sac. Elle hérite // de Object, car un sac peut // en contenir un autre. Le sac // est implémenté sous la forme // d’une liste chaînée.

BagList *head; // La tête de liste.

public:

Bag(void); // Le constructeur : appel celui de Object.

~Bag(void); // Le destructeur.

void print(void); // Fonction d’affichage du sac.

bool has(unsigned long int) const;

// true si le sac contient l’objet.

bool is_empty(void) const; // true si le sac est vide.

void add(Object &); // Ajoute un objet.

void remove(Object &); // Retire un objet.

};

Bag::Bag(void) : Object() {

return; // Ne fait rien d’autre qu’appeler Object::Object().

}

Bag::~Bag(void) {

BagList *tmp = head; // Détruit la liste d’objet.

while (tmp != NULL)

Chapitre 8. C++ : la couche objet void Bag::print(void)

{

BagList *tmp = head;

cout << "Sac n " << handle() << "." << endl;

cout << " Contenu :" << endl;

while (tmp != NULL) {

cout << "\t"; // Indente la sortie des objets.

tmp->ptr->print(); // Affiche la liste objets.

tmp = tmp->next;

} return;

}

bool Bag::has(unsigned long int h) const {

BagList *tmp = head;

while (tmp != NULL && tmp->ptr->handle() != h) tmp = tmp->next; // Cherche l’objet.

return (tmp != NULL);

BagList *tmp = new BagList; // Ajoute un objet à la liste.

tmp->ptr = &o;

BagList *tmp1 = head, *tmp2 = NULL;

while (tmp1 != NULL && tmp1->ptr->handle() != o.handle()) {

tmp2 = tmp1; // Cherche l’objet...

tmp1 = tmp1->next;

}

if (tmp1!=NULL) // et le supprime de la liste.

{

if (tmp2!=NULL) tmp2->next = tmp1->next;

else head = tmp1->next;

delete tmp1;

} return;

}

Avec la classe Bag définie telle quelle, il est à présent possible de stocker des objets dérivant de la classe Object avec les fonctionsaddetremove:

Chapitre 8. C++ : la couche objet

class MonObjet : public Object {

/* Définir la méthode print() pour l’objet... */

};

Bag MonSac;

int main(void) {

MonObjet a, b, c; // Effectue quelques opérations // avec le sac :

MonSac.add(MonSac); // Un sac peut contenir un sac !

MonSac.print(); // Attention ! Cet appel est récursif ! // (plantage assuré).

return 0;

}

Nous avons vu que la classe de base servait de moule aux classes dérivées. Le droit d’empêcher une fonction membre virtuelle pure définie dans une classe dérivée d’accéder en écriture non seulement aux données de la classe de base, mais aussi aux données de la classe dérivée, peut donc faire partie de ses prérogatives. Cela est faisable en déclarant le pointeurthiscomme étant un pointeur constant sur objet constant. Nous avons vu que cela pouvait se faire en rajoutant le mot cléconst après la déclaration de la fonction membre. Par exemple, comme l’identifiant de l’objet de base est placé en protectedau lieu d’être enprivate, la classe Object autorise ses classes dérivées à le modifier.

Cependant, elle peut empêcher la fonctionprintde le modifier en la déclarantconst: class Object

virtual void print(void) const=0; // Fonction virtuelle pure.

unsigned long int handle(void) const; // Fonction renvoyant // le numéro d’identification // de l’objet.

};

Dans l’exemple donné ci-dessus, la fonctionprintpeut accéder en lecture àh, mais plus en écriture.

En revanche, les autres fonctions membres des classes dérivées peuvent y avoir accès, puisque c’est une donnée membreprotected. Cette méthode d’encapsulation est donc coopérative (elle requiert la bonne volonté des autres fonctions membres des classes dérivées), tout comme la méthode qui consistait en C à déclarer une variable constante. Cependant, elle permettra de détecter des anomalies à la compilation, car si une fonctionprintcherche à modifier l’objet sur lequel elle travaille, il y a manifestement une erreur de conception.

Chapitre 8. C++ : la couche objet Bien entendu, cela fonctionne également avec les fonctions membres virtuelles non pures, et même avec les fonctions non virtuelles.

8.16. Pointeurs sur les membres d’une classe

Nous avons déjà vu les pointeurs sur les objets. Il nous reste à voir les pointeurs sur les membres des classes.

Les classes regroupent les caractéristiques des données et des fonctions des objets. Les membres des classes ne peuvent donc pas être manipulés sans passer par la classe à laquelle ils appartiennent. Par conséquent, il faut, lorsqu’on veut faire un pointeur sur un membre, indiquer le nom de sa classe. Pour cela, la syntaxe suivante est utilisée :

définition classe::* pointeur

Par exemple, si une classe test contient des entiers, le type de pointeurs à utiliser pour stocker leur adresse est :

int test::*

Si on veut déclarer un pointeurpde ce type, on écrira donc :

int test::*p1; // Construit le pointeur sur entier // de la classe test.

Une fois le pointeur déclaré, on pourra l’initialiser en prenant l’adresse du membre de la classe du type correspondant. Pour cela, il faudra encore spécifier le nom de la classe avec l’opérateur de résolution de portée :

p1 = &test::i; // Récupère l’adresse de i.

La même syntaxe est utilisable pour les fonctions. L’emploi d’untypedefest dans ce cas fortement recommandé. Par exemple, si la classe test dispose d’une fonction membre appeléelit, qui n’attend aucun paramètre et qui renvoie un entier, on pourra récupérer son adresse ainsi :

typedef int (test::* pf)(void); // Définit le type de pointeur.

pf p2=&test::lit; // Construit le pointeur et // lit l’adresse de la fonction.

Cependant, ces pointeurs ne sont pas utilisables directement. En effet, les données d’une classe sont instanciées pour chaque objet, et les fonctions membres reçoivent systématiquement le pointeurthis sur l’objet de manière implicite. On ne peut donc pas faire un déréférencement direct de ces pointeurs.

Il faut spécifier l’objet pour lequel le pointeur va être utilisé. Cela se fait avec la syntaxe suivante : objet.*pointeur

Chapitre 8. C++ : la couche objet

Pour les pointeurs d’objet, on pourra utiliser l’opérateur->*à la place de l’opérateur .*(appelé pointeur sur opérateur de sélection de membre).

Ainsi, siaest un objet de classe test, on pourra accéder à la donnéeide cet objet à travers le pointeur p1avec la syntaxe suivante :

a.*p1 = 3; // Initialise la donnée membre i de a avec la valeur 3.

Pour les fonctions membres, on mettra des parenthèses à cause des priorités des opérateurs : int i = (a.*p2)(); // Appelle la fonction lit() pour l’objet a.

Pour les données et les fonctions membres statiques, cependant, la syntaxe est différente. En effet, les données n’appartiennent plus aux objets de la classe, mais à la classe elle-même, et il n’est plus nécessaire de connaître l’objet auquel le pointeur s’applique pour les utiliser. De même, les fonctions membres statiques ne reçoivent pas le pointeur sur l’objet, et on peut donc les appeler sans référencer ce dernier.

La syntaxe s’en trouve donc modifiée. Les pointeurs sur les membres statiques des classes sont com-patibles avec les pointeurs sur les objets et les fonctions non-membres. Par conséquent, si une classe contient une donnée statique entière, on pourra récupérer son adresse directement et la mettre dans un pointeur d’entier :

int *p3 = &test::entier_statique; // Récupère l’adresse // de la donnée membre // statique.

La même syntaxe s’appliquera pour les fonctions : typedef int (*pg)(void);

pg p4 = &test::fonction_statique; // Récupère l’adresse // d’une fonction membre // statique.

Enfin, l’utilisation des ces pointeurs est identique à celle des pointeurs classiques, puisqu’il n’est pas nécessaire de fournir le pointeurthis. Il est donc impossible de spécifier le pointeur sur l’objet sur lequel la fonction doit travailler aux fonctions membres statiques. Cela est naturel, puisque les fonctions membres statiques ne peuvent pas accéder aux données non statiques d’une classe.