Initialisations multiples

Il est parfaitement permis à une classe d’avoir un ou plusieurs membres instances d’autres classes. En utilisant toujours les classes d’exemples du début, voici une classe contenant des membres instances :

class multiple {     exemple ex;     autre au;     public :     multiple() {} // constructeur par défaut     // ...     };

Tout le problème consiste à initialiser ces membres dans le constructeur de la classe. La solution immédiate :

multiple::multiple(){     ex.exemple::exemple(); // incorrect     au.autre::autre(0);    // idem}

est incorrecte, car on ne peut pas appeler explicitement un constructeur.

Le langage fournit un mécanisme spécial pour résoudre ce problème. Derrière la liste des arguments du constructeur, on place le symbole deux-points :, suivi des initialisations de chaque membre, séparées par une virgule :

multiple::multiple() : ex(), au(0); // constructeur par défaut

Les constructeurs sont appelés dans l’ordre de leur écriture (donc ici d’abord exemple::exemple(), puis autre::autre(0)).

Cette séquence d’initialisation doit être écrite dans chaque constructeur, s’il y a lieu :

class multiple {     exemple ex;     autre au;     public :     multiple() : ex(), au(0) {}     multiple(double d) : ex(), au(d) {}     multiple(double d, int i, char c) : ex(i, c), au(d) {}     multiple(autre& a, exemple& e) : ex(e), au(a) {}     multiple(multiple& m) : ex(m.ex), au(m.au) {}     // ...     };

Le dernier constructeur est un constructeur de copie ; comme celui qui le précède, il appelle les constructeurs de copie des classes exemple et autre (qui rappelons-le sont toujours définis).

Tous les types ont un constructeur de copie, pas seulement les classes. De ce fait, même une classe très ordinaire peut initialiser ses membres de cette façon :

class ordinaire {     double dd;     int ii;     public :     ordinaire(int i, double d) : ii(i), dd(d) {}     // ...     };

Ce constructeur est équivalent à :

ordinaire(int i, double d) { ii = i; dd  = d;}

L’intérêt de la première notation est assez faible dans ce cas, la seconde est bien plus claire.

Lorsque le destructeur d’une classe est appelé, automatiquement ou non, les destructeurs des membres sont exécutés à la fin de celui-ci de manière automatique.

Lorsqu’une classe contient des références à d’autres types (classes ou non), le mécanisme d’initialisation est strictement identique. Cependant, dans ce cas, l’initialisation est obligatoire. Ainsi l’écriture :

class fautive {     int& i;     public:     fautive() {};    // NON, pas d’init. de la référence     };

provoque une erreur Error : Reference member 'i' is not initialized in function fautive::fautive(), le membre référence 'i' n’est pas initialisé.

Il faut dans ce cas écrire obligatoirement une initialisation utilisant le constructeur de copie, et aucun autre. Par exemple l’écriture suivante pour le constructeur de fautive :

fautive() : i(22) {}

est refusée par le compilateur (Error : Reference member 'i' need a temporary for initialization, le membre référence 'i' nécessite une variable temporaire pour l’initialisation).

C’est tout à fait logique : le compilateur ne peut pas savoir si un membre référence a été initialisé sur une variable temporaire au moment de la destruction. En conséquence, il interdit d’initialiser une référence sur une variable temporaire, et en contrepartie il n’y a jamais d’appel de destructeur sur les membres références au moment de la destruction de l’instance. Si ce mécanisme ne vous convient pas, il faut utiliser un membre normal, non une référence.

Les seuls constructeurs acceptables pour que la classe fautive ne le soit plus seraient :

fautive::fautive() : i(j) { }fautive::fautive(int k) : i(k) { };

j est une variable globale, ou un autre membre de la classe fautive.

Une classe peut aussi contenir des pointeurs sur des instances d’autres classes. Dans ce cas, il n’y a aucune difficulté particulière :

class multiple {     exemple *pex;     autre *pau;     public :     multiple()         { pex = new exemple; pau = new autre(0); }     multiple(autre& a, exemple& e)         { pex = new exemple(e); pau = new autre(a);}     multiple(multiple& m) {         pex = new exemple(*m.pex);         pau = new autre (*m.pau); }     ~multiple() { delete pau; delete pex; }     // ...     };

Le destructeur doit alors explicitement libérer la mémoire occupée par les instances pointées.

Précédent Précédent Sommaire Sommaire Suivant Suivant