Vous êtes sur la page 1sur 12

Centre Informatique pour les Lettres

et les Sciences Humaines


Apprendre C++ avec Qt : Leon 10
Constructeurs et destructeurs






1 - La surcharge des noms de fonctions.................................................................................... 2
2 - Constructeurs..................................................................................................................... 4
Des fonctions invisibles ? ................................................................................................4
Dclaration, dfinition et appel explicites d'un constructeur............................................4
Constructeurs par dfaut ................................................................................................5
Constructeurs par copie ..................................................................................................5
Constructeurs de transtypage .........................................................................................7
Constructeurs arguments multiples .............................................................................8
De quels constructeurs disposons-nous ?........................................................................9
Echec d'un constructeur .................................................................................................9
3 - Listes d'initialisations.......................................................................................................... 9
4 - Destructeurs..................................................................................................................... 11
5 - Bon, c'est gentil tout a, mais a fait dj 10 pages. Qu'est-ce que je dois vraiment en
retenir ?............................................................................................................................ 12
Document du 16/05/06 - Retrouvez la version la plus rcente sur http://www.up.univ-mrs.fr/wcpp
C++ - Leon 10 Constructeurs et destructeurs 2/12
Notre utilisation du mcanisme des classes reste encore assez rudimentaire, et il nous manque
en particulier une mthode permettant d'initialiser les variables membre lorsqu'un objet est cr.
Le propos de cette Leon est donc d'examiner plus en dtail le processus d'instanciation.

Deux autres thmes seront abords : la destruction des objets et la surcharge des noms de
fonctions. Le thme de la destruction est logiquement li celui de l'instanciation, puisqu'il
s'agit du processus inverse. Comme il s'agit en outre d'un phnomne qui ne ncessite qu'un
expos trs bref, il est naturel de traiter ces deux sujets dans la mme Leon. La surcharge n'a,
en revanche, pas de rapport logique avec l'instanciation ou la destruction, et aborder ce thme
ici peut sembler tout fait arbitraire, voire contestable. Il se trouve simplement que surcharger
les noms de fonctions n'est ni une opration primordiale (ce qui explique que nous n'ayons pas
encore abord le sujet), ni une technique complexe (ce qui explique qu'elle ne fasse pas l'objet
d'une Leon spcifique), ni un procd que l'on peut ignorer lorsqu'on s'intresse au processus
d'instanciation (ce qui explique que nous allons commencer par explorer cette question).
1 - La surcharge des noms de fonctions
Le langage C++ accepte, sous certaines conditions, l'existence de fonctions portant le mme
nom. On dit alors que le nom en question est surcharg (puisqu'il sert dsigner plusieurs
fonctions diffrentes). L'usage courant a cependant impos une expression plus simple :

On dit qu'une fonction est surcharge lorsqu'il existe (au moins) une autre fonction portant
exactement le mme nom.

Attention, en dpit du raccourci verbal gnralement employ, ce n'est pas la fonction qui se
trouve surcharge, mais bien son nom. Cette "surcharge" n'implique rien de spcial pour la
fonction, dont la dclaration et la dfinition restent parfaitement inchanges.

L'existence de fonctions portant le mme nom pose videmment un problme : lorsque ce nom
est utilis dans une expression, quel est le code qui doit tre excut pour valuer celle-ci ? Le
compilateur doit disposer d'un critre de dcision clair, et le langage impose donc des
conditions pour que l'homonymie entre fonctions soit acceptable :

Deux fonctions ne peuvent porter le mme nom que si elles diffrent par leur caractre
constant ou par le type d'au moins un de leurs paramtres.

Il est donc possible de dfinir deux fonctions diffrentes dont les dclarations seraient
voi d f ( i nt n, doubl e x) ; 1
voi d f ( char n, doubl e x) ; 2
puisque le premier paramtre de la premire est de type i nt alors que le premier paramtre de
la seconde est de type char . L'ordre des paramtres est bien entendu significatif, et
voi d f ( i nt n, doubl e x) ; / / dcl ar at i on d' une f onct i on 3
voi d f ( doubl e x, i nt ) ; / / dcl ar at i on d' une f onct i on homonyme de l a pr cdent e 4
sont galement des dclarations correspondant deux fonctions diffrentes.

Les noms attribus aux paramtres sont en revanche sans importance, et les lignes
voi d f ( i nt n, doubl e x) ; / / dcl ar at i on d' une f onct i on 1
voi d f ( i nt machi n, doubl e t r uc) ; / / r edcl ar at i on de l a f onct i on pr cdent e 2
constituent en fait deux dclarations de la mme fonction
1
et non les dclarations de deux
fonctions homonymes. Comme une fonction ne peut tre dfinie qu'une seule fois, toute
tentative pour dfinir la pseudo seconde fonction se soldera par un message indiquant que
cette dfinition est inacceptable, car l'unique fonction f ( ) existe dj.

Dans le cas de fonctions membre, le fait que l'une d'entre elles soit prive du droit de modifier
l'instance au titre de laquelle elle est invoque suffit les distinguer, et l'on peut donc avoir :

cl ass CExempl e 1
{ 2
publ i c: 3
voi d f ( i nt x, doubl e y) ; / / une pr emi r e f onct i on membr e 4
voi d f ( i nt x, doubl e y) const ; / / une seconde f onct i on membr e 5
}; 6

1
Rappels : Un objet peut tre dclar plusieurs fois, condition que toutes ces dclarations soient identiques. Les
noms des paramtres ne figurent dans la dclaration qu' titre documentaire, et sont ignors par le compilateur.
J-L Pris - 16/05/06
C++ - Leon 10 Constructeurs et destructeurs 3/12
La constance et la liste (ordonne) des types de ses arguments constituent ce qu'on appelle la
signature d'une fonction.

La condition pour que deux fonctions puissent porter le mme nom est donc que leurs
signatures diffrent. C'est grce ces signatures que le compilateur dtermine quel code doit
tre excut lorsque l'valuation d'une expression implique l'appel d'une fonction surcharge.

Si nous disposons de deux fonctions dclares ainsi
voi d g( bool x) ; / / dcl ar at i on d' une f onct i on 1
voi d g( char * s) ; / / dcl ar at i on d' une f onct i on homonyme de l a pr cdent e 2
il est en effet clair que des lignes telles que
bool t est = t r ue; 1
g( t est ) ; 2
g( f al se) ; 3
se traduiront par l'excution de la premire fonction g( ) (celle qui dispose d'un paramtre de
type bool pour recevoir la valeur boolenne transmise), alors que les lignes
char t ext e[ ] = " Br avo" ; 4
char c = ' a' ; 5
g( t ext e) ; 6
g( &c) ; 7
dclencheront l'excution de la seconde (celle qui dispose d'un paramtre de type "pointeur sur
char " pour recevoir l'adresse transmise).

Dans le cas de fonctions membre dont les signatures ne diffrent que parce que l'une des deux
est constante, la rgle applique est simple : la fonction "non constante" est excute, sauf
lorsque l'appel est effectu au titre d'une instance constante (seule la fonction qui ne peut pas
modifier les variables membre est alors lgitime).

L'utilisation des signatures pour choisir entre des fonctions homonymes interagit parfois avec le
mcanisme de conversion automatique. Ainsi, si nous disposons d'une fonction dclare
voi d h( i nt a) ; / / pr emi r e f onct i on h( )
l'appel suivant est tout fait possible :
h( ' x' ) ;
En effet, bien que 'x' soit de type char , le compilateur est capable de convertir automatiquement
sa valeur en un i nt convenant l'initialisation du paramtre de la fonction h( ) . Imaginons
maintenant que l'volution du programme conduise introduire une fonction dclare ainsi :
voi d h( char a) ; / / seconde f onct i on h( )
Les signatures diffrent, l'homonymie est donc accepte. Les lignes de code qui utilisaient une
expression de type char pour spcifier la valeur transmise la premire fonction h( )
dclenchent donc dsormais l'excution de la seconde. Ceci est gnralement souhaitable, mais
il faut viter d'utiliser la surcharge dans les cas o ce phnomne serait inopportun.

Par ailleurs, l'utilisation de valeurs par dfaut conduit certaines fonctions avoir plusieurs
signatures. Valeurs par dfaut et surcharge peuvent donc s'avrer incompatibles. Imaginons, par
exemple, que nous disposions de deux fonctions dclares ainsi :
voi d k( i nt a, char b = ' x' ) ;
voi d k( i nt a) ;
Ces deux fonctions prsentent effectivement des signatures diffrentes : (i nt , char ) dans un cas,
et (i nt ) dans l'autre. Du fait de la valeur par dfaut dont dispose son second paramtre, la
premire fonction dispose toutefois d'une signature "alternative", rduite, elle aussi, un simple
i nt . Il n'y a donc plus de moyen de dterminer si
k( 12) ;
doit se traduire par l'appel de la premire fonction k( ) , avec utilisation de la valeur par dfaut
du second paramtre, ou par l'appel de la seconde fonction k( ) .

La surcharge est indispensable dans certains cas, et elle peut tre utile lorsqu'elle permet aux
utilisateurs d'une collection de fonctions d'utiliser un mme nom pour appliquer un "mme"
traitement des donnes de types diffrents. Un exemple trs simple et trs familier est celui
de l'addition : il vous semble naturel d'utiliser le mme symbole pour additionner deux entiers
et pour additionner deux dcimaux, mme si vous savez (Leon 1) que les oprations
ncessaires pour traiter ces deux cas diffrent profondment. La surcharge vous permet de
doter les fonctions que vous crivez de la mme souplesse : selon le type des arguments, le
dtail des oprations peut varier considrablement, mais l'utilisateur ne connat qu'un seul
nom et n'a pas prendre lui-mme en compte le type des arguments.
J-L Pris - 16/05/06
C++ - Leon 10 Constructeurs et destructeurs 4/12
2 - Constructeurs
Les classes que nous crons sont gnralement destines nous permettre de stocker des
donnes. La dfinition d'une classe n'est alors qu'une premire tape : il nous faut ensuite
l'instancier. Ce sont ces instances qui nous permettent finalement de stocker et de manipuler
efficacement nos donnes. L'intrt du mcanisme des classes repose donc en grande partie
sur le processus d'instanciation, processus que nous avons utilis jusqu' prsent sans nous
inquiter outre mesure des oprations qu'il implique. La plupart de ces oprations restent sous
la responsabilit exclusive du compilateur, et nous n'aurons jamais nous y intresser. Le
langage C++ nous offre cependant un moyen d'influer, lorsque nous le jugeons ncessaire, sur
le processus d'instanciation des classes que nous avons cres :

La cration d'une instance d'une classe s'accompagne toujours de l'excution d'une fonction
particulire, que l'on appelle un constructeur de la classe.
Des fonctions invisibles ?
Les constructeurs prsentent certaines caractristiques qui les distinguent des fonctions
"ordinaires". Une des caractristiques les plus droutantes pour le programmeur novice est
sans doute que les constructeurs sont souvent appeles implicitement, c'est dire sans que la
ligne de code qui dclenche leur excution prsente les caractristiques qui permettent
habituellement de reconnatre un appel de fonction : le nom de la fonction, suivi d'un couple de
parenthses encadrant la (ou les) valeur(s) transmise(s) comme argument(s). L'tranget de la
situation est amplifie par le fait que le compilateur cre automatiquement certains
constructeurs, qui n'apparaissent alors pas dans le code dfinissant la classe.

Imaginons, par exemple, une classe dfinie de la faon suivante :

cl ass CTr esSi mpl e 1
{ 2
publ i c : 3
i nt l eMembr e; 4
}; 5

Cette classe ne comporte aucune fonction membre visible, et il est donc assez difficile
d'imaginer que son instanciation l'aide d'une ligne aussi anodine que

CTr esSi mpl e uneI nst ance;

se traduit en fait par l'appel (invisible) d'une fonction membre. C'est pourtant trs exactement
ce qui se produit : le compilateur ajoute automatiquement un constructeur la classe, et ce
constructeur est (implicitement) appel lors de la cration d'une instance.

Le rle du compilateur est de produire un excutable partir du texte source, et non de
complter le texte source. Un constructeur ajout une classe par le compilateur n'existe donc
aucun moment sous forme d'un texte en C++ que nous pourrions consulter.

Ce mcanisme quelque peu opaque s'claircit rapidement ds lors que les constructeurs sont
explicitement dclars, dfinis et appels.
Dclaration, dfinition et appel explicites d'un constructeur
Un constructeur est une fonction membre qui porte le mme nom que la classe et est
dpourvue de type.

Attention : c'est bien de type que les constructeurs sont dpourvus, et pas simplement de valeur
de retour. En d'autres termes, les constructeurs ne sont mme pas de type voi d.

Pour reprendre l'exemple prcdent, la classe CTr esSi mpl e peut donc tre dfinie ainsi :

cl ass CTr esSi mpl e 1
{ 2
publ i c : 3
i nt l eMembr e; 4
CTr esSi mpl e( ) ; 5
}; 6

J-L Pris - 16/05/06
C++ - Leon 10 Constructeurs et destructeurs 5/12
La dfinition du constructeur en question ne prsente pas de particularits par rapport celle
de n'importe quelle autre fonction membre : on reprend la dclaration (en prenant toutefois
soin d'utiliser le nom complet de la fonction), mais on remplace le point virgule final par un
couple d'accolades contenant le code de la fonction.

CTr esSi mpl e: : CTr esSi mpl e( ) 1
{ 2
/ / l e bl oc de code df i ni ssant ce const r uct eur est vi de
} 3

Il peut sembler choquant que le bloc de code dfinissant le constructeur reste vide.
Contrairement aux apparences, cela ne signifie pas que l'excution du constructeur soit sans
intrt. Une des caractristiques trs particulires des constructeurs est que leur excution
provoque la cration d'une instance avant que le bloc de code qui les dfinit ne commence tre
excut. Le vritable processus de cration d'une instance est du ressort exclusif du
compilateur, il ne peut pas rellement tre dcrit en C++ "normal".

La dclaration et la dfinition explicites de ce constructeur ne changent rien aux possibilits
d'utilisation de la classe CTr esSi mpl e, qui peut toujours tre instancie normalement :
CTr esSi mpl e uneI nst ance; / / appel i mpl i ci t e du const r uct eur

Notons galement qu'il est aussi possible d'appeler explicitement le constructeur lors de
l'instanciation de la classe :
CTr esSi mpl e uneI nst ance = CTr esSi mpl e( ) ; / / appel expl i ci t e du const r uct eur

Un tel appel explicite est toujours possible, que le constructeur ait ou non t dfini
explicitement. Il reste toutefois d'un usage assez rare dans le cas de la dfinition d'une
variable, car il en alourdit l'criture sans prsenter d'avantages vraiment significatifs.
Constructeurs par dfaut
Les constructeurs que nous avons rencontrs jusqu' prsent sont dpourvus d'arguments, et
permettent donc d'obtenir l'instanciation de la classe sans avoir fournir de donnes.

Un constructeur dpourvu d'arguments est appel un constructeur par dfaut.

Cette absence d'argument restreint la souplesse d'usage du constructeur, et nous serons
rapidement amens envisager des constructeurs plus labors. Le constructeur par dfaut
assure cependant un "service minimum" qui permet d'instancier la classe. C'est pourquoi

Lorsque le code dfinissant une classe ne comporte aucun constructeur, le compilateur rend la
classe instanciable en lui ajoutant automatiquement un constructeur par dfaut.

Le constructeur par dfaut fourni par le compilateur correspond un constructeur dont le bloc
d'instructions serait vide. Lorsque le constructeur est dfini explicitement, il est bien entendu
possible d'y placer des instructions, et celles-ci seront alors excutes comme si le
constructeur avait t invoqu au titre de l'instance qui vient d'tre cre.

Une des missions qui choient naturellement un constructeur est l'initialisation des variables
membre, et le constructeur par dfaut de la classe que nous avons imagine dans les exemples
prcdents pourrait tre dfini ainsi :

CTr esSi mpl e: : CTr esSi mpl e( ) 1
{ 2
l eMembr e = 0; 3
} 4

L'intrt des constructeurs devient alors vident : tant donn que l'instanciation de la classe
CTr esSi mpl e s'accompagne automatiquement de l'appel du constructeur, il devient
rigoureusement impossible qu'une variable de ce type soit cre sans que sa variable membre
ne reoive une valeur initiale. Une source d'erreurs de programmation se trouve ainsi limine.
Constructeurs par copie
La dfinition d'une variable n'est pas le seul cas o une classe se trouve instancie. Lors de
l'appel d'une fonction utilisant comme paramtre une instance de la classe, il est galement
ncessaire de crer une instance.

J-L Pris - 16/05/06
C++ - Leon 10 Constructeurs et destructeurs 6/12
En effet, comme nous le savons depuis la Leon 5, la fonction ne va pas travailler sur l'objet
utilis pour spcifier la valeur qui lui est transmise, mais va utiliser cette valeur pour initialiser
un objet qui lui est propre (le paramtre lui-mme). Il faut donc initialiser l'objet nouvellement
cr (le paramtre) l'aide des valeurs contenues dans l'objet utilis par la fonction appelante
pour spcifier la valeur de ce paramtre. Chaque fois que nous crivons quelque chose comme :

CTr esSi mpl e uneI nst ance; / / l e const r uct eur par df aut i ni t i al i se l eMembr e 0 1
uneI nst ance. l eMembr e = 17; / / on af f ect e une nouvel l e val eur au membr e 2
maFonct i on( uneI nst ance) ; 3

la fonction appele se trouve en position de devoir initialiser son paramtre avec la valeur
transmise. Une situation analogue se prsente si nous crivons :

CTr esSi mpl e uneI nst ance; 1
uneI nst ance. l eMembr e = 18; 2
CTr esSi mpl e uneAut r e = uneI nst ance; / / i ni t i al i sat i on d' une i nst ance 3

Dans un cas comme dans l'autre, le constructeur ne doit pas initialiser l eMembr e de l'instance
en cours de cration
2
avec la valeur 0, mais avec la valeur contenue dans l eMembr e de
l'instance qui sert de "modle", c'est dire 17 dans le premier cas, et 18 dans le second. Quelle
que soit la faon dont il est dfini, le constructeur par dfaut est parfaitement incapable de
raliser cette opration car, tant lui-mme dpourvu de paramtre, il est condamn utiliser
toujours la mme valeur pour initialiser l eMembr e. Lorsque ce genre d'initialisation est
ncessaire, il nous faut donc disposer d'un autre type de constructeur, capable d'utiliser une
instance existant dj comme "modle" de l'instance crer. Ce "modle" sera communiqu au
constructeur par le biais d'un paramtre de type "rfrence une instance constante".

Il n'est pas envisageable que le paramtre soit de type "instance", puisque nous venons de voir
que l'utilisation ce type de paramtre est prcisment l'un des cas qui exige la mise en uvre du
constructeur qu'il s'agit ici de dfinir ! L'usage d'une rfrence vite ce cercle vicieux et, comme
le propos du constructeur par copie n'est certainement pas de modifier l'instance qui lui sert de
modle, il est prfrable que son paramtre dsigne l'objet en question comme tant constant.

On appelle constructeur par copie un constructeur qui reoit comme unique paramtre une
rfrence une instance de la classe.

Un constructeur par copie a normalement vocation s'inspirer de l'objet qui lui est
communiqu pour initialiser les variables membre de l'instance en cours de cration. Dans de
nombreux cas, cette "inspiration" consiste purement et simplement donner aux variables
membre de la nouvelle instance des valeurs identiques celles rencontres dans l'instance qui
sert de modle, ensemble d'oprations que l'on qualifie souvent de "copie membre membre".

Nous rencontrerons bientt des cas o le constructeur par copie ne peut se contenter d'une
approche aussi rudimentaire. Un des cas classiques est celui des variables membre de type
pointeur : un constructeur par copie "membre membre" produit une instance qui pointe au
mme endroit que le modle utilis, ce qui n'est pas toujours l'effet souhait, et peut mme
s'avrer fort dangereux.

La prsence d'un constructeur par copie est indispensable l'utilisation d'une instance de la
classe pour en initialiser une autre. Etant donn qu'une telle initialisation est implicitement
effectue ds qu'une fonction utilise un paramtre de type "instance de la classe", un
constructeur par copie est une fonction membre trs souvent requise, ce qui justifie que

Le compilateur gnre automatiquement un constructeur par copie pour toute classe qui en est
dpourvue alors que l'usage qui en est fait le ncessite.

Le constructeur par copie gnr par le compilateur se contente de l'approche rudimentaire
voque ci-dessus : chaque variable membre du nouvel objet adopte pour valeur celle du
membre correspondant de l'instance qui sert de modle. Dans le cas de notre classe, s'il tait
visible, le constructeur gnr automatiquement ressemblerait donc

CTr esSi mpl e: : CTr esSi mpl e( const & CTr esSi mpl e l eModel e) 1
{ 2
l eMembr e = l eModel e. l eMembr e; 3
} 4

2
Le paramtre dans le premier cas, la variable uneAut r e dans le second.
J-L Pris - 16/05/06
C++ - Leon 10 Constructeurs et destructeurs 7/12
Les constructeurs par dfaut et par copie d'une mme classe portent videmment le mme nom
(celui de la classe) et ne se distinguent que par la liste des types de leurs d'arguments. Comme
des fonctions de ce type sont frquemment gnres automatiquement par le compilateur, il est
fort probable que vous ayez dj cr et utilis (sans le savoir) des fonctions surcharges !
Constructeurs de transtypage
Les constructeurs par dfaut et par copie ne sont pas les seuls types de constructeurs
envisageables. Selon la nature de la classe, il peut en effet tre assez naturel d'utiliser, pour
spcifier l'tat initial d'une instance, autre chose qu'une autre instance de cette mme classe.
Dans le cas de notre classe CTr esSi mpl e, il serait assez tentant de pouvoir initialiser
directement l'unique variable membre l'aide d'une valeur entire. Ceci devient possible ds
lors que la classe dispose d'un constructeur admettant pour paramtre un i nt :

cl ass CTr esSi mpl e 1
{publ i c : 2
i nt l eMembr e; 3
CTr esSi mpl e( ) ; / / const r uct eur par df aut 4
CTr esSi mpl e( i nt val eur ) ; / / const r uct eur de t r anst ypage par t i r d' i nt 5
}; 6

Le constructeur en question peut tre dfini ainsi :

CTr esSi mpl e: : CTr esSi mpl e( i nt val eur ) 1
{ 2
l eMembr e = val eur ; 3
} 4

Il est alors possible d'appeler explicitement ce constructeur

CTr esSi mpl e uneI nst ance = CTr esSi mpl e( 3) ;

On pourrait croire que la ligne prcdente provoque d'abord l'appel du constructeur de
transtypage pour crer une instance temporaire et anonyme qui serait ensuite utilise par un
constructeur par copie pour crer uneI nst ance. Il n'en est rien, et cette ligne ne gnre qu'un
unique appel, au constructeur de transtypage.

Toutefois, on prfre gnralement la simplicit syntaxique d'un appel implicite

CTr esSi mpl e uneI nst ance( 3) ; / / appel i mpl i ci t e du const r uct eur de t r anst ypage

qui peut aussi tre obtenu en utilisant la notation "traditionnelle" de l'initialisation

CTr esSi mpl e uneI nst ance = 3; / / appel i mpl i ci t e du const r uct eur de t r anst ypage

Il faut aussi remarquer que la disponibilit d'un constructeur de transtypage permet au
compilateur d'effectuer automatiquement certaines conversions, ce qui autorise par exemple
l'affectation suivante :

CTr esSi mpl e uneI nst ance; / / appel i mpl i ci t e du const r uct eur par df aut 1
uneI nst ance = 15; / / af f ect at i on avec t r anst ypage aut omat i que ! 2

Cette affectation peut sembler un peu "magique", mais le droulement des oprations est
finalement assez simple :
- L'expression place droite de l'oprateur d'affectation est value. Il s'agit d'une constante
littrale, dont la valeur est 15 et le type i nt .
- L'expression place gauche de l'oprateur d'affectation est value. Il s'agit du nom d'une
variable dont le type est CTr esSi mpl e, et l'expression dsigne donc une zone de mmoire
susceptible de stocker le rsultat obtenu droite, sous rserve que les types soient
"compatibles" (deux types sont compatibles lorsqu'ils sont identiques ou lorsque l'on sait
comment produire une valeur du type de gauche partir d'une valeur du type de droite).
- Les types i nt et CTr esSi mpl e ne sont pas identiques, mais le compilateur sait comment
produire une valeur de type CTr esSi mpl e partir d'une valeur de type i nt : il suffit de
transmettre cette dernire au constructeur de CTr esSi mpl e qui attend ce type de paramtre !
Une fois ceci fait, il ne reste plus qu' appliquer l'oprateur d'affectation entre deux objets de
types CTr esSi mpl e : la variable uneI nst ance et l'instance (temporaire et anonyme) produite
par le constructeur partir de l'entier 15.

Cette apparente possibilit d'affectation une instance de la classe d'une valeur d'un type
diffrent explique la terminologie employe :

J-L Pris - 16/05/06
C++ - Leon 10 Constructeurs et destructeurs 8/12
On appelle constructeur de transtypage un constructeur qui permet la cration d'une instance
partir d'une valeur d'un autre type que la classe elle-mme.

Il arrive parfois qu'un constructeur de transtypage permette au compilateur d'effectuer
automatiquement des conversions qui ne sont pas souhaitables. On peut alors interdire l'usage
automatique du constructeur concern en faisant prcder sa dclaration du mot expl i ci t . Si
notre classe est dfinie ainsi

cl ass CTr esSi mpl e
{
publ i c :
i nt l eMembr e;
CTr esSi mpl e( ) ; / / const r uct eur par df aut
expl i ci t CTr esSi mpl e( i nt val eur ) ; / / const r uct eur de t r anst ypage
};

la conversion d'un i nt en CTr esSi mpl e n'est plus effectue que si elle explicitement demande

CTr esSi mpl e uneI nst ance; / / appel i mpl i ci t e du const r uct eur par df aut
uneI nst ance = 15; / / ERREUR : t r anst ypage aut omat i que i nt er di t !
uneI nst ance = CTr esSi mpl e( 15) ; / / OK : appel expl i ci t e du const r uct eur

ou si elle intervient lors d'une initialisation

CTr esSi mpl e i ncr oyabl e( 3) ; / / appel i mpl i ci t e d' un const r uct eur expl i ci t !

La possibilit d'effectuer l'initialisation d'une instance partir d'une valeur d'un autre type
dpend bien entendu de la nature de la classe considre et de l'usage qui en est fait. Il n'est
donc pas possible pour le compilateur d'inventer des constructeurs de transtypage, et ceux-ci
n'existent que dans la mesure o l'auteur de la classe les a dfinis. Il est, par ailleurs, tout
fait possible de dfinir des constructeurs exigeant plusieurs arguments (et qui ne sont donc ni
"par dfaut", ni "par copie", ni "de transtypage").
Constructeurs arguments multiples
Imaginons que nous disposions d'une classe comportant plusieurs variables membre. Si nous
souhaitons pouvoir initialiser plusieurs de ces variables avec des valeurs quelconques, il nous
faut bien entendu un constructeur disposant d'autant de paramtres.

cl ass CDuo 1
{ 2
publ i c: 3
doubl e m_doubl e; 4
i nt m_i nt ; 5
CDuo ( doubl e unDoubl e, i nt unI nt ) ; / / const r uct eur deux ar gument s 6
}; 7

La dfinition d'un constructeur de ce genre est sans surprise :

CDuo: : CDuo ( doubl e unDoubl e, i nt unI nt ) 1
{ 2
m_doubl e = unDoubl e; 3
m_ent i er = unI nt ; 4
} 5

Dfinie ainsi, notre classe se prte l'instanciation grce l'une des syntaxes suivantes :

CDuo uneI nst ance = CDuo( 0. 0, 0) ; / / appel expl i ci t e du const r uct eur 1
CDuo uneAut r e( 3. 14, 18) ; / / appel i mpl i ci t e du const r uct eur 2

Il faut toutefois noter que, tant donn qu'un constructeur comportant plusieurs arguments
n'est pas un constructeur de transtypage, il n'est pas possible d'en utiliser un appel implicite
pour effectuer l'affectation d'une liste de valeurs une instance de notre classe :

uneI nst ance = {2. 7, 36}; / / I MPOSSI BLE !

Une affectation n'est ici possible qu'entre deux objets de mme type, ce qui exige qu'une
instance (temporaire et anonyme) soit cre par appel explicite du constructeur :

uneI nst ance = CDuo( 2. 7, 36) ; / / OK

J-L Pris - 16/05/06
C++ - Leon 10 Constructeurs et destructeurs 9/12
L'appel explicite d'un constructeur muni de paramtres permet de crer " la vole" une valeur
ayant pour type la classe mentionne.
De quels constructeurs disposons-nous ?
Le fait que certains constructeurs puissent parfois tre gnrs automatiquement par le
compilateur simplifie considrablement la mise en uvre des classes les plus simples. Ce
phnomne risque, en revanche, de gnrer une certaine confusion dans l'esprit du
programmeur dbutant, qui connat l'existence de ces constructeurs, mais matrise encore mal
les rgles qui les gouvernent. Le tableau ci-dessous rsume les caractristiques essentielles des
quatre types de constructeurs :

Constructeur Signature Gnration automatique
par dfaut Pas de paramtre.
Lorsque la dfinition explicite de la classe
ne comporte aucun constructeur.
par copie
Paramtre unique, de type
"rfrence une instance de la
classe".
Lorsque la dfinition explicite de la classe
ne comporte pas de constructeur par copie
et que l'usage fait de la classe le ncessite.
de transtypage
Un seul paramtre, d'un type
autre que la classe elle-mme.
Jamais.
autres Plusieurs paramtres. Jamais.

Un des points remarquables de cet ensemble de rgles est que

La dfinition explicite d'un constructeur, quel qu'il soit, inhibe la gnration automatique d'un
constructeur par dfaut.

Ainsi, si la dfinition d'une classe ne comporte aucun constructeur dclar explicitement, la
simple adjonction d'un constructeur de transtypage, par exemple, rend invalides toutes les
lignes de code qui craient jusqu' prsent sans problme des instances non initialises. Si
cette "instanciation sans initialisation" doit rester possible, il est alors ncessaire de dfinir
explicitement un constructeur par dfaut. Dans le cas contraire, la classe peut rester
dpourvue de constructeur par dfaut, mais toutes les lignes de code qui utilisaient
implicitement celui-ci doivent tre modifies, de faon ce qu'elles mentionnent dsormais une
valeur d'initialisation. La dfinition explicite d'un constructeur acceptant un ou plusieurs
arguments permet donc de priver une classe de constructeur par dfaut, ce qui revient exiger
que toute instanciation de cette classe comporte une initialisation explicite.

La classe CDuo dfinie ci-dessus est dans ce cas : la prsence du constructeur deux arguments
inhibe la gnration automatique d'un constructeur par dfaut, et une dfinition telle que
CDuo maVar i abl e; / / ERREUR : const r uct eur par df aut non di sponi bl e
provoquerait une erreur de compilation.
Echec d'un constructeur
Les traitements effectus dans un constructeur peuvent tre de toutes natures, et il arrive
parfois qu'ils soient lgitimement susceptibles d'chouer (du fait de l'puisement d'une
ressource qui leur est ncessaire, par exemple). Ce type de situation n'est pas facile grer, car
les constructeurs sont souvent invoques implicitement et ne renvoient de toute faon pas de
valeur qui leur permettrait de signaler qu'ils ont rencontr un problme.

Le langage C++ propose un mcanisme dit de "gestion des exceptions" (cf. Leon 23) qui peut
permettre de traiter ce genre de cas. Si ce mcanisme n'est pas utilis, il faut faire en sorte que
les constructeurs susceptibles d'chouer laissent les instances sur lesquelles ils oprent dans
un tat qui permet de vrifier facilement si leur construction a t complte.
3 - Listes d'initialisations
Nous avons jusqu' prsent voqu l'utilisation des constructeurs pour initialiser les variables
membre des instances en cours de cration. Si cet emploi du mot "initialisation" semble justifi
par le fait que le constructeur est, par dfinition, excut avant que l'instance ait pu recevoir
quelque valeur que ce soit, il n'en reste pas moins que, dans les exemples prcdents, les
constructeurs effectuent des oprations qui restent, du point de vue syntaxique, de simples
affectations. La distinction est, le plus souvent, sans grande importance pratique. Il existe
J-L Pris - 16/05/06
C++ - Leon 10 Constructeurs et destructeurs 10/12
cependant des circonstances o une affectation s'avre impossible, et c'est notamment le cas
lorsque les donnes membre auxquelles le constructeur est cens confrer une valeur initiale
sont des constantes, des rfrences, ou des instances d'une classe ne comportant pas de
constructeur par dfaut.

Il est, bien entendu, impossible de procder l'affectation d'une valeur une constante ou une
rfrence, mais le problme n'est pas l : la cration d'une constante ou d'une rfrence exige
absolument qu'il y ait une vritable initialisation (au sens syntaxique du terme). La difficult
n'est donc pas que le constructeur n'a pas le droit de procder des affectations, mais bien que
l'instance ne peut mme pas tre cre pour tre ensuite confie au constructeur. Le mme
problme se pose lorsque l'une des variables membre est elle-mme une instance d'une classe
qui ne comporte pas de constructeur par dfaut : la cration de ce membre exige une
initialisation. Rendre l'affectation possible (en dfinissant un constructeur de transtypage, par
exemple) n'est donc d'aucun secours, car la variable membre sur laquelle le constructeur aurait
alors le droit de procder une affectation ne peut pas tre cre.

La solution adopte par les concepteurs de C++ est de doter les constructeurs d'un moyen de
spcifier avec quelles valeurs doivent tre initialises les variables membre de l'instance sur
laquelle ils opreront ds qu'elle aura t cre. La syntaxe employe pour obtenir ce rsultat
consiste insrer la spcification des valeurs d'initialisation entre la parenthse qui clt la liste
des paramtres et l'accolade qui ouvre le corps de la fonction. Cette "liste d'initialisation"
dbute par le symbole "deux points" et numre les noms des membres devant tre initialiss,
suivis chacun d'un couple de parenthses encadrant la valeur devant tre utilise. Le
constructeur par dfaut de notre classe lmentaire, que nous avions dfinie ainsi

CTr esSi mpl e: : CTr esSi mpl e( ) 1
{ 2
l eMembr e = 0; 3
} 4

pourrait donc galement tre dfinie comme ceci :

CTr esSi mpl e: : CTr esSi mpl e( ) : l eMembr e( 0) 1
{}/ / l ' i ni t i al i sat i on est f ai t e, l e const r uct eur n' a pl us r i en f ai r e 2

Comme nous l'avons vu dans la Leon 3, on choisit souvent de dfinir les fonctions membre trs
brves dans la dfinition de la classe. La dfinition de la classe devient alors

cl ass CTr esSi mpl e
{
publ i c :
i nt l eMembr e; / / dcl ar at i on de l a var i abl e membr e
CTr esSi mpl e( ) : l eMembr e( 0) { } / / DEFI NI TI ON di r ect e du const r uct eur
};

Bien que les valeurs d'initialisations soient utilises avant que le constructeur ne soit
effectivement appel, il est tout fait possible que la liste d'initialisation utilise les valeurs qui
seront reues par le constructeur lors de son appel. Le constructeur de la classe CDuo, que
nous avions dfini ainsi

CDuo: : CDuo ( doubl e unDeci mal , i nt unEnt i er ) 1
{ 2
m_deci mal = unDeci mal ; 3
m_ent i er = unEnt i er ; 4
} 5
pourrait donc tre rcrit comme cela :
CDuo: : CDuo ( doubl e unDoubl e, i nt unI nt ) : m_deci mal ( unDoubl e) , m_ent i er ( unI nt ) 1
{}/ / l es i ni t i al i sat i ons sont f ai t es, l e const r uct eur n' a pl us r i en f ai r e 2

Dans les deux exemples qui prcdent, le recours une liste d'initialisation relve de la pure
coquetterie : il n'y a aucun inconvnient rel "initialiser" les variables membre l'aide
d'affectations effectues dans le corps du constructeur. Ce n'est en revanche pas le cas de
l'exemple suivant, qui concerne une classe dfinie ainsi :

J-L Pris - 16/05/06
C++ - Leon 10 Constructeurs et destructeurs 11/12
cl ass CMoi nsSi mpl e 1
{ 2
publ i c: 3
const i nt m_const ant e; 4
doubl e & m_r ef ; 5
CDuo m_duo; 6
CMoi nsSi mpl e ( i nt uneConst ant e, doubl e &uneRef , CDuo unDuo) ; 7
}; 8

Si l'on est conscient du problme pos par la nature des membres de la classe CMoi nsSi mpl e,
la dfinition de son constructeur ne pose pas rellement de problme :

CMoi nsSi mpl e: : CMoi nsSi mpl e( i nt uneConst ant e, doubl e &uneRef , CDuo unDuo) 1
: m_const ant e( uneConst ant e) , m_r ef ( uneRef ) , m_duo( unDuo) 2
{}/ / l es i ni t i al i sat i ons sont f ai t es, l e const r uct eur n' a pl us r i en f ai r e 3

La classe CMoi nsSi mpl e peut alors tre instancie, condition de satisfaire le seul
constructeur disponible en procdant une initialisation explicite :

doubl e unDoubl e = 2; 1
CDuo monDuo( 1. 2, 747) ; 2
CMoi nSi mpl e maVar ( 18, unDoubl e, unDuo) ; 3

Les listes d'initialisation prsentent une particularit trange qui est gnralement sans
importance mais qui peut parfois crer des problmes : l'ordre dans lequel les initialisations sont
effectues n'est pas celui spcifi par la liste d'initialisation, mais l'ordre dans lequel les variables
membre concernes sont dclares. Le constructeur par dfaut de la classe suivante, par
exemple, ne produit pas du tout des instances dans l'tat souhait :

cl ass CPi ege
{
publ i c:
i nt deux;
i nt un;
CPi ege: : CPi ege( ) : un ( 1) , deux( un + 1) {}
};

En effet, du fait que le membre deux est dclar avant le membre un, il est le premier tre
initialis, ce qui signifie que le calcul effectu cette occasion fait intervenir la valeur de un
avant que ce membre n'ait t initialis... d'o un rsultat imprvisible.
4 - Destructeurs
De mme que la naissance d'une instance s'accompagne toujours de l'appel d'un constructeur,
la "mort" d'une instance provoque l'appel du destructeur de la classe.

Un destructeur est une fonction membre qui porte le mme nom que la classe, prcd du
signe ~, et est dpourvue de type et dpourvue de paramtre.

L'absence de paramtre exclut la surcharge, puisqu'une seule signature est possible5 (celle qui
est rduite la liste vide). Une classe peut donc comporter de nombreux constructeurs, mais
elle aura toujours un seul et unique destructeur.

L'appel explicite du destructeur d'une classe est possible, mais n'est ncessaire que dans des
cas extrmement particuliers, qui ne nous intressent pas pour l'instant.

Attention : le destructeur est automatiquement appel lorsqu'une instance "meurt", mais ceci ne
signifie pas que l'appel du destructeur "tue" l'instance. La mmoire occupe par une variable
locale, par exemple, ne sera jamais libre par l'appel explicite du destructeur. Cet appel
provoque l'excution des instructions dfinissant le destructeur, mais celles-ci ne peuvent en
aucun cas appliquer une variable un traitement qui la ferait disparatre.

Les oprations effectues par un destructeur dpendent troitement de la nature de la classe
concerne. Il s'agit le plus souvent d'oprations "symtriques" de celles effectues lors de la
construction : fermeture d'un fichier que le constructeur aurait ouvert, arrt d'un processus
que le constructeur aurait lanc, etc. Les constructeurs gnrs automatiquement par le
compilateur se bornant initialiser les variables membre, ils ne ncessitent aucune opration
"symtrique" au moment de la destruction, ce qui explique que :
J-L Pris - 16/05/06
C++ - Leon 10 Constructeurs et destructeurs 12/12

Lorsqu'une classe est dpourvue de destructeur, le compilateur lui en ajoute automatiquement
un, qui n'effectue aucun traitement.

Avec un destructeur dfini explicitement, notre exemple le plus simple devient :

cl ass CTr esSi mpl e 1
{ 2
publ i c : 3
i nt l eMembr e; 4
~CTr esSi mpl e( ) ; / / dcl ar at i on du dest r uct eur : pas de t ype 5
/ / nomde l a cl asse pr cd de ~
}; 6

et la dfinition d'une fonction quivalente au destructeur gnr automatiquement serait :

CTr esSi mpl e: : ~CTr esSi mpl e 1
{}/ / ce dest r uct eur ne f ai t r i en 2
5 - Bon, c'est gentil tout a, mais a fait dj 10 pages. Qu'est-ce
que je dois vraiment en retenir ?
1) La signature d'une fonction est dtermine par sa constance et la liste des types de ses
paramtres.

2) Deux fonctions peuvent porter le mme nom si leurs signatures diffrent.

3) Lorsque deux fonctions sont homonymes, on dit qu'elles sont surcharges.

4) Lorsqu'une classe est instancie, il y a toujours excution d'un de ses constructeurs.

5) Le rle d'un constructeur n'est pas de crer l'objet sur lequel il opre.

6) Un constructeur est une fonction membre dpourvue de type et portant le nom de la classe.

7) Un constructeur sans arguments est appel constructeur par dfaut.

8) Si la dfinition d'une classe ne mentionne aucun constructeur, le compilateur cre
automatiquement un constructeur par dfaut.

9) Un constructeur par copie est un constructeur recevant comme paramtre une rfrence
une instance de la classe.

10) L'initialisation d'une instance au moyen d'une autre ncessite un constructeur par copie.

11) Si, lors d'un appel de fonction, la valeur d'un objet est transmise, le paramtre
correspondant ne peut tre initialis que par le constructeur par copie.

12) Si une classe a besoin d'un constructeur par copie et qu'elle en est dpourvue, le
compilateur en gnre automatiquement un.

13) Les constructeurs gnrs par le compilateur s'avrent parfois insuffisants, et l'on peut tre
conduit d'une part crer des versions plus labores des constructeurs par dfaut et par
copie, et d'autre part crer d'autres types de constructeurs.

14) Les listes d'initialisation permettent de procder de vritables initialisations des membres
de l'instance cre, et non de simples affectations, ce qui est parfois indispensable.

15) Lorsqu'une instance disparat, il y a toujours excution du destructeur de la classe.

16) Un destructeur est une fonction membre dpourvue de type et de paramtre et dont le nom
est compos en faisant prcder celui de la classe du signe ~.

17) Le rle du destructeur n'est pas de dtruire l'instance au titre de laquelle il est excut.
J-L Pris - 16/05/06