Table des Matières

Notes au lecteur

7
LES BASES DU C++

1) Les objets et les classes
Notions de base
L'idée
Mise en œuvre C + + '
L'intérêt par rapport au C
Résumé
Pour bien démarrer en C + +
Compléments
Fonctions inline
Le mot-clé this
Éléments static dans une classe
Déclaration de classe anticipée

13
13
13
14
19
20
20
21
21
23
24
25

2) Les c o n s t r u c t e u r s et les destructeurs
Les notions de base
L'idée
Mise en œuvre C + + constructeurs
Mise en œuvre C + + destructeurs
Compléments
Constructeur copie
Constructeurs de conversion

27
27
27
28
30
32
32
36

3) L'héritage simple
Notions de base
L'idée
Mise en œuvre C + +
Redéfinition d'une fonction-membre
Conversion de Dérivée* vers Base*
Résumé

37
37
37
38
41
42
43
4

Pont entre C et C + +

L'intérêt par rapport au C
Compléments
Héritage et constructeurs
Héritage et destructeurs
Exemple complet
Listes d'initialisations

43
44
44
44
45
47

4) La surcharge
Notions de base
L'idée
Mise en œuvre C + +
L'intérêt par rapport au C
Compléments
Surcharge d'opérateurs
Surcharge de l'opérateur =
Opérateurs de conversion de types

51
51
51
52
53
54
54
57
61

5) Les petits + du C++
Les commentaires
Ruse de sioux
Paramètres par défaut
Les constantes
Déclarations

63
63
64
65
67
73

RÉSUMÉ
Encapsulation
Constructeurs & destructeurs
Héritage
Surcharge

75
77
77
78
79

ENCORE PLUS DE C++
6) La fin du malloc : new et delete
Notions de base
L'idée
Mise en œuvre C + +
L'intérêt par rapport au C

83
83
83
84
86

7) La f i n du printf : cout, cin et cerr
Notions de base
L'idée
Mise en œuvre C + +
L'intérêt par rapport au C
Compléments
Surcharger « et »

89
90
90
90
91
93
93
Table des matières

Formater les sorties
Tableau récapitulatif

5

95
100

8) Le p o l y m o r p h i s m e et la virtualité
Notions de base
L'idée
Mise en œuvre C + +
Résumé
L'intérêt par rapport au C
Compléments
Destructeurs virtuels
Classes abstraites et fonctions virtuelles pures

102
102
102
104
105
105
107
107
108

9) Les références
Notions de base
L'idée
Mise en œuvre C + +
L'intérêt par rapport au C
Compléments
Références et objets provisoires
Fonction retournant une référence

114
114
114
115
115
116
116
117

10) Les templates ou la mise en œuvre de la généricité
Notions de base
L'idée
Mise en œuvre C + +
Templates de fonctions
Templates de classes
Compléments
Attention à l'ambiguïté

118
119
119
119
119
121
124
124

11) Les classes et f o n c t i o n s amies
Notions de base
L'idée
Mise en œuvre C + +
Attention !

125
126
126
126
127

12) L'héritage m u l t i p l e
Notions de base
L'idée
Mise en œuvre C + +
L'intérêt par rapport au C
Compléments
Duplication de données d'une classe de base
Masquage du polymorphisme

130
130
130
131
133
134
134
136
6

Pont entre C et C++

Héritage virtuel

138

13) Les e x c e p t i o n s
Notions de base
L'idée
Mise en œuvre C + +
L'intérêt par rapport au C
Résumé
Exemple complet
Compléments
Spécifier des exceptions
Exception non interceptée
Exceptions en cascade

140
140
140
141
145
145
145
147
147
148
150

14) La c o m p i l a t i o n séparée
Notions de base
L'idée
Mise en œuvre
Quoi mettre, et dans quels fichiers ?
Comment lancer la compilation ?
Compléments
Comment éviter les redéclarations ?
Static, inline et portée
Travailler avec d'autres langages

153
153
153
154
155
159
161
161
162
164

GUIDE DE SURVIE
15) Conseils
Les étapes d'un projet
Conception
Trouver les classes
Architecturer les classes
Détailler les classes
Évaluer l'ensemble
Codage C + +
Quels outils ?
Mise en œuvre de la réutilisabilité
Implantation des classes

169
169
170
170
171
173
174
174
174
175
177

16) Questions-Réponses

179

Index

185
16

Pont entre C et C++

d o n n é e s d ' u n objet, données-membres. P a r opposition, les autres d o n n é e s et fonctions sont qualifiées de hors-classe.
R e p r e n o n s notre e x e m p l e . Voici c o m m e n t n o u s aurions p u
définir la classe L i v r e :
class Livre
{
private :
char
char
char
public :
void
void
void

N o t e z la présence du
point-virgule après
l'accolade fermante.

Le C + + n'aime pas
forcer la main des
programmeurs. Il est
possible de définir des
données public, c'està-dire accessibles

titre[20];
auteur[20];
éditeur[20];

Saisir();
Afficher();
Imprimer();

} ;

C o m m e n t définir une f o n c t i o n - m e m b r e
C o m m e v o u s p o u v e z le constater, n o u s n ' a v o n s fait q u e déclarer les fonctions S a i s i r , A f f i c h e r e t I m p r i m e r . I l faut
m a i n t e n a n t les définir c o m p l è t e m e n t . La s y n t a x e est identiq u e au l a n g a g e C, à ceci près : il faut indiquer au compilateur à quelle classe est rattachée u n e fonction. Le format de
définition d'une fonction-membre (c'est-à-dire inclue dans
u n e classe) est le suivant :

directement de
l'extérieur de l'objet
C'est une pratique à
déconseiller, car elle
viole l'idée m ê m e
d'encapsulation.

type_retourné
NomDeVotreClasse: : f o n c t i o n { p a r a m è t r e s )
{
// corps de la fonction
}

Ce qui d o n n e d a n s n o t r e e x e m p l e :
void

Livre::Saisir()

{

puts("Entrez le titre du livre : " ) ;
gets(titre);
puts("Entrez le nom de l'auteur : " ) ;
gets(auteur);
puts("Entrez l'éditeur
:");
gets(éditeur);
}

void
Livre::Afficher()
{
printf("Titre : %s par %s (%s)",
titre, auteur, éditeur);
Les objets et les classes

17

)

void

Livre::Imprimer()

{

fprintf(stdprn, "Titre : %s par %s (%s)",
titre, auteur, éditeur);
}

L e s fonctions d ' u n e classe p e u v e n t l i b r e m e n t a c c é d e r à toutes ses d o n n é e s - m e m b r e s (ici : t i t r e , a u t e u r e t é d i t e u r ) .
N ' i m p o r t e quelle fonction d ' u n e classe p e u t é g a l e m e n t a p p e ler u n e autre fonction de la m ê m e classe. Voilà ! N o t r e prem i è r e classe est écrite ! V o y o n s m a i n t e n a n t de quelle m a n i è r e
n o u s p o u v o n s l'utiliser.
C r é e r un objet
P o u r utiliser u n e classe, il faut d ' a b o r d créer un objet qui a u r a c o m m e type cette classe. L a s y n t a x e est l a m ê m e q u e p o u r
déclarer une variable en C :
Nom_Classe

nom_objet;

D a n s l ' e x e m p l e du livre, cela d o n n e :
Livre

mon_livre;

B i e n e n t e n d u , toutes les formes de déclaration du C s o n t acceptées : tableaux, pointeurs, tableaux de p o i n t e u r s , etc. Par
exemple :
Livre
Livre

ma_bibliotheque[20];
// tableau de 20 objets-livres
*sur_ma_table_de_chevet;
// pointeur sur un objet-livre

A c c é d e r à la partie p u b l i q u e d'un objet
M a i n t e n a n t q u e n o u s s a v o n s c o m m e n t déclarer un objet, il
reste à découvrir c o m m e n t l'utiliser. En clair : c o m m e n t saisir, afficher ou i m p r i m e r un objet « livre ». V o u s ne serez p a s
d é p a y s é : l'accès a u x f o n c t i o n s - m e m b r e s p u b l i q u e s d ' u n o b jet se fait c o m m e si v o u s accédiez à u n e d o n n é e d ' u n e structure classique du l a n g a g e C :
18

Pont entre C et C++

objet.fonction_publigue(paramètres);
pointeur_sur_objet -> fonction_publique(paramètres);

Il est t e m p s d'écrire un petit p r o g r a m m e qui va saisir puis
afficher dix livres :
void

main()

{

Livre
int

bouquin[10];
i;

for (i = 0; i < 10; i++)
bouquin[i].Saisir();
for (i = 0; i < 10; i++)
bouquin[i].Afficher();
}

C ' e s t aussi s i m p l e que cela. V o u s s a v e z m a i n t e n a n t c o m m e n t
écrire des classes simples, et c o m m e n t utiliser des objets.
Précision i m p o r t a n t e sur l'accès a u x d o n n é e s private
V o u s v o u s r a p p e l e z q u e les d o n n é e s o u fonctions p r i v a t e
d ' u n e classe ne sont p a s visibles à partir d e s fonctions d'un
autre objet. M a i s u n e petite précision s ' i m p o s e : u n e fonction
m e m b r e d ' u n e classe A peut accéder directement à toutes les
d o n n é e s (y c o m p r i s p r i v a t e ) d'autres objets de classe A.
C e c i n ' e s t p a s f o r c é m e n t évident p u i s q u ' e l l e m a n i p u l e dans
ce cas les d o n n é e s d ' u n autre objet. Elle p e u t p o u r t a n t accéd e r a u x d o n n é e s e t fonctions p r i v a t e d e cet autre objet car
il est de la m ê m e classe. E x e m p l e :
class A
{
private :
int a;
public :
//

...

void fontion_a();
} ;

class B
{
private :
int b;
public :
//
};

...
L e s objets et les classes

void

19

A::fonction_a()

{

A
B

autre_a;
objet_b;

a = 1;
autre_a.a = 1 ;
objet_b.b = 1;

//
//
//
//

OK : on reste dans cet objet
OK : autre_a est de classe A
NON : b est private
dans une autre classe

}

L'intérêt par
rapport au C

V o u s l ' a v e z v u , u n objet r e g r o u p e des d o n n é e s e t d e s fonctions qui o p è r e n t sur ces d o n n é e s . Q u e l est l'intérêt de rais o n n e r e n objets plutôt q u ' e n fonctions o u e n structures,
c o m m e c'est le cas en C ?
L e c o d e g a g n e e n sécurité. V o u s s a v e z q u e les d o n n é e s
d ' u n objet ne sont m a n i p u l é e s q u e p a r ses p r o p r e s fonctions, les f o n c t i o n s - m e m b r e s . V o u s p o u v e z d o n c contrôler
l'intégrité des d o n n é e s et cibler v o s r e c h e r c h e s en c a s de
bogue.
L e s p r o g r a m m e s g a g n e n t e n clarté. S i l'architecture d e s
objets est b i e n conçue*, v o u s c o m p r e n e z r a p i d e m e n t le
rôle de tel ou tel objet. U n e fonction ne se p r o m è n e p a s
d a n s le vide, elle est rattachée à un objet, d o n c à
l ' e n s e m b l e des d o n n é e s q u ' e l l e m a n i p u l e .
La clarté des p r o g r a m m e s et leur c l o i s o n n e m e n t en objets
p e r m e t t e n t u n e m a i n t e n a n c e et u n e évolutivité p l u s facile.
G r â c e a u x trois a v a n t a g e s p r é c é d e n t s , des é q u i p e s d e dév e l o p p e u r s p e u v e n t p l u s facilement e n v i s a g e r l'écriture
de très gros p r o g r a m m e s .

* Les pointeurs de
fonction ne rendent
jamais un programme
plus clair...

C e r t a i n s p o u r r a i e n t p r é t e n d r e q u e l'on p e u t s i m u l e r le principe de classe en C. Il suffirait, après tout, de c r é e r u n e structure c o n t e n a n t des pointeurs de fonction, p o u r s t o c k e r les
fonctions m e m b r e s . C e r t e s , m a i s s'agissant d ' u n e g y m n a s t i q u e périlleuse, v o u s p e r d e z le bénéfice de la clarté d e s p r o g r a m m e s * . D e plus, rien n e v o u s interdit d ' a c c é d e r
d i r e c t e m e n t a u x d o n n é e s de votre structure, ce qui tord le
c o u à l'une d e s règles f o n d a m e n t a l e s de l ' e n c a p s u l a t i o n : cac h e r les d o n n é e s . Par ailleurs, v o u s serez d a n s l'incapacité de
s i m u l e r les autres caractéristiques m a j e u r e s d u C + + , q u e
n o u s d é c o u v r i r o n s d a n s les chapitres suivants.
20

Pont entre C et C++

Résumé

N o u s v e n o n s de p r é s e n t e r la n o t i o n la p l u s i m p o r t a n t e du
C + + et des l a n g a g e s orientés-objets : l'encapsulation.
L e C + + p e r m e t d e créer e t m a n i p u l e r d e s objets. U n objet s e
c o m p o s e de données et de fonctions de traitement qui lui sont
p r o p r e s . C e r t a i n e s de ces d o n n é e s et de ces fonctions sont
c a c h é e s , c'est-à-dire q u ' e l l e s ne sont accessibles q u e de
l'intérieur de l'objet, à partir de ses fonctions p r o p r e s .
P o u r créer u n objet e n C + + , i l faut d ' a b o r d définir u n m o u l e ,
a p p e l é classe e n C + + . L e s objets sont e n s u i t e créés c o m m e
des variables classiques, à partir de leur classe.
Les d o n n é e s et fonctions définies d a n s u n e classe s o n t a p p e lées respectivement données membres et fonctions membres, par
o p p o s i t i o n a u x d o n n é e s et fonctions hors-classe. Par e x e m p l e ,
n ' i m p o r t e quelle fonction de la librairie s t a n d a r d du C
( c o m m e p r i n t f ) est u n e fonction hors-classe.

Pour bien
démarrer
en C++

En tant q u e p r o g r a m m e u r en l a n g a g e C, la p h i l o s o p h i e objets
v o u s déroutera peut-être a u début. V o u s n e d e v e z p l u s p e n ser en t e r m e de fonctions, m a i s en t e r m e d'objets, r e g r o u p a n t
leurs p r o p r e s d o n n é e s e t leurs p r o p r e s fonctions. Par e x e m ple, si v o u s v o u l e z afficher le p l a t e a u d ' u n j e u , ne p e n s e z p a s
à quelque chose c o m m e a f f i c h e r _ p l a t e a u
(plateau)
m a i s plutôt à p l a t e a u . a f f i c h e r ( ) , o ù p l a t e a u est u n
objet c o n t e n a n t , par e x e m p l e , la p o s i t i o n des p i o n s du j e u .
Bref, v o u s partez d ' u n objet et lui e n v o y e z un m e s s a g e (sous
forme de fonction). R a s s u r e z - v o u s , c'est un réflexe qui viendra r a p i d e m e n t e t q u e v o u s trouverez d e p l u s e n p l u s
agréable. B i e n q u e la p h i l o s o p h i e objet n é c e s s i t e u n e c o n c e p tion p l u s s o i g n é e q u ' a u p a r a v a n t , elle p r o c u r e un réel plaisir
et b e a u c o u p d ' a v a n t a g e s .
Les objets et les classes

21

Compléments
R a p p e l o n s q u e v o u s p o u v e z sauter cette section d a n s u n
p r e m i e r t e m p s , et p a s s e r d i r e c t e m e n t au chapitre suivant. S i ,
toutefois, v o u s désirez e n connaître u n p e u p l u s sur les classes, c o n t i n u e z votre lecture.

Fonctions
inline

L e s fonctions inline sont une facilité d u C + + p e r m e t t a n t
d'optimiser
la
vitesse
d'exécution
des
programmes.
L ' e x é c u t i o n d ' u n e fonction inline est en effet p l u s rapide q u e
celle d ' u n e fonction définie n o r m a l e m e n t : le c o m p i l a t e u r
r e m p l a c e les appels à de telles fonctions p a r le c o d e de c e s
fonctions.
Cette r e m a r q u e en e n g e n d r e u n e autre : il v a u t m i e u x q u e les
fonctions inline soient très courtes (une ligne ou d e u x ) p o u r
éviter d e cloner i n u t i l e m e n t d e n o m b r e u s e s lignes d e c o d e .
M i s e en œuvre C + +
E x a m i n o n s l ' e x e m p l e suivant :
class UnNombre
{
private :
int
public :
int
void
void

nbr;
get_nbr ();
set_nbr (int);
Afficher();

} ;

C o m m e v o u s p o u v e z le voir, cette classe déclare trois fonctions p u b l i q u e s , qui ne sont e n c o r e définies nulle part. En
théorie, il faudrait définir c h a c u n e de ces fonctions en d e h o r s
de la classe, de cette m a n i è r e :
int UnNombre: :get_nbr()
{
return nbr;
}

Le C + + m e t à notre disposition les fonctions inline, p o u r définir c o m p l è t e m e n t u n e fonction à l'intérieur de la définition
de sa classe. Ainsi, au lieu de définir à part d e s fonctions très
22

Pont entre C et C++

c o u r t e s , v o u s p o u v e z les inclure d i r e c t e m e n t d a n s la classe.
C ' e s t c e q u e n o u s allons faire p o u r les fonctions s e t _ n b r e t
get_nbr:
class UnNombre
{
private :
int
public :
int
void
void

nbr;

get_nbr()
set_nbr(int n2)
Afficher();

{ return nbr; }
{ nbr = n2; }

} ;

C o m m e v o u s p o u v e z l e constater, n o u s a v o n s défini complèt e m e n t les d e u x p r e m i è r e s fonctions p u b l i q u e s de cette classe
(en gras d a n s l ' e x e m p l e ) . C e s fonctions sont a p p e l é e s inline.
L a troisième, A f f i c h e r , n ' e s t q u e déclarée — n o t e z l e pointv i r g u l e après la déclaration—. Il faudra la définir en dehors
de la classe, c o m m e n o u s a v i o n s l'habitude de le faire.
Q u a n d on définit u n e fonction inline, il est inutile d'accoler le
n o m de la classe et les caractères « : : » a v a n t le n o m de la
fonction, p u i s q u ' e l l e est définie d a n s le c o r p s de sa classe.

Ce système est efficace si vous utilisez la
compilation séparée.

Le mot-clé inline
Il existe un autre m o y e n de bénéficier du m é c a n i s m e inline,
sans définir les fonctions d i r e c t e m e n t d a n s la classe : il suffit
d'utiliser le mot-clé inline en tête de la définition de la
fonction. Q u a n d v o u s spécifiez q u ' u n e fonction est inline,
v o u s d o n n e z s i m p l e m e n t u n e indication a u c o m p i l a t e u r , qui
est libre de la suivre ou p a s . D'ailleurs certains compilateurs
refusent de rendre inline d e s fonctions qui c o n t i e n n e n t des
mots-clés c o m m e for ou while.
Si, par e x e m p l e , n o u s avions v o u l u q u e Afficher soit une
fonction inline, sans p o u r autant inclure s o n c o d e d a n s la
déclaration de la classe UnNombre, il aurait fallu écrire :

N o u s détaillerons cela
au chapitre 14.

inline

void

UnNombre:: Afficher()

{

printf("Le nombre est : %d", nbr);
}
Les objets et les classes

23

La déclaration de classe restant identique, m a i s il faut cette
fois-ci indiquer au c o m p i l a t e u r le n o m de la classe à laquelle
appartient
la
fonction
inline
(c'est
le
rôle
de
« U n N o m b r e : : »), p u i s q u e cette fonction n ' e s t p a s déclarée
d i r e c t e m e n t d a n s la classe.

Remarque : des
fonctions hors-classes
peuvent elles-aussi
êtres définies inline.

Le mot-clé this

Résumé
L e C + + p e r m e t a u p r o g r a m m e u r d e définir d e s fonctionsm e m b r e inline. C e s fonctions se c o m p o r t e n t c o m m e toutes
les autres fonctions d ' u n e classe, à la seule e x c e p t i o n q u e lors
d e l a compilation, c h a q u e appel à u n e fonction i n l i n A e s t
r e m p l a c é p a r le corps de cette fonction. Il en résulte un g a i n
de t e m p s à l'exécution. On réserve c e p e n d a n t cet a v a n t a g e
a u x fonctions très courtes, afin q u e la taille finale de
l ' e x é c u t a b l e ne soit p a s trop é n o r m e .

C h a q u e classe p o s s è d e a u t o m a t i q u e m e n t u n e d o n n é e c a c h é e
u n p e u spéciale : l e pointeur t h i s . Utilisé d a n s u n e fonction
d e classe, t h i s est u n pointeur sur l'objet c o u r a n t à partir
d u q u e l on a appelé la fonction m e m b r e . E x e m p l e :
class A
{
private:
int i ;
public :
void f ( ) ;
) ;

void A : :f()
{
this -> i = 1;

// équivaut à i = 1;

}

t h i s est de type « p o i n t e u r sur l'objet c o u r a n t qui a a p p e l é
l a fonction » . D a n s l ' e x e m p l e , t h i s est d e t y p e p o i n t e u r d e
A . V o u s e n déduirez q u e ( * t h i s ) représente l'objet c o u rant, et v o u s a u r e z raison.
L'utilité d e t h i s est patente p o u r certains opérateurs, o u
p o u r tester l'égalité de d e u x objets (voir p a g e 6 1 ) .
24

Pont entre C et C++

Éléments static
dans une
classe

On ne peut pas
utiliser le mot-clé this
dans une fonction
static, puisque celle-ci
ne dépend pas d'un
objet précis.

V o u s p o u v e z déclarer des objets, variables ou fonctions
s t a t i c d a n s u n e classe. C e s é l é m e n t s seront alors c o m m u n s à tous les objets de cette classe. Ainsi, u n e variable
s t a t i c aura la m ê m e valeur p o u r tous les objets de cette
classe. S i l'un des objets c h a n g e cette variable s t a t i c , elle
sera m o d i f i é e p o u r tous les autres objets.
On distingue ainsi les variables et fonctions d'instance (celles
qui sont spécifiques à un objet), et les variables et fonctions
de classe ( c o m m u n e s à tous les objets de cette classe). C e s
dernières doivent être déclarées s t a t i c .
I l faut initialiser c h a q u e variable s t a t i c e n d e h o r s d e l a
déclaration d e classe, sans répéter l e mot-clé s t a t i c (voir
l ' e x e m p l e ci-dessous).
L e s f o n c t i o n s - m e m b r e s déclarées s t a t i c d o i v e n t être a p p e lées à partir du n o m de la classe, c o m m e ceci :
N o m C l a s s e : : f ( ), sans faire référence à un objet précis. Elles p e u v e n t d o n c être a p p e l é e s s a n s q u ' a u c u n objet de la
classe n'existe.
#include <iostream.h>

Pour c o m p r e n d r e cet
exemple, vous devez
d'abord lire le
chapitre 2 sur les
constructeurs.

class
Flam
{
protected:
static
int nb_objets;
int
donnée;
public :
Flam() : donnée(0) { nb_objets++; }
static
affiche_nb_objets()
{ cout << nb_objets << endl; }
};

int

Flam::nb_objets = 0;

// initialisation

void
main()
{
// appel de la fonction static à partir de la classe
Flam::affiche_nb_objets();
Flam

a, b, c; // déclarons 3 objets

// appel de la fonction static à partir d'un objet
a.affiche_nb_objets();
}

// affichage
// 0
// 3
Les objets et les classes

25

D a n s cet e x e m p l e , le constructeur de Flam ajoute 1 à la v a riable de classe nb_objets
(déclarée static au s e n s
C + + ) . En créant trois objets a, b et c, n o u s a p p e l o n s trois fois
ce constructeur. On p e u t appeler u n e fonction static en la
faisant p r é c é d e r du n o m de la classe suivi de d e u x d o u b l e p o i n t s ou à partir d ' u n objet. Il n ' y a p a s de différence entre
les d e u x .
D i f f é r e n c e e n t r e le m o t - c l é static du C et du C + +
A t t e n t i o n : d a n s ce q u e n o u s v e n o n s d ' e x p l i q u e r , le m o t - c l é
static sert à déclarer d e s variables o u fonctions de classe.
En C standard, r a p p e l o n s q u e static signifie q u e l ' é l é m e n t
n ' e s t accessible que dans s o n fichier s o u r c e .

Déclaration
de classe
anticipée

T o u t c o m m e v o u s p o u v e z déclarer u n e structure à un endroit et la définir c o m p l è t e m e n t ailleurs, v o u s a v e z la p o s sibilité de le faire p o u r des classes. La s y n t a x e est s i m p l e :
class nom_de_classe;. I m a g i n o n s q u ' u n e classe A c o n tienne un objet de classe B et r é c i p r o q u e m e n t . Il faut recourir
à u n e déclaration p o u r q u e A ait c o n n a i s s a n c e de B a v a n t q u e
B ne soit définie :
class B;

// déclaration anticipée

class A
{
protected:
B
objet_B;
public :
I I . . .
};

class B
{
protected:
A
objet_A;
public : // ...
};
Les constructeurs
et les destructeurs

La vie et la mort d'un objet C++ sont régies
et les destructeurs. Les fonctions constructeurs
création d'un objet, les fonctions destructeurs
qu'il sort de sa portée de visibilité, c'est-à-dire

par les constructeurs
sont appelées à la
sont invoquées dès
dès qu'il meurt.

Les notions de base
L'idée

E n C o u e n C + + , q u a n d v o u s déclarez u n e variable s a n s
l'initialiser, son c o n t e n u est i n d é t e r m i n é . E n C + + , q u a n d
v o u s déclarez u n objet d ' u n e certaine classe, s o n c o n s t r u c teur sera a p p e l é automatiquement. Un c o n s t r u c t e u r est u n e
f o n c t i o n - m e m b r e qui p o r t e toujours le n o m de la classe d a n s
laquelle elle est définie, et qui ne renvoit rien (pas m ê m e un
v o i d ) . S i v o u s n e définissez p a s e x p l i c i t e m e n t u n c o n s t r u c teur p o u r u n e classe, l e C + + e n utilise u n d'office, qui v a s e
contenter de réserver de la m é m o i r e p o u r toutes les variables
de v o t r e classe.
Attention : q u a n d v o u s définissez un p o i n t e u r sur un objet,
a u c u n constructeur n ' e s t appelé. Si v o u s désirez e n s u i t e allouer de la m é m o i r e et appeler un constructeur, utilisez n e w
(voir chapitre 6, p a g e 8 3 ) .
28

Pont entre C et C++

P a r a l l è l e m e n t a u x constructeurs, l e C + + n o u s p r o p o s e les
destructeurs. Q u a n d l'objet sort de la p o r t é e du bloc d a n s lequel il a été déclaré, n o t a m m e n t q u a n d l ' e x é c u t i o n du prog r a m m e atteint la fin d ' u n bloc où l'objet est défini, son
destructeur est appelé automatiquement.

Mise en
œuvre C++
constructeurs

C o m m e n t définir u n e fonction c o n s t r u c t e u r ? N o u s savons
q u ' e l l e doit porter l e m ê m e n o m q u e s a c l a s s e . P r e n o n s
l ' e x e m p l e d ' u n e classe Equipage :
class Equipage
{
private :
int

personnel;

public :
Equipage(int personnel_initial);
} ;

Equipage::Equipage(int personnel_initial)
{
personnel = personnel_initial;
}

N o t r e constructeur de la classe Equipage ne r e t o u r n e auc u n e valeur, p a s m ê m e u n v o i d , p u i s q u e c'est u n constructeur. Il accepte un p a r a m è t r e , le n o m b r e de p e r s o n n e s dans
l ' é q u i p a g e . C e l a signifie q u e l o r s q u e v o u s v o u d r e z déclarer
un objet de classe Equipage, il faudra faire suivre
l'identifiant de l'objet p a r les p a r a m è t r e s effectifs du constructeur, entre p a r e n t h è s e s .
À l ' e x é c u t i o n du p r o g r a m m e suivant, le c o n s t r u c t e u r Equipage : : Equipage est appelé a u t o m a t i q u e m e n t , avec
c o m m e p a r a m è t r e l'entier 311 :
Ici, le constructeur est
appelé, ce qui initialise
la d o n n é e - m e m b r e

void

main()

{

Equipage
base_alpha(311) ;
// déclaration de l'objet et appel du constructeur

personnel à 311
)

Constructeur sans paramètre
Si v o u s définissez un constructeur s a n s p a r a m è t r e , par
e x e m p l e c o m m e ceci :
Les constructeurs et les destructeurs

29

Equipage : : Equipage()
{

nombre = 0 ;
)

il faut faire attention à la m a n i è r e de l'appeler. L ' e x e m p l e
suivant v o u s m o n t r e ce qu'il faut et ne faut pas faire :
void

main()

{

Equipage
Equipage

mon_equipage();
mon_autre_equipage;

// (1) incorrect
// (2) correct

}

Si v o u s ajoutez des p a r e n t h è s e s , c o m m e d a n s le c a s 1, le
c o m p i l a t e u r c o m p r e n d q u e v o u s déclarez u n e fonction !
Donc, pour appeler correctement un constructeur sans par a m è t r e , il faut o m e t t r e les p a r e n t h è s e s , c o m m e d a n s le cas 2.
C ' e s t s e u l e m e n t dans ce cas que le c o n s t r u c t e u r sans p a r a m è tre est appelé.
C o n s t r u c t e u r s surchargés
Il est tout à fait p o s s i b l e de définir p l u s i e u r s c o n s t r u c t e u r s
h o m o n y m e s , qui s e d i s t i n g u e r o n t alors p a r l e t y p e e t / o u l a
quantité d e p a r a m è t r e s . R e p r e n o n s l a classe E q u i p a g e e t
définissons trois c o n s t r u c t e u r s :
class Equipage
{
private :
int

personnel;

public :
Equipage(int personnel_initial);
Equipage();
Equipage(int nbr_femmes,
int nbr_hommes);
} ;

// constructeur 1
Equipage:: Equipage(int personnel_initial)
{
personnel = personnel_initial;
}

// constructeur 2
Equipage : : Equipage()
{
personnel = 0;

// 1
// 2
// 3
30

Pont entre C et C++

}
// constructeur 3
Equipage::Equipage(int nbr_femmes, int nbr_hommes)
{
personnel = nbr_femmes + nbr_hommes;
}

V o u s d i s p o s e z m a i n t e n a n t de trois m a n i è r e s d'initialiser un
objet d e classe E q u i p a g e . L ' e x e m p l e qui suit illustre c e concept :
void

main()

{

Equipage base_alpha(311);
Equipage mixte(20, 1 ) ;
Equipage inconnu;

// appel au constructeur 1
// appel au constructeur 3
// appel au constructeur 2

}

Mise en
œuvre C++
destructeurs

R a p p e l o n s q u e le destructeur est a p p e l é automatiquement
q u a n d un objet sort de la p o r t é e du b l o c d a n s lequel il est
déclaré — c'est-à-dire q u a n d il n ' e s t p l u s accessible au prog r a m m e u r . U n e f o n c t i o n - m e m b r e destructeur n e retourne
p a s de t y p e (pas m ê m e un v o i d ) , et n ' a c c e p t e aucun paramètre. C o m m e p o u r les c o n s t r u c t e u r s , le c o m p i l a t e u r g é n è r e un
destructeur p a r défaut p o u r toutes les classes qui n ' e n sont
p a s p o u r v u e s . Ce destructeur p a r défaut se b o r n e à libérer la
m é m o i r e des d o n n é e s de la classe. M a i s si v o u s désirez faire
u n e o p é r a t i o n particulière p o u r détruire un objet, et notamm e n t si v o u s a v e z d e s p o i n t e u r s d o n t il faut libérer l'espace
m é m o i r e , v o u s p o u v e z définir v o t r e p r o p r e destructeur.
Attention ! C o n t r a i r e m e n t a u x c o n s t r u c t e u r s , il ne p e u t exister qu'un seul destructeur par classe !
Le n o m de la fonction destructeur est de la forme
« -NomDeClasse ». Le p r e m i e r s i g n e est un « tilde ».
Exemple
I m a g i n o n s u n e classe qui c o n t i e n n e u n e d o n n é e - m e m b r e
p o i n t a n t v e r s u n e c h a î n e de caractères :
#include <stdio.h>
#include <string.h>
class Capitaine
Les constructeurs et les destructeurs

31

{

private :
char *nom;
public :
// constructeur
Capitaine) char *nom_initial );
// destructeur
-Capitaine();
} ;

// constructeur
Capitaine::Capitaine( char *nom_initial )
{
nom = (char*) malloc(strlen(nom_initial) + 1 ) ;
strcpy(nom, nom_initial);
}

// destructeur
Capitaine::-Capitaine()
{
free( nom );
}

Compléments
Constructeur
copie

Lorsqu'il faut initialiser un objet avec un autre objet de m ê m e
classe, le constructeur copie est appelé a u t o m a t i q u e m e n t .
Voici u n petit e x e m p l e d e c o d e a p p e l a n t l e c o n s t r u c t e u r c o pie de la classe B e t a :
class

Beta

// le contenu ne nous intéresse pas ici

void

fonction_interessante(Beta beta)

// traitement sur beta

void

main()
32

Pont entre C et C++

Beta

premier,
deuxième (premier),
troisième = premier;

fonction_interessante(premier);

// (1)
// (2)
// (3)

}

* voir le paragraphe sur
les listes d'initialisations,
page 47,

* Dans ce cas,
l'opérateur = n'est
pas appelé puisque ce
n'est pas une
affectation.

La ligne (1) signifie q u e l'objet d e u x i è m e doit être initialisé a v e c l'objet p r e m i e r * .
La ligne (2) initialise elle aussi l'objet t r o i s i è m e avec
l'objet p r e m i e r . C e n ' e s t p a s u n e affectation, c'est une
initialisation !*
Enfin, la ligne (3) fait appel à u n e fonction qui va recopier
le p a r a m è t r e effectif, p r e m i e r , d a n s s o n p a r a m è t r e formel, b e t a .
Bref, ces trois lignes font implicitement a p p e l au constructeur
c o p i e . C o m m e p o u r les c o n s t r u c t e u r s o r d i n a i r e s ou les destructeurs, l e C + + e n g é n è r e u n p a r défaut s i v o u s n e l'avez
p a s fait v o u s - m ê m e . Ce c o n s t r u c t e u r c o p i e p a r défaut effectue u n e c o p i e « b ê t e », m e m b r e à m e m b r e , d e s d o n n é e s de la
classe. D a n s les cas où v o s classes c o n t i e n n e n t d e s pointeurs,
cela p o s e un p r o b l è m e : en ne c o p i a n t q u e le pointeur, l'objet
c o p i é et l'objet c o p i a n t pointeraient sur la m ê m e zone mém o i r e . Et ce serait fâcheux ! C e l a signifierait q u ' e n modifiant
l ' u n d e s d e u x objets, l'autre serait é g a l e m e n t modifié de
m a n i è r e « invisible » !
L e s c h é m a suivant illustre c e p r o b l è m e .

Schéma illustrant le problème d'une copie membre à membre
Les constructeurs et les destructeurs

33

P o u r r é s o u d r e c e p r o b l è m e , v o u s d e v e z d o n c écrire votre
propre constructeur copie, qui effectuera u n e c o p i e p r o p r e —
avec, par e x e m p l e , u n e nouvelle allocation p o u r c h a q u e
pointeur d e l'objet copié. N o u s o b t e n o n s l e s c h é m a suivant,
après u n e c o p i e « p r o p r e ».

Schéma illustrant le résultat d'une copie propre.

C o m m e n t définir son p r o p r e c o n s t r u c t e u r copie ?
La f o r m e d ' u n c o n s t r u c t e u r copie est toujours la m ê m e . Si
votre classe s'appelle Gogol, s o n c o n s t r u c t e u r c o p i e

s'appellera Gogol: : Gogol (const Gogol&). Le p a r a m è * les références sont
traitées au chapitre 9,
page I 13.

*cout est un mot clé
C++ utilisé pour
l'affichage de
caractères. Voir

tre est u n e référence* à un objet de classe Gogol. Ici const
n ' e s t p a s n é c e s s a i r e m a i s fortement r e c o m m a n d é , p u i s q u e
v o u s ne modifierez pas l'objet à copier.
É c r i v o n s la classe Gogol, p o s s é d a n t un p o i n t e u r de char, et
efforçons-nous d'écrire son c o n s t r u c t e u r de c o p i e :
#include <string.h>
#include <iostream.h>

// pour cout*

class Gogol
{
private:
char

chaptre 7, page 89.

*pt;

public :
// constructeur normal
Gogol(char * c ) ;
// constructeur de copie
Gogol(const Gogol &a_copier);
34

Pont entre C et C++

// accès aux données membre
void set_pt(char * c ) ;
char *get_pt();
// autres fonctions
void Afficher();:
>;

// constructeur normal
Gogol::Gogol(char *c)
{
pt = new char [strlen(c) + 1 ] ;
strcpy(pt, c ) ;
}

// constructeur de copie
Gogol:: Gogol(const Gogol &a_copier)
{

if (this != &a_copier) // voir dans la marge

Il faut vérifier que

{

l'objet source et

// effaçons l'ancien pt
delete pt;

l'objet destination
sont différents, car

// allouons de la place pour le nouveau pt
pt = new char [strlen(a_copier.pt) + 1 ] ;

s'ils étaient identiques,
delete pt effacerait à
la fois les pointeurs

// donnons une valeur au nouveau pt
strcpy(pt, a_copier.pt);

source et destination !
}
}

void Gogol::set_pt(char *c)
{
delete pt;
pt = new char [strlen(c) + 1 ] ;
strcpy(pt, c ) ;
}

char *Gogol: :get_pt()
{
return pt;
}

void Gogol::Afficher()
{

cout << pt << endl;
}

void
{

maint)
Gogol

gog("Zut"),
bis = gog;
// appel du constructeur copie

gog.Afficher();
bis.Afficher();

// Zut
// Zut
Les constructeurs et les destructeurs

35

// on modifie la chaine de gog
gog.set_pt("Mince alors");
gog.Afficher();
bis.Afficher();

// Mince alors
// Zut

}

C o n c l u s i o n : les objets gog et bis p o s s è d e n t b i e n d e u x p o i n teurs d é s i g n a n t d e u x z o n e s m é m o i r e s différentes. C ' e s t parfait. M a i s si n o u s n ' a v i o n s p a s écrit notre p r o p r e
constructeur copie, celui g é n é r é p a r défaut aurait c o p i é les
d o n n é e s m e m b r e à m e m b r e . A i n s i , d a n s gog et bis, pt aurait été le m ê m e . D o n c , le fait de détruire le p o i n t e u r de gog
aurait é g a l e m e n t détruit celui de bis p a r effet de b o r d . Inutile de préciser les c o n s é q u e n c e s d é s a s t r e u s e s d ' u n tel é v é nement.

Constructeurs
de conversion

Si vous avez besoin
de l'opération réciproque (convertir un
objet de votre classe
vers une variable de
type T), allez voir
p g 61 où l'on vous
ae
dira que c'est
possible.

Un c o n s t r u c t e u r n ' a y a n t q u ' u n seul a r g u m e n t de t y p e T
spécifie u n e c o n v e r s i o n d ' u n e variable de t y p e T v e r s un o b jet de la classe du constructeur. Ce c o n s t r u c t e u r sera a p p e l é
a u t o m a t i q u e m e n t c h a q u e fois q u ' u n objet de t y p e T sera rencontré là où il faut un objet de la classe de constructeur.
P a r e x e m p l e , i m a g i n o n s q u ' o n veuille u n e c o n v e r s i o n autom a t i q u e d ' u n entier vers un objet de classe Prisonnier, où
l'entier r e p r é s e n t e s o n n u m é r o matricule. A i n s i , q u a n d l e
c o m p i l a t e u r attend un objet Prisonnier et qu'il n ' a q u ' u n
entier à la p l a c e , il effectue la c o n v e r s i o n a u t o m a t i q u e m e n t
en créant l'objet Prisonnier à l'aide du c o n s t r u c t e u r de
conversion. Exemple :
#include <iostream.h>
class Prisonnier
{
protected:
int numéro;
char nom [50] ;
public :
//

...

Prisonnier(int n) : numéro(n)
{ cout << "Conversion de " << n << endl;
nom[0] = 0 ; }
} ;

void
{

fonction(Prisonnier p)
//

. . .
36

Pont entre C et C++

}

void
{

main()

Prisonnier

p = 2;

// donne p = Prisonnier(2)

fonction(6); // donne fonction(Prisonnier(6))
}
// affiche :
// Conversion de 2
// Conversion de 6
L'héritage simple

L'héritage vous permet de modéliser le monde réel avec souplesse,
d'organiser vos classes et de réutiliser celles qui existent déjà. C'est
un
concept fondamental
en programmation
orientée-objets.
Nous
parlons ici d'héritage simple, par opposition à l'héritage multiple
qui sera développé dans la deuxième partie du livre.

Notions de base
L'idée

L e s informaticiens se feraient b e a u c o u p m o i n s de soucis s'ils
p o u v a i e n t r é g u l i è r e m e n t réutiliser des p o r t i o n s d e c o d e
qu'ils ont déjà écrit p o u r des projets antérieurs. G r â c e à
l'héritage, u n e part du r ê v e devient réalité : v o u s p o u v e z
profiter des classes déjà existantes p o u r en créer d'autres.
M a i s l à o ù l'héritage est intéressant, c'est q u e v o u s p o u v e z
ne garder q u ' u n e partie de la classe initiale, modifier certaines de ses fonctions ou en ajouter d'autres.
L'héritage s'avère très a d a p t é a u x cas o ù v o u s d e v e z m a n i puler des objets qui ont des p o i n t s c o m m u n s , m a i s qui diffèrent l é g è r e m e n t .
38

Pont entre C et C++

* N o u s aurions pu
parler de la classifica-

Le principe
Le p r i n c i p e initial de l'héritage est p r o c h e de celui de la classification en catégories : on part du c o n c e p t le p l u s général
p o u r se spécialiser d a n s les cas particulier. A d m e t t o n s que
n o u s v o u l i o n s parler de véhicules*, et e s s a y o n s de tisser des
liens d'héritage entre q u e l q u e s v é h i c u l e s b i e n c o n n u s :

t i o n des cornichons,
des jeunes filles ou
des flippers, mais un
exemple classique est
sans doute mieux
adapté à un large
public.

Le sens des flèches d'héritage pourrait être : « p o s s è d e les caractéristiques de » ou « est un », au s e n s large du terme.
Ainsi, voiture est un véhicule. De m ê m e p o u r le bateau. Le camion, lui, p o s s è d e les caractéritiques de voiture et, indirectem e n t , de véhicule.
O n dit q u e v o i t u r e hérite de v é h i c u l e . D e m ê m e , bateau hérite
de v é h i c u l e , et c a m i o n de voiture.
D ' u n cas général, r e p r é s e n t é par véhicule, n o u s a v o n s tiré
d e u x cas particuliers : voiture et bateau. Camion est lui-même
un « cas particulier » de voiture, car il en p o s s è d e grosso modo
toutes les caractéristiques, et en ajoute d'autres q u i lui sont
propres.

Mise en
œuvre C++

D a n s l ' e x e m p l e ci-dessus, c h a q u e é l é m e n t du s c h é m a est rep r é s e n t é p a r u n e classe. U n e r e m a r q u e de vocabulaire :
q u a n d u n e classe A hérite d ' u n e classe B, on dit q u e A est la
classe de base et B la classe dérivée. P o u r y voir p l u s clair, écriv o n s les classes v é h i c u l e et voiture, et e x a m i n o n s ensuite ce
q u e cela signifie a u n i v e a u d u C + + :

Le m o t clé protected
est expliqué dans les
pages qui suivent.

class Véhicule
{
protected :
int
// ...

nombre_de_places;
L'héritage simple

39

public :
// constructeurs
Véhicule();
// ...
// fonctions de traitement
void
Afficher));
// . . .
} ;

Remarque : nous
n'avons pas détaillé

class Voiture : public Véhicule
{

protected :
int chevaux_fiscaux;
// . . .
public :
// constructeurs
Voiture();
// fonctions de traitement
void
CalculVignette();

ces classes afin de
nous concentrer sur
le mécanisme
d'héritage. Vous
retrouverez les classes
complètes en fin de
chapitre.
} ;

R i e n ne d i s t i n g u e ces classes d'autres classes ordinaires, si ce

n ' e s t la c h a î n e « : public Véhicule » à la suite du n o m
de la classe Voiture. C ' e s t bel et b i e n cette c h a î n e qui indiq u e au compilateur q u e la classe Voiture hérite de la classe

Véhicule. Le mot-clé public qualifie la spécification d'accès
d e l'héritage. N o u s v e r r o n s ci-dessous q u e l e t y p e d ' h é r i t a g e
d é t e r m i n e quelles d o n n é e s de la classe de b a s e sont accessib l e s d a n s la classe dérivée.
V o i l à , n o u s a v o n s déclaré q u e la classe Voiture héritait de
la classe Véhicule. Q u e cela signifie-t-il c o n c r è t e m e n t ? Q u e
d a n s d e s objets de classe dérivée (Voiture), v o u s p o u v e z
a c c é d e r à des d o n n é e s ou fonctions m e m b r e s d'objets de la
classe de b a s e (Véhicule).

* Rappelons que la
spécification d'accès est
déterminée par le mot-clé
qui suit les deux-points
(«:») après la ligne class

NomClasseDérivée.

Accès a u x m e m b r e s de la classe de b a s e
P o u r savoir p r é c i s é m e n t quelles d o n n é e s s o n t accessibles
d a n s la classe dérivée, il faut considérer la spécification
d'accès* ainsi q u e le t y p e des d o n n é e s (public, protected
ou private) de la classe de b a s e . Le tableau s u i v a n t d o n n e
les c o m b i n a i s o n s possibles :
40

Pont entre C et C++

A u t r e m e n t dit, le
mot-clé protected est
identique à private si
vous n'utilisez pas
l'héritage.

L e c o n t e n u d u tableau i n d i q u e l e type d u m e m b r e ( p u b l i c ,
p r o t e c t e d , p r i v a t e o u inaccessible) d a n s l a classe dérivée.
V o u s d é c o u v r e z d a n s c e tableau u n n o u v e a u spécificateur
d ' a c c è s : p r o t e c t e d . C e spécificateur est i d e n t i q u e à p r i v a t e à l'intérieur de la classe. Il diffère u n i q u e m e n t en cas
d'héritage, p o u r d é t e r m i n e r s i les é l é m e n t s p r o t e c t e d sont
accessibles ou n o n d a n s les classes dérivées.
P o u r m i e u x c o m p r e n d r e ce tableau, voici trois petits exemples de dérivation :
class Base
{
private :
int
protected :
int
public :
int

detective_prive;
acces_protege;
domaine_publicp±e;

} ;

class Deriveel : public Base
{
// detective_prive est inaccessible ici
// acces_protege est considéré comme « protected »
// domaine_public est considéré comme « public »
} ;

class Derivee2 : protected Base
{
// detective_prive est inaccessible ici
// acces_protege est considéré comme « protected »
// domaine_public est considéré comme « protected »
>;

class
{
//
//
//
};

Derivee2 : private Base
detective_prive est inaccessible ici
acces_protege est considéré comme « private »
domaine_public est considéré comme « private »
L'héritage simple

41

Ces exemples amènent plusieurs remarques :
U n e d o n n é e o u fonction m e m b r e p r i v a t e est toujours inaccessible d a n s ses classes dérivées
L a spécification d ' a c c è s d ' u n e d o n n é e ( p u b l i c , p r o t e c t e d o u p r i v a t e ) d a n s u n e classe dérivée est e n fait l a
spécification d ' a c c è s la p l u s forte. Par e x e m p l e , si u n e
f o n c t i o n - m e m b r e est p r o t e c t e d d a n s l a classe d e b a s e ,
e t q u e l'héritage est p u b l i c , elle devient p r o t e c t e d
d a n s la classe dérivée.
E n règle générale, o n utilise l'héritage p u b l i c p o u r m o déliser des relations du m o n d e « réel ». V o i r le chapitre 15
sur les conseils.

Redéfinition
d'une fonctionmembre

V o u s p o u v e z définir des fonctions a u x entêtes i d e n t i q u e s
d a n s différentes classes a p p a r e n t é e s p a r u n e relation
d'héritage. La fonction a p p e l é e d é p e n d r a de la classe de
l'objet qui l'appelle, ce qui est d é t e r m i n é s t a t i q u e m e n t à la
compilation. R e p r e n o n s : u n e f o n c t i o n - m e m b r e d ' u n e classe
dérivée p e u t d o n c avoir le m ê m e n o m (et les mêmes p a r a m è tres) q u e celle d ' u n e classe de b a s e .
P r e n o n s l ' e x e m p l e d ' u n e gestion de textes qui d i s t i n g u e les
textes b r u t s des textes m i s en forme, et définissons d e u x
fonctions a u x entêtes identiques (mais au c o r p s différent) :
#include <stdio.h>
class TexteBrut
public :
void
void

Imprimer(int nb) ;

TexteBrut::Imprimer(int nb)

printf("Imprimer de la classe TexteBrutXn");

class TexteMisEnForme : public TexteBrut
public :
void
void

// héritage

Imprimer(int n b ) ;

TexteMisEnForme::Imprimer(int nb)

printf("Imprimer de la classe TexteMisEnFormeXn");
42

Pont entre C et C++

void
main()
{
TexteBrut
TexteMisEnForme

txt;
joli_txt;

txt.Imprimer( 1 ) ;
joli_txt.Imprimer(1);
}

// affichage :
Imprimer de la classe TexteBrut
Imprimer de la classe TexteMisEnForme

Conversion de
Dérivée*
vers Base*

D a n s un cas où u n e classe Dérivée hérite p u b l i q u e m e n t
d ' u n e classe Base, les c o n v e r s i o n s de p o i n t e u r s de Dérivée
v e r s des p o i n t e u r s de Base est faite a u t o m a t i q u e m e n t aux
endroits où le c o m p i l a t e u r attend un p o i n t e u r sur Base :
class Base
{ >;
class Dérivée : public Base
{ };
void
main()
{
Base
*base;
Dérivée *derivee;
base = dérivée;

// OK

}

Attention : cette c o n v e r s i o n n ' e s t p a s autorisée si l'héritage

est protected ou private ! Si v o u s r e m p l a c e z public
p a r protected d a n s le listing ci-dessus, la d e r n i è r e ligne du
main p r o v o q u e r a u n e erreur de c o m p i l a t i o n . P o u r q u o i ?
P a r c e q u e p o u r q u ' u n p o i n t e u r sur u n e classe dérivée puisse
être converti sur u n e classe de b a s e , il faut q u e cette dernière
soit accessible, c'est-à-dire q u ' e l l e p o s s è d e des m e m b r e s public accessibles. Or ce n ' e s t p a s le cas d ' u n h é r i t a g e pro-

tected ou private.
Résumé

E n C + + , u n e classe p e u t hériter d ' u n e autre. O n dit alors que
la classe qui hérite est u n e classe dérivée, l'autre classe étant
a p p e l é e classe de base. Q u a n d u n e classe A hérite d'une
classe B, elle p e u t a c c é d e r à certaines d o n n é e s ou fonctions
L'héritage simple

43

m e m b r e s d e l a classe B . C ' e s t u n petit p e u c o m m e s i u n e
partie de la classe B avait été recopiée d a n s le c o r p s de la
classe A.
Pour savoir ce qui est accessible d a n s A, il faut c o n s i d é r e r la
spécification d ' a c c è s ( p u b l i c , p r o t e c t e d o u p r i v a t e ) , l e
type d ' a c c è s des d o n n é e s d e B ( p u b l i c , p r o t e c t e d o u
p r i v a t e ) e t consulter l e tableau d e l a p a g e 4 0 .

L'intérêt par
rapport au C

Là, le C est c o m p l è t e m e n t battu. G r â c e à l'héritage, v o u s allez enfin p o u v o i r réutiliser facilement le c o d e q u e v o u s a v e z
déjà écrit. V o u s p o u r r e z enfin « factoriser » les p o i n t s c o m m u n s d ' u n e structure d'objets. Il n ' e x i s t e rien, d a n s le
l a n g a g e C , qui p e r m e t t e d e mettre e n œ u v r e facilement u n
m é c a n i s m e similaire.
P r e n o n s un e x e m p l e : v o u s d e v e z gérer les e m p l o y é s d ' u n e
centrale nucléaire. À partir d ' u n e classe de b a s e E m p l o y é ,
v o u s faites hériter d'autres classes c o r r e s p o n d a n t a u x catégories de p e r s o n n e l : Ouvrier, C a d r e , A g e n t S é c u r i t é , etc. Si à
l'avenir d'autres p o s t e s sont créés (par e x e m p l e P o r t e P a r o l e
ou A v o c a t ) , v o u s p o u r r e z créer la classe c o r r e s p o n d a n t e et la
faire hériter de E m p l o y é ou de classes spécifiques. D a n s n o tre e x e m p l e , un P o r t e P a r o l e pourrait hériter de la classe C a dre. L ' a v a n t a g e : v o u s n ' a v e z p a s à ré-écrire tout ce q u e les
portes-paroles et les c a d r e s ont en c o m m u n s .

Compléments
Héritage et
constructeurs

L e s c o n s t r u c t e u r s n e sont j a m a i s hérités. Q u a n d v o u s déclarez un objet d ' u n e classe dérivée, les c o n s t r u c t e u r s p a r d é faut d e s classes de b a s e s v o n t être a p p e l é s a u t o m a t i q u e m e n t
avant q u e le c o n s t r u c t e u r spécifique de la classe dérivée ne le
soit. Voici un e x e m p l e :
class Base
{
public :
Base();
44

Pont entre C et C++

} ;

class Deriveel : public Base
{
public :
Deriveel();
} ;

class Derivee2 : public Deriveel
{
public :
Derivee2();
};

void

main()

{

Base
b;
// appel à Base()
Deriveel dl; // appels à Base() puis à Deriveel()
Derivee2 d2 ; // appels à Base() , Deriveel().. .
// puis Derivee2()
// corps de la fonction
}

P o u r ne p a s a p p e l e r les c o n s t r u c t e u r s p a r défauts m a i s des
c o n s t r u c t e u r s avec des p a r a m è t r e s , v o u s d e v e z recourir aux
listes d'initialisations, e x p l i q u é e s p l u s loin d a n s ce chapitre.

Héritage et
destructeurs

A l o r s q u e les constructeurs sont a p p e l é s d a n s l'ordre desc e n d a n t (de la classe de b a s e à la classe d é r i v é e ) , c'est
l'inverse p o u r les destructeurs : celui de la classe d é r i v é e est
a p p e l é en p r e m i e r , p u i s celui de la classe de b a s e immédiat e m e n t supérieure, et ainsi de suite j u s q u ' à la classe de base
la plus haute. C o m p l é t o n s l ' e x e m p l e p r é c é d e n t :
void

main()

{

Base
b;
// appel à Base()
Deriveel dl ; // appels à Base{) puis à DeriveelO
Derivee2 d2 ; // appels à Base(), Deriveel().. .
// ...puis Derivee2()
// corps de la fonction
}

// mort de b : appel à -Base()
// trépas de dl : appels à -Deriveel() puis -Base()
// éradication de d2 : appels à ~Derivee2(),
//
-Deriveel() puis -Base()
L'héritage simple

Exemple
complet

45

R e p r e n o n s notre p r o b l è m e d e véhicules. Voici l e c o d e c o m plet des classes Véhicule et Voiture, et d e s e x e m p l e s
d'accès a u x d o n n é e s de la classe de b a s e , à savoir Véhiculé.
class Véhicule
{
private :
char
buffer_interne[128];
// uniquement utilisé par les fonctions de
// la classe Véhicule. Inaccessible aux
// classes dérivées.
protected :
char
nom_vehicule[20];
int
nombre_de_places;
// données protégées, transmises aux classes
// dérivées mais toujours inaccessibles
/ / à l'extérieur de l'objet
public :
// fonctions qui resteront publiques
// dans les classes dérivées (à part les
// constructeurs)
// constructeur
Véhicule(char *nom, int places);
// fonctions d'accès aux données membre
char
*get_nom_vehicule();
int
get_nombre_de_places();
void
set_nom_vehicule(char *nom);
int
set_nombre_de_place(int p ) ;
// fonctions de traitement
void
Afficher();;
};

// définition des fonctions
Véhicule::Véhicule(char *nom, int places)
{
sprintf(nom_vehicule, "%20s", n o m ) ;
// on écrit dans la chaine nom_vehicule les
// premiers caractères de la chaine nom.
nombre_de_places = places;
}

char

*Vehicule::get_nom_vehicule()

{

return nom_vehicule;
}

int

Véhicule::qet nombre de places!)

{

return nombre_de_places;

20
46

Pont entre C et C++

}

void
Véhicule::set nom„vehicule(char *nom)
{
sprintf(nom_vehicule, "%20s", nom);
// on écrit dans la chaine nom_vehicule les 20
// premiers caractères de la chaine nom.
}

int
{

Véhicule::set_nombre_de_place(int

p)

nombre_de_places = p;
}
//

Voici maintenant la classe dérivée

class Voiture : public Véhicule
{

private :
float
SavantCalcul ();
protected :
int chevaux_fiscaux;
public :
// constructeurs
Voiture(char *nom, int places, int chevaux);
// fonctions d'accès
int get_chevaux_fiscaux();
void set_chevaux_fiscaux(int c h ) ;
// fonctions de traitement
float
CalculVignette();
} ;

Voiture::Voiture(char *nom, int places, int chevaux)
{

sprintf(nom_vehicule, "%20s", n o m ) ;
nombre_de_places = places;
chevaux_fiscaux = chevaux;
}
int Voiture : :get_chevaux_fiscaux()
{

return chevaux_fiscaux;
}

void
Voiture::set_chevaux_fiscaux(int ch)
{
chevaux_fiscaux = ch;
}

float Voiture::SavantCalcul(int code)
{
float
savant_calcul;
// on fait un savant calcul avec "code"
return savant_calcul;
L'héritage simple

47

}

La fonction
CalculVignette fait

float

Voiture::CalculVignette()

{

int code;

n'importe quoi,
soyez certain que

if (chevaux_fiscaux < 2)
code = 0 ;
else
code = chevaux_fiscaux - 1;
return (SavantCalcul(code));

l'auteur en a
parfaitement
conscience.
}

Listes
d'initialisations

L e s lecteurs les p l u s alertes n e m a n q u e r o n t p a s d e c o n s t a t e r
q u e l q u e s l o u r d e u r s d a n s l ' e x e m p l e p r é c é d e n t . E x a m i n e z les
c o n s t r u c t e u r s de la classe Voiture. E x a m i n e z m a i n t e n a n t
les c o n s t r u c t e u r s de la classe Véhicule. Je récapitule p o u r
v o u s les d e u x c o n s t r u c t e u r s ci-dessous. N e r e m a r q u e z - v o u s
pas une certaine ressemblance ?
Véhicule::Vehicule(char *nom, int places)
{
sprintf(nom„vehicule, "%20s", nom);
nombre_de_places = places;
}

Voiture::Voiture(char *nom, int places, int chevaux)
{
sprintf(nom_vehicule, %20s", nom);
nombre de_places = places;
chevaux_fiscaux = chevaux;
}
n

O h , m a i s c'est b i e n sûr ! L e s d e u x p r e m i è r e s lignes s o n t
i d e n t i q u e s ! N ' e s t - c e p a s là le s i g n e p r o v o c a t e u r d ' u n e red o n d a n c e inutile et d a n g e r e u s e p r o p r e à susciter u n e colère
aussi v i v e q u e justifiée ?
Le p r o b l è m e vient du fait q u e les c o n s t r u c t e u r s (ainsi q u e les
d e s t r u c t e u r s , m a i s c e n ' e s t p a s l e p r o p o s ici) n e s o n t p a s hérités. C e l a signifie q u e , q u e l q u e part d a n s le c o n s t r u c t e u r de
Voiture, v o u s d e v e z initialiser les v a r i a b l e s - m e m b r e s défin i e s d a n s Véhicule, et d o n t Voiture a hérité. Ici, il s'agit
d e s v a r i a b l e s nom et places. Le p r o b l è m e est en fait s i m p l e :
c o m m e n t faire p o u r appeler un c o n s t r u c t e u r de la c l a s s e de
B a s e , d e p u i s un c o n s t r u c t e u r d ' u n e classe d é r i v é e ? D e u x
solutions :
48

Pont entre C et C++

Vous êtes en train de
paniquer ? Vous avez
l'impression d'être
noyé sous les n o u veautés syntaxiques
du C + + ? Ne trem-

Faire c o m m e d a n s l ' e x e m p l e , c'est-à-dire recopier les lig n e s de c o d e du constructeur de la c l a s s e de b a s e qui
v o u s intéresse. C ' e s t très laid : d u p l i q u e r b ê t e m e n t du
c o d e , c'est alourdir le p r o g r a m m e , et si j a m a i s ce c o d e doit
être modifié, v o u s devrez retrouver tous les e n d r o i t s où
v o u s l ' a v e z recopié. I m a g i n e z u n p e u c e q u e cela peut
d o n n e r d a n s u n e hiérarchie de p l u s i e u r s c e n t a i n e s de
classes !
La m e i l l e u r e solution : utiliser u n e liste d'initialisation.
Q u ' e s t - c e d o n c ? C ' e s t un m o y e n p r a t i q u e d ' a p p e l e r direct e m e n t des constructeurs p o u r des d o n n é e s - m e m b r e qu'il
faut initialiser. Voici un e x e m p l e d'utilisation de la liste
d'initialisation :

blez plus, car l'aidem é m o i r e C + + est
arrivé ! Il est joli et il
vous attend sur le

// constructeur de la classe Dérivée, amélioré
Voiture ::Voiture(char *nom, int places, int chevaux)
: Véhicule(nom, places)
{

rabat de la
couverture !

chevaux_fiscaux = chevaux;
)

J u s t e après les p a r a m è t r e s du constructeur, tapez le caractère : (deux points) et a p p e l e z e n s u i t e les constructeurs
d e s classes de b a s e s q u e v o u s a v e z c h o i s i s , a v e c leurs par a m è t r e s b i e n e n t e n d u . Si v o u s a p p e l e z p l u s i e u r s constructeurs, séparez-les p a r u n e virgule.
D a n s l ' e x e m p l e ci-dessus, les p a r a m è t r e s nom e t p l a c e s
utilisés p o u r appeler l e c o n s t r u c t e u r d e V é h i c u l e sont
ceux que vous avez passés au constructeur de V o i t u r e .
V o u s p o u v e z é g a l e m e n t utiliser u n e liste d'initialisation
p o u r v o s variables s i m p l e s (entier, caractère, etc.) Exemple :
// constructeur de la classe Dérivée, encore amélioré
Voiture::Voiture(char *nom, int places, int chevaux)
: Véhicule(nom, places), chevaux_fiscaux(chevaux)
{ }

L e s différents é l é m e n t s de la liste d'initialisation sont sép a r é s p a r u n e virgule. V o u s r e m a r q u e z q u e m a i n t e n a n t , il
n ' y a plus d'instruction d a n s le corps de la fonction !
La liste d'initialisation est le seul m o y e n d'initialiser une
d o n n é e - m e m b r e spécifiée constante. R a p p e l o n s qu'une
L'héritage simple

49

c o n s t a n t e ne p e u t être qu'' initialisée (et j a m a i s affectée), voir
p a g e 67. Un autre a v a n t a g e d e s listes d'initialisation : elles
n e créent p a s d'objets t e m p o r a i r e s c o m m e u n e fonction
ordinaire. L e s objets sont initialisés d i r e c t e m e n t , d ' o ù gain
de t e m p s certain.
Résumé
U n e liste d'initialisation p e u t appeler d e s c o n s t r u c t e u r s d e s
classes d e b a s e o u initialiser des variables s i m p l e s . R é s u m é
de la s y n t a x e :
Classe::Classe(paramètres) : liste_ini
{
// corps du constructeur de Classe
}

O ù l i s t e _ i n i contient u n o u p l u s i e u r s é l é m e n t s s u i v a n t s ,
séparés p a r d e s virgules :
ConstructeurClasseDeBase(paramètres) ou
Variable(valeur_initiale)
La surcharge

Le C++ offre aux programmeurs la possibilité de définir des fonctions homonymes. Ce principe est appelé « surcharge de fonction ».

Notions de base
L'idée

Écrire d e u x fonctions qui portent l e m ê m e n o m est u n e c h o s e
impensable en langage C. Pas en C + + . Imaginez que vous
v o u l i e z écrire u n e fonction qui trie des tableaux d'entiers.
L ' e n t ê t e de cette fonction ressemblerait à ceci :
void

tri(int tab[], int taille_tableau);

Si p a r la suite v o u s a v e z b e s o i n d ' u n e fonction q u i trie des
tableaux de réels, v o u s d e v e z réécrire la fonction, en lui donn a n t un autre n o m :
void

tri_reels(float tab[], int taille_tableau);

S i v o u s êtes logiques avec v o u s - m ê m e , v o u s d e v r e z égalem e n t c h a n g e r le n o m de la fonction initiale :
void

tri_entiers(int tab[], int taille_tableau);
52

Pont entre C et C++

Ce qui v o u s oblige à modifier tous les appels à cette fonction.
G r â c e au m é c a n i s m e de surcharge de fonction, v o u s pouvez
d o n n e r le n o m tri a u x d e u x fonctions, le compilateur pouv a n t les différencier grâce a u x types d ' a r g u m e n t s .
N o t e z q u e le l a n g a g e C p r o p o s e déjà un type de surcharge
p o u r les o p é r a t e u r s arithmétiques : le m é c a n i s m e mis en
œ u v r e d é p e n d du type des objets sur l e s q u e l s on invoque
l'opérateur.

Mise en
œuvre C++

R e p r e n o n s notre e x e m p l e de tri, et appliquons-lui le principe
de s u r c h a r g e . L e s d e u x entêtes d e v i e n n e n t :
void
void

tri(int
tab[], int taille_tableau);
triffloat tab[], int taille_tableau);

La seule différence étant le type du p r e m i e r paramètre.
E x a m i n o n s m a i n t e n a n t les appels de fonctions suivants :
void main()
{
float
int

tab_f[] = { 1.5, 1.2, 7.2, 0.07 } ;
tab_i[] = { 3,
2,
1,
0 };

tri(tab_f, 4 ) ;
tri(tab_i, 4 ) ;
}

Le p r e m i e r a p p e l à tri fait a u t o m a t i q u e m e n t référence à la
d e u x i è m e fonction tri, qui accepte un tableau de flottants
en p r e m i e r p a r a m è t r e . Le d e u x i è m e a p p e l à tri se fait natur e l l e m e n t avec la fonction tri qui s ' o c c u p e d e s entiers.
C o m m e n t le c o m p i l a t e u r fait-il la différence e n t r e plusieurs
fonctions h o m o n y m e s ?
T o u t d ' a b o r d , seules les fonctions situées d a n s la portée de
l'appel —c'est-à-dire « visibles »— sont p r i s e s en compte.
E n s u i t e , les critères suivants sont e x a m i n é s :
1. Le type ou le n o m b r e de p a r a m è t r e s . D è s q u e d e u x fonctions h o m o n y m e s diffèrent p a r le t y p e ou le n o m b r e de
l'un ou de p l u s i e u r s de leurs p a r a m è t r e s , le compilateur
reconnaît la b o n n e fonction s e l o n les types réels passés à
l'appel. C ' e s t l ' e x e m p l e q u e n o u s v e n o n s d e voir.
La surcharge

53

Attention ! Le type de retour de la fonction n ' e s t j a m a i s
pris en c o m p t e ! Par e x e m p l e , il est illégal de déclarer les
d e u x fonctions suivantes :
int
float

truc(int);
truc(int);

Le c o m p i l a t e u r ne pourrait faire la différence. En revanc h e , il est toujours possible d'écrire :
int
float

truc(int);
truc(float);

P u i s q u e , cette fois, le type des p a r a m è t r e s est différent.

* Les classes de base

2. Si l'étape 1 ne p e r m e t pas d'identifier la fonction à a p p e ler, le c o m p i l a t e u r essaie de convertir les p a r a m è t r e s réels
vers les p a r a m è t r e s des fonctions h o m o n y m e s . A i n s i , u n
c h a r o u u n s h o r t seront convertis v e r s u n int, les
float seront convertis v e r s des double, les p o i n t e u r s de
classes dérivées seront convertis en p o i n t e u r s de classes
de base*, etc.

et les classes dérivées
seront expliquées au
chaprtre 3, sur
l'héritage.

L'intérêt par
rapport au C

En cas d ' a m b i g u ï t é insoluble, le c o m p i l a t e u r signale u n e erreur. D e toutes m a n i è r e s , m i e u x v a u t éviter les cas a m b i g u s
qui sont s o u r c e d'affres éternelles.

O n pourrait croire q u e les fonctions h o m o n y m e s r e n d e n t les
p r o g r a m m e s m o i n s lisibles et plus confus. C e r t e s , si v o u s
définissez plusieurs fonctions « traite_donnees », qui font
des c h o s e s t o t a l e m e n t différentes, le gain en clarté sera plutôt
discutable.
E n r e v a n c h e , d a n s les cas o ù v o u s d i s p o s e z d ' u n j e u d e
fonctions qui fait p r a t i q u e m e n t la m ê m e c h o s e , m a i s d o n t
certains p a r a m è t r e s diffèrent, v o u s a v e z intérêt à utiliser la
s u r c h a r g e . Bref, c h a q u e fois q u e le sens des fonctions est
s e n s i b l e m e n t identique, d o n n e z - l e u r l e m ê m e n o m .
D a n s tous les c a s , le l a n g a g e C v o u s oblige à d o n n e r d e s
n o m s différents, s i m p l e m e n t p o u r q u e l e c o m p i l a t e u r reconnaisse ses petits. R e m a r q u o n s q u e d a n s l ' e x e m p l e du tri,
54

Pont entre C et C + +

n o u s p o u r r o n s aussi n o u s servir du m é c a n i s m e de la généricité (mis en œ u v r e par les templates), e x p o s é au c h a p i t r e 10.

Compléments
Surcharge
d'opérateurs

E n C + + , les o p é r a t e u r s c o u r a n t s ( + , - , = = , etc.) p e u v e n t être
s u r c h a r g é s . C o m m e n t est-ce p o s s i b l e ? En fait, q u a n d vous
utilisez un opérateur op sur un objet, le c o m p i l a t e u r génère
un appel à la fonction operator op. L e s trois écritures suiv a n t e s sont é q u i v a l e n t e s :
objet_l = objet_2 + objet_3;
objet_l = operatort (objet2, objet3);
objet_l = objet_2.(operator+(objet_3));

// (1)
// (2)

« o p e r a t o r op », où op est un o p é r a t e u r s t a n d a r d (ici +) est en
réalité u n n o m d e fonction c o m m e u n autre, qui est défini
p a r défaut p o u r les types d e b a s e d u C + + . D o n c , p o u r personnaliser le c o m p o r t e m e n t des o p é r a t e u r s , il v o u s suffit de
s u r c h a r g e r la fonction « o p e r a t o r op » correspondante,
c o m m e v o u s s a v e z déjà le faire p o u r v o s p r o p r e s fonctions.
Attention ! Q u a n d v o u s s u r c h a r g e z un o p é r a t e u r , il faut
veiller à choisir l'une ou l'autre des d e u x m é t h o d e s de
l ' e x e m p l e ci-dessus :
D a n s le cas (1), il s'agit de la fonction operator+ globale,
définie en d e h o r s de toute classe. Elle p r e n d d e u x paramètres, c o r r e s p o n d a n t aux d e u x m e m b r e s d e l'addition.
D a n s le cas ( 2 ) , il s'agit de la f o n c t i o n - m e m b r e operator+, définie d a n s la classe d'objet_2, q u i s u p p o s e que
l'objet sur lequel elle est a p p e l é e est le p r e m i e r paramètre
de l'opération d'addition (ici ob j et_2).
Si v o u s utilisiez à la fois ces d e u x m é t h o d e s de surcharge
p o u r la m ê m e fonction, le c o m p i l a t e u r ne pourrait p a s faire
la différence.
V o u s p o u v e z s u r c h a r g e r les o p é r a t e u r s s u i v a n t s :
La surcharge

+

-

*

/

+ = - = *= / =
! = < = >= &&

A

%
A

%= =
||
++

&

&=
--

~

|=
,

i

=

55

<

<< » > > = < < =
->* ->
()
[]

>

==

V o y o n s l ' e x e m p l e d ' u n p r o g r a m m e c o m p l e t redéfinissant
l'opérateur + p o u r des objets de classe C o l o r :
#include <stdio.h>
class Color
{
private :
int
rouge;
int
vert;
int
bleu;
public :
// constructeurs (voir chapitre 2)
Color(int r, int v, int b ) ;
Color();
void
Afficher));
Color
operator+ (Color Sdroite);
};

Color;:Color()
{
rouge = vert = bleu = 0 ;
}

Color::Color(int r, int v, int b)
{
rouge = r;
vert = v;
bleu = b;
)

void
{

Color::Afficher()
printf("rouge=%d vert=%d bleu=%dn",
rouge, vert, bleu);

}

* Le passage par référence est une exclusivité C++ ! Vous en
saurez plus en lisant le
chapitre 9, page 113.

Color
Color::operator+ (Color Sdroite)
// droite est passé par « référence »*
{

// fonction redéfinissant l'opérateur + :
// RESULTAT = A + B
/ / A est l'objet Color courant
// B est le paramètre "droite"
// RESULTAT est la valeur retournée
Color

résultat) (rouge + droite.rouge) / 2,
(vert + droite.vert) / 2,
(bleu + droite.bleu) / 2 ) ;
56

Pont entre C et C++

return résultat;
}

void
{

main()
Color

//
//

rouge_pur(10, 0, 0 ) ,
bleu_pur(0, 0, 10),
mixage ;

mixage = rouge_pur + bleu_pur;
cette ligne éqruivaut à :
mixage = rouge_pur.operator+ (bleu_pur)
mixage.Afficher();
// affiche "rouge=5 vert=0 bleu=5"

}

Plusieurs r e m a r q u e s :
Le p r e m i e r p a r a m è t r e d ' u n e fonction o p é r a t e u r (Color
&droite d a n s l ' e x e m p l e ) est p a s s é p a r référence (voir
chapitre 9, p a g e 1 1 3 ) . En d e u x m o t s : q u a n d v o u s p a s s e z
un p a r a m è t r e p a r référence, cela é q u i v a u t à p a s s e r l'objet
original. Ainsi, modifier un p a r a m è t r e p a s s é p a r référence
revient à modifier le p a r a m è t r e réel, utilisé à l'appel de la
fonction.
U n o p é r a t e u r r e t o u r n e u n objet d e l a m ê m e classe q u e ses
p a r a m è t r e s (ici, un objet de classe Color). V o u s devrez
d o n c créer un objet de cette classe d a n s la fonction opérateur, l'initialiser en fonction des d e u x p a r a m è t r e s de
l'opérateur, et le retourner.

P o u r q u o i passer droite par référence dans o p e r a t o r + ?
E x p l i q u o n s plutôt p o u r q u o i un p a s s a g e p a r valeur habituel
serait p r o b l é m a t i q u e . A d m e t t o n s q u e n o u s a y o n s défini
operator+ c o m m e suit :
Color
Color::operator+ (Color droite)
// droite est passé par valeur
{

// . . .
}

L e p a r a m è t r e d r o i t e étant p a s s é p a r v a l e u r , u n e copie
s ' o p è r e entre le p a r a m è t r e effectif (l'objet b l e u _ p u r ) et le
p a r a m è t r e formel utilisé d a n s l a fonction (l'objet d r o i t e ) .
La surcharge

57

D a n s c e cas, l a c o p i e n e p o s e p a s d e p r o b l è m e , m a i s si, p a r
e x e m p l e , votre classe c o m p o r t a i t un p o i n t e u r alloué, la c o p i e
d'objet p a r défaut serait m a u v a i s e .
A v e c un p a s s a g e p a r référence, il n ' y a p a s de c o p i e , p u i s q u e
l a fonction o p e r a t o r + agit d i r e c t e m e n t sur l e p a r a m è t r e effectif, b l e u _ p u r . E n d'autres t e r m e s , m a n i p u l e r l'objet
d r o i t e é q u i v a u t à m a n i p u l e r l'objet b l e u _ p u r .
Récapitulons :
Soit v o u s utilisez u n p a s s a g e p a r référence e t v o u s n ' a v e z
p a s b e s o i n de v o u s soucier de la c o p i e de v o t r e objet,
Soit v o u s utilisez u n p a s s a g e par v a l e u r e t v o u s d e v e z
vérifier q u e la copie s'effectue bien. Si n é c e s s a i r e , définissez un c o n s t r u c t e u r de c o p i e (voir p a g e 3 1 ) .
Inutile de préciser — m a i s je le fais q u a n d m ê m e — q u e la
p r e m i è r e solution est plus intéressante q u e la s e c o n d e .

Surcharge de
l'opérateur =

P o u r q u o i redéfinir l'opérateur = ?
L'affectation d ' u n objet, via l'opérateur = , est u n e o p é r a t i o n
sensible qui mérite q u ' o n s'y attarde. Si v o u s ne définissez
p a s d'opérateur = p o u r u n e classe, le c o m p i l a t e u r en g é n è r e
un p a r défaut qui effectue u n e affectation « b ê t e », d o n n é e s
membre à données membre. Bien que ce genre de comport e m e n t soit satisfaisant d a n s le cas de classes s i m p l e s , il devient parfois d a n g e r e u x si v o u s utilisez des p o i n t e u r s . Le
p r o b l è m e est similaire à celui du constructeur-copie (voir
page 31).
C o n s i d é r o n s l ' e x e m p l e suivant :
Sinclude <stdio.h>
tinclude <string.h>

Le constructeur Exclamation ainsi que la
fonction Affiche sont

class Exclamation
{
private:
char *cri ;
public :
Exclamation(char *c)
{ cri = new char[strlen(c)+1]; strcpy(cri, c ) ; }

définis « inline »

void set_cri(char * n c ) ;

(voir page 21 ). new

void Affiche()
{ printf("%sn", cri); )

et delete remplacent
malloc et free (voir
chapitre 6 page 83).

};
58

Pont entre C et C++

void
Exclamation::set_cri(char *nc)
{
// vérifions si nous n'avons pas affaire aux
// même pointeurs
if (ne == cri)
return;
// libérons la mémoire de l'ancienne valeur de cri
delete cri;
// allouons de la mémoire pour la nouvelle valeur
cri = new char[strlen(nc)+1];
// copions le paramètre dans la donnée cri.
strcpy(cri, n e ) ;
}

void
main()
{
Exclamation

beurk("Beurk"),
bof("Bof");

beurk.Affiche();
bof.Affiche();

// affiche "Beurk"
// affiche "Bof"

bof = beurk;

// operateur= défini par défaut

beurk.Affiche();
bof.Affiche();

// affiche "Beurk"
// affiche "Beurk"

bof.set_cri("Ecoeurant");
beurk.Affiche();
bof.Affiche();

/ / o n modifie bof et beurk

// affiche n'importe quoi
// affiche "Ecoeurant"

}

La c o p i e p a r défaut a ici des effets d é s a s t r e u x : en copiant
u n i q u e m e n t le p o i n t e u r cri et n o n ce qui est p o i n t é , n o u s
n o u s r e t r o u v o n s avec d e u x objets d o n t la d o n n é e cri pointe
sur la m ê m e z o n e m é m o i r e :
La surcharge

59

Le résultat de cette d é c a d e n c e ne se fait p a s attendre : il suffit
d e modifier l'un des d e u x objets p o u r q u e s o n j u m e a u soit
affecté. C ' e s t ce q u e n o u s faisons
dans
la
ligne
bof . s e t _ c r i ( " E c o e u r a n t ") ; Cette instruction libère
l ' a n c i e n n e v a l e u r d e c r i e n a p p e l a n t d e l e t e . O r , cette anc i e n n e valeur est é g a l e m e n t p a r t a g é e p a r l'objet b e u r k . S a n s
le savoir, n o u s a v o n s d o n c effacé d e u x cris au lieu d'un.
C o m m e n t redéfinir l'opérateur = ?
C e s constatations affreuses n o u s p o u s s e n t à écrire notre propre opérateur d'affectation, qui va se c h a r g e r de copier le c o n t e n u d e s p o i n t e u r s cris. R a p p e l o n s q u e l ' u s a g e de = appelle
l a fonction o p é r â t o r = .
Voici l'exemple complet :
tinclude <stdio.h>
#include <string.h>
class Exclamation
{
private:
char *cri ;
public:
Exclamation(char *c)
{ cri = new char[strlen(c)+1]; strcpy(cri, c ) ; }
void set_cri(char * n c ) ;
void Affiche()
{ printf("%sn", cri); )
Exclamation
};

&operator=(Exclamation &source);
60

Pont entre C et C++

void
Exclamation::set_cri(char *nc)
{
if (ne == cri)
return;
delete cri;
cri = new char(strlen(ne)+1];
strcpy(cri, n e ) ;
}
Cette fonction, qui
retourne une
référence, est
expliquée à la suite

Exclamation &Exclamation::operator=(Exclamation Ssource)
{
// évitons d'affecter deux objets identiques
if (this == &source)
return *this; // retournons l'objet courant

du listing.

delete cri;
cri = new char[ strlen(source.cri)+1 ];
strcpy(cri, source.cri);
return *this;
// retournons l'objet courant
}

void
main()
{
Exclamation

beurk("Beurk"), bof("Bof");

beurk.Affiche();
bof.Affiche();

// affiche "Beurk"
// affiche "Bof"

bof = beurk;

// operateur= défini par nos soins

beurk.Affiche();
bof.Affiche();

// affiche "Beurk"
// affiche "Beurk"

bof.set_cri("Ecoeurant");
beurk.Affiche();
bof.Affiche();

// on ne modifie que bof

// affiche "Beurk" : parfait !
// affiche "Ecoeurant"

}

P o u r q u o i ' r e t u r n *this'?
L ' o p é r a t e u r = doit retourner un objet de type Exclamation.
P l u s e x a c t e m e n t , i l doit retourner l'objet sur lequel o p e r a tor+ a été a p p e l é , a p r è s q u e l'affectation a été faite. En ret o u r n a n t *this, n o u s r e t o u r n o n s b i e n l'objet c o u r a n t q u e
n o u s v e n o n s d e modifier.
M a i s p o u r q u o i retourner u n e référence plutôt q u ' u n objet Exclamation tout s i m p l e ? P o u r r é p o n d r e c o r r e c t e m e n t au
cas de figure suivant :
(bof = beurk).Affiche();
La surcharge

61

Si n o u s r e t o u r n i o n s u n e s i m p l e copie du résultat, l ' o p é r a t i o n
A f f i c h e n e s'effectuerait p a s sur b o f m a i s sur s a c o p i e .
P o u r q u o i faire le test 'if (this == & s o u r c e ) ' ?
Ce test a p o u r b u t de vérifier si n o u s ne v o u l o n s p a s affecter
l'objet l u i - m ê m e (ce serait le cas de l'instruction a = a ; ). Si
c e test n'était p a s réalisé, n o u s n o u s r e t r o u v e r i o n s d a n s u n e
situation très désagréable. R e g a r d e z un instant la p r e m i è r e
ligne après l e test. O u i , c'est b i e n d e l e t e c r i . I m a g i n e z
m a i n t e n a n t q u ' u n c o q u i n ait écrit b e u r k = b e u r k . E n faisant d e l e t e c r i , n o u s effacerions l e c r i d e l'objet s o u r c e
mais aussi, sans le vouloir, celui de l'objet destination. D è s
lors, rien d ' é t o n n a n t à ce q u e n o u s n o u s e x p o s i o n s à
l ' i m p r é v u e n a c c é d a n t a u c r i d e l'objet source.

Opérateurs
de conversion
de types

L'opération réciproque, à savoir la
conversion d'un type
quelconque en un
objet de la classe, est
tout à fait possible ! Il

P o u r autoriser et contrôler la c o n v e r s i o n d ' u n e classe A v e r s
un type T q u e l c o n q u e , v o u s p o u v e z définir un o p é r a t e u r de
c o n v e r s i o n de n o m operator T ( ). C h a q u e fois q u ' u n objet
de classe A doit être c o n s i d é r é c o m m e u n e v a r i a b l e de t y p e T,
l'appel à cet o p é r a t e u r se fera a u t o m a t i q u e m e n t . Attention !
Ce g e n r e d ' o p é r a t e u r ne doit pas avoir de type de retour, m a i s
doit tout de m ê m e r e t o u r n e r u n e variable de t y p e T (on évite
ainsi l a r e d o n d a n c e d u n o m d u t y p e e t d e s r i s q u e s
d ' i n c o h é r e n c e liés à cette r e d o n d a n c e superflue).
A d m e t t o n s q u e v o u s v o u l i e z m o d é l i s e r des articles d a n s u n
m a g a s i n . La classe Article, qui contient diverses informations, doit p o u v o i r être utilisée d a n s d e s e x p r e s s i o n s de calcul de prix. Il faut d o n c définir operator float ( ) ;

suffit d'utiliser un
constructeur spécial
(voir page 35),

On ne devrait pas
spécifier la taille du
tableau directement
dans la classe, mais
plutôt utiliser une
constante. Toutes
mes excuses p o u r
cette hérésie
éhontée.

#include <string.h>
#include <stdio.h>
class Article
{
protected:
char
nom[50];
float
prix;
int
nb_disponible;
public:
Article(char *n, float p, int nb)
: prix(p), nb_disponible(nb)
{ strcpy(nom, n ) ; }
operator float();
};
62

Pont entre C et C++

Article : :operator float()
{

return prix;
}
void
main()
{
float
total;
Article b("Bonne bière", 12., 3 0 ) ;
total = 5 * b;
// conversion de b en float
printf("5 bonnes bières valent %.2f F", total);
}

// affichage : 5 bonnes bières valent 60.00 F
Les petits + du C++

Pour conclure cette première partie, attardons-nous sur les petits
changements qui rendent la vie du programmeur plus agréable.
Contrairement aux autres chapitres, celui-ci ne se décompose pas
en deux parties (notions de base et compléments). Chaque point est
expliqué en une seule fois, sans distinction de « niveau de difficulté ».

Les commentaires
V o u s êtes u n p r o g r a m m e u r c o n s c i e n c i e u x , v o u s c o m m e n t e z
donc abondamment vos programmes. Le C + + a pensé à
v o u s e t p r o p o s e u n e n o u v e l l e m a n i è r e d'introduire d e s
c o m m e n t a i r e s d a n s les c o d e s - s o u r c e s :
void

main()

{

// commentaire pertinent jusqu'à la fin de la ligne
}

L e s d e u x caractères « / / » i n d i q u e n t au p r é - p r o c e s s e u r le
d é b u t d ' u n c o m m e n t a i r e , qui s'arrête a u t o m a t i q u e m e n t à la
fin de la ligne. V o u s n ' a v e z d o n c p a s b e s o i n de r é p é t e r / /
p o u r m e t t r e fin au c o m m e n t a i r e .
64

Pont entre C et C++

B i e n e n t e n d u , v o u s p o u v e z toujours utiliser l ' a n c i e n style de
c o m m e n t a i r e (entre / * e t * / ) , très p r a t i q u e p o u r les prog r a m m e u r s qui ont b e a u c o u p de c h o s e s à r a c o n t e r — sur
p l u s i e u r s lignes. L e s d e u x styles d e c o m m e n t a i r e s peuvent
être i m b r i q u é s : u n e section de c o d e entre / * et * / p e u t contenir d e s c o m m e n t a i r e s / / , et r é c i p r o q u e m e n t .

Ruse de sioux

Il existe un autre m o y e n de m e t t r e en c o m m e n t a i r e de nomb r e u s e s lignes, très utile si ces lignes c o n t i e n n e n t déjà des
c o m m e n t a i r e s avec / * et * /. P e n s e z au p r é - p r o c e s s e u r et
utilisez # i f d e f e t # e n d i f . Voici l e listing initial :
void

fonction_boguee()

{

char

*pointeur;

// pointeur sur un truc

/* début de la fonction */
strcpy(pointeur, "Plantage");
}

V o u s v o u l e z m e t t r e en c o m m e n t a i r e tout le c o n t e n u de la
fonction. A m o i n s de s u p p r i m e r le c o m m e n t a i r e intérieur,
v o u s ne p o u v e z p a s utiliser / * et * / , p u i s q u e le p r e m i e r / *
serait fermé par le * / du c o m m e n t a i r e intérieur.
La solution :
void
{
#if 0
char

fonction_boguee()

*pointeur;

// pointeur sur un truc

/* début de la fonction */
strcpy(pointeur, "Plantage");
#endif
}

Rappelons que # i f
expression est é v a l u é p a r l e prép r o c e s s e u r . Si expression est vrai, le c o d e qui suit (jusqu'à
# e n d i f ) est c o m p i l é . Il suffit d o n c de d o n n e r u n e expression fausse p o u r m e t t r e en « c o m m e n t a i r e s » la z o n e de code
v o u l u e . C ' e s t p o u r q u o i n o u s utilisons # i f 0 qui n ' e s t jam a i s vrai.
L e s petits + du C++

65

Paramètres par défaut
Là aussi, u n e idée s i m p l e et efficace : v o u s p o u v e z d o n n e r
u n e v a l e u r p a r défaut p o u r u n o u p l u s i e u r s p a r a m è t r e s d e
fonction, d a n s la déclaration de cette fonction. A i n s i , à l'appel,
v o u s p o u v e z o m e t t r e les p a r a m è t r e s qui p o s s è d e n t u n e v a leur p a r défaut. E x e m p l e :

Ici, on donne une

/* fichier fonction.h */
float
calcul_tva(float montant, float tva = 18.6);

valeur par défaut
(18.6) au deuxième
paramètre de la
fonction calcul_tva.

/* fichier fonction.cpp */
float
calcul_tva(float montant, float tva)
{
if (tva > 0. && tva < 100.)
return montant * (tva / 100.);
else
{
/* traitement d'erreur... */ }
}

/* fichier main.cpp */
#include "fonction.h"
void

main()

{

float

res;

res = calcul_tva(100.);
// 2ème argument vaut 18.6
res = calcul_tva(100., 5.5);
res = calcul_tva(100., 18.6);
}

I l existe d e u x possibilités d ' a p p e l e r l a fonction c a l cul_tva :
A v e c d e u x p a r a m è t e s : c'est la solution c l a s s i q u e . D a n s ce
cas, le p a r a m è t r e p a r défaut est « é c r a s é » p a r la v a l e u r
d'appel du deuxième paramètre.
A v e c un seul p a r a m è t r e : d a n s ce c a s , le c o m p i l a t e u r s u p p o s e q u e l e d e u x i è m e p a r a m è t r e p r e n d l a v a l e u r p a r défaut (dans l ' e x e m p l e , 1 8 . 6 ) .
Attention : t o u s les p a r a m è t r e s qui d i s p o s e n t d ' u n e v a l e u r
p a r défaut d o i v e n t être déclarés après les p a r a m è t r e s norm a u x , c'est-à-dire à la fin de la liste des p a r a m è t r e s .
66

Pont entre C et C++

P l u s i e u r s p a r a m è t r e s par défaut
Il est p o s s i b l e de définir u n e v a l e u r par défaut p o u r plusieurs p a r a m è t r e s d'une fonction. Veillez c e p e n d a n t à les
déclarer après les p a r a m è t r e s n o r m a u x . L ' e x e m p l e suivant est
i m p o s s i b l e car a m b i g u :
void
phonquession(int a = 1, int b, int c = 3);
// erreur !

C e t t e déclaration est i m p o s s i b l e : le p r e m i e r p a r a m è t r e a une
v a l e u r p a r défaut, m a i s p a s le d e u x i è m e . P o u r q u o i n'est-ce
p a s autorisé ? C ' e s t a s s e z l o g i q u e . I m a g i n e z q u e v o u s appeliez la fonction c o m m e ceci :
phonquession(12 , 13);

M e t t e z - v o u s un instant d a n s la p e a u du p a u v r e compilateur :
q u e v e u t dire cet appel ? Q u e le p r e m i e r p a r a m è t r e vaut 12,
et le d e u x i è m e 13 ? Ou b i e n le d e u x i è m e 12 et le troisième
13 ? A l l o n s , s o y o n s r a i s o n n a b l e s , et c o n t e n t o n s - n o u s de déclarer les v a l e u r s par défaut à la file, en fin d ' e n t ê t e :
void
phonquession(int
// mieux.

b, int a = 1, int c = 3 ) ;

A i n s i , n o u s l e v o n s toute a m b i g u ï t é . Voici c o m m e n t seraient
interprêtés différents appels à p h o n q u e s s i o n :
phonquession(10);
// b:10 a:l
phonquession(10, 11);
// b:10 a:ll
phonquession(10, 11, 1 2 ) ; // b:10 a:ll

c:3
c:3
c:12
Les petits + du C++

67

Les constantes
Habitué(e) du l a n g a g e C, v o u s d e v e z c e r t a i n e m e n t utiliser
# d e f ine à tour de b r a s p o u r définir v o s c o n s t a n t e s . L e s p l u s
a u d a c i e u x d'entre v o u s a u r o n t peut-être a d o p t é le mot-clé
const ( d i s p o n i b l e d e p u i s la n o r m e ANSI du l a n g a g e C ) . Le
C + + v a p l u s loin e n é t e n d a n t les possibilités d e const. N o u s
allons voir tout cela au travers des différents m o y e n s
d'utiliser des c o n s t a n t e s e n C + + .
U n e c o n s t a n t e s i m p l e , visible d a n s un b l o c ou un seul fichier s o u r c e
C ' e s t la m a n i è r e la plus s i m p l e de déclarer u n e c o n s t a n t e :
const

float

TVA = 0.186;

Il est i n d i s p e n s a b l e d'initialiser la c o n s t a n t e au m o m e n t de sa
déclaration. On ne p e u t p a s s i m p l e m e n t déclarer const
float TVA, puis, q u e l q u e s lignes en d e s s o u s , affecter u n e
valeur à TVA. T o u t c o m p i l a t e u r C + + v o u s insultera si v o u s
agissez de la sorte.
D a n s le cas d ' u n tableau constant, ce sont tous ses é l é m e n t s
qui sont c o n s t a n t s :
const
float
tab[2] = 1.1;

tab[3] = { 2.2, 3.3, 4.4 } ;
// impossible, refusé par le compilateur

C o m m e toutes les autres variables, la p o r t é e d ' u n e c o n s t a n t e
d é p e n d de l'endroit où elle a été déclarée.
C o n s t a n t e s et p o i n t e u r s
Il faut b i e n distinguer ce qui est pointé du pointeur l u i - m ê m e .
G r â c e à const, n o u s s o m m e s en m e s u r e de spécifier au
c o m p i l a t e u r si n o u s v o u l o n s q u e le pointeur soit c o n s t a n t , ou
b i e n q u e les données pointées soient c o n s t a n t e s , ou e n c o r e q u e
les d e u x soient figés. Détaillons ces trois c a s :
68

Pont entre C et C++

char
char
char

chainel[] = "baratin";
chaine2 [ ] = "billevei/sées " ;
chaine3[] = "balivernes";

// ce qui est en gras est constant
const char *
p = chainel;
char * const p = chaine2;
const char * const p = chaine3;

// (1)
// (2)
// (3)

D a n s (1), la c h a î n e " b a r a t i n " est c o n s t a n t e , m a i s pas le
p o i n t e u r p. V o u s p o u v e z d o n c écrire p = 0 (après v o u s être
assuré q u ' u n autre p o i n t e u r d é s i g n e " b a r a t i n " , sans
quoi v o u s ne p o u v e z p l u s y a c c é d e r ) .
D a n s (2), c'est le p o i n t e u r p qui est constant. Si v o u s utilisez u n autre p o i n t e u r p o u r a c c é d e r à " b i l l e v e u s é e s " ,
v o u s p o u r r e z la modifier librement.
D a n s (3), tout est verrouillé. Ni p ni la chaîne
" b a l i v e r n e s " n e p e u v e n t être modifiés.
Illustrons u n e dernière fois ce qui est p o s s i b l e et ce qui ne
l'est p o i n t :
void

main()

{

char chainel[] = "baratin";
char chaine2[] = "billeveusées";
char chaine3[] = "balivernes";
const char *
p = chainel; // (1)
char * const p = chaine2; // (2)
const char * const p = chaine3; // (3)
pl[2] = 'X';
pl = new char[10];

// impossible
// OK

p2[2] = 'X•;
p2 = new char[10];

/ / OK
// impossible

p3[2] = 'X';
p3 = new char[10];

// impossible
// impossible

}

Pourquoi ne pas avoir déclaré les chaînes comme ceci :
const char * p = "baratin";

En c o d a n t la c h a î n e " b a r a t i n " en « d u r » c o m m e ci-dessus,
n o u s n ' a u r i o n s p a s pu la modifier. En effet, u n e chaîne dé-
Les petits + du C++

69

clarée ainsi est s t o c k é e d a n s u n e z o n e d e m é m o i r e p r o t é g é e .
T o u t accès à cette z o n e e n g e n d r e un p l a n t a g e p l u s ou m o i n s
i m m é d i a t . N o u s a v o n s d o n c été contraints d'utiliser d e s v a riables intermédiaires, qui stockent les d o n n é e s d a n s u n e
z o n e d e m é m o i r e modifiable.
U n e c o n s t a n t e s i m p l e , v i s i b l e d a n s p l u s i e u r s f i c h i e r s sources
Un petit rappel de C s t a n d a r d s ' i m p o s e . Si v o u s v o u l e z déclarer u n e variable globale visible d a n s p l u s i e u r s fichiers
sources, v o u s p r o c é d e z ainsi :

C o m m e v o u s l e v o y e z , l a variable g l o b a l e n ' e s t définie
q u ' u n e seule fois, d a n s l e fichier m a i n . c p p . C h a q u e fois
q u ' u n autre fichier v e u t p o u v o i r l'utiliser, il doit i n d i q u e r
q u e cette variable existe et est définie « ailleurs », d a n s un
autre fichier (c'est l e rôle d u m o t - c l é e x t e r n ) .
Les petits + du C++

71

D é c l a r e r q u ' u n e fonction r e t o u r n e u n e c o n s t a n t e
N e lisez p a s c e p a r a g r a p h e avant d e c o n n a î t r e les références,
e x p o s é e s a u chapitre 9 , p a g e 113. P o u r q u ' u n e fonction retourne u n e référence c o n s t a n t e , i l suffit d ' i n d i q u e r c o n s t
a v a n t le type de retour de la fonction. L'intérêt de cette
t e c h n i q u e est d'éviter la c o p i e d ' u n objet entre la fonction
a p p e l é e et la fonction appelante, tout en p r é s e r v a n t
l'intégrité de l'objet. E x e m p l e :
const int &f()
{
static big_objet big;
// manipulations sur big
return big;
}

void
main()
{
f() = 0; // déclenche une erreur de compilation
// à cause du const.
}

D é c l a r e r u n e c o n s t a n t e interne à u n e classe
C ' e s t un p r o b l è m e intéressant : c o m m e n t déclarer u n e c o n s tante qui ne sera visible et utilisable q u e d a n s sa classe ?
V o u s seriez peut-êter tenté de p r o c é d e r ainsi :
class MaClasse
{
private:
const int MAX = 100;
public :

// incorrect, hélas

// . . .
} ;

C ' e s t oublier q u e l'on ne p e u t p a s affecter de v a l e u r directem e n t d a n s l a déclaration d ' u n e classe. V o u s n e p o u v e z q u e
déclarer d e s variables, p a s les initialiser. M a l i n , v o u s v o u s dites q u e v o u s aller initialiser la c o n s t a n t e en d e h o r s de la
classe :
class MaClasse
{
private:
const int MAX;
public :
// ...

// toujours incorrect
72

Pont entre C et C++

} ;

MaClasse::MAX = 100;

// toujours incorrect

C ' e s t e n c o r e i m p o s s i b l e , car v o u s n e p o u v e z affecter a u c u n e
v a l e u r à u n e c o n s t a n t e , en d e h o r s de s o n initialisation. Et ce
n ' e s t p a s u n e initialisation ici, car MAX est u n e variable
d ' i n s t a n c e (liée à un objet précis) et n o n de classe. Elle ne
p e u t d o n c p a s être initialisée sans être s t a t i c . Q u e faire,
m e direz-vous ? I l existe d e u x solutions. U n e avec s t a t i c
c o n s t , l'autre avec enum.
La static const solution.
U n e c o n s t a n t e p r o p r e à u n e classe doit être l o g i q u e m e n t déclarée s t a t i c c o n s t . Elle doit être s t a t i c car c'est u n e
variable de classe, d o n t la valeur est c o m m u n e à tous les objets de cette classe ; et elle doit être c o n s t car on v e u t interdire les modifications de sa valeur.
L a seule m a n i è r e d'initialiser u n e variable s t a t i c c o n s t
déclarée d a n s u n e classe, c'est de l'initialiser en d e h o r s de la
classe. En voici la d é m o n s t r a t i o n :
class MaClasse
{
private:
static const int MAX;
public :
MaClasse();

// constante de classe

} ;

const int MaClasse::MAX = 100;

L ' e n u m solution
Si v o s c o n s t a n t e s sont de t y p e entier, v o u s p o u v e z utiliser
e n u m p o u r les déclarer et les initialiser à l'intérieur d ' u n e
classe. B i e n q u e cela ne m a r c h e q u e sur les entiers, cette solution p r é s e n t e l ' a v a n t a g e d'initialiser la c o n s t a n t e immédiatement. C ' e s t la seule m é t h o d e q u e je c o n n a i s s e pour
initaliser u n e c o n s t a n t e de classe d a n s le corps de la c l a s s e , et
d o n c qui p e r m e t t e de l'utiliser p o u r d i m e n s i o n n e r un tableau
d a n s la classe. V o i c i un e x e m p l e :
Les petits + du C++

class MaClasse
{
private:
enum { MAX = 100 };
int tab[MAX];
public :

73

// MAX est de type int
// OK

// ...

>;

Déclarations
E n C + + v o u s p o u v e z déclarer v o s variables o u objets
n ' i m p o r t e où d a n s le c o d e , y c o m p r i s a p r è s u n e série
d'instructions. La p o r t é e de telles variables va de l'endroit de
leur déclaration à la fin du bloc c o u r a n t ( l ' a c c o l a d e f e r m a n t e
en général).
void

main()

{

int i;
for (i=0; i<100; i++)
{
int j ;
// traitement
}

// ici j est inconnu
}

V o u s p o u v e z déclarer d a n s u n sous-bloc u n e variable portant l e m ê m e n o m q u ' u n e autre variable d é c l a r é e d a n s u n
bloc supérieur. D a n s c e c a s , l a variable l a p l u s p r o c h e d e
l'endroit de l'instruction est utilisée. E x e m p l e :
void

main()

{

int i;
for (i=0; i<100; i++)
{
int i ;
i = 1;
74

Pont entre C et C++

}

// ici i vaut 100 et pas 1
}

Variable de boucle
Voici u n e construction intéressante : v o u s p o u v e z déclarer
u n e variable de b o u c l e d i r e c t e m e n t dans l'instruction
f or ( , , ), ce qui v o u s garantit q u e p e r s o n n e n'utilisera cette
variable en d e h o r s de la b o u c l e :
void
main()
{
for (int i = 0; i < 100; i++)
{
// traitement
}
)
Résumé

La découverte du C++ est un peu déroutante, tant
les termes et concepts inédits sont nombreux. C'est
pourquoi nous vous proposons ici un résumé rapide
de la première partie du livre. Il vous permettra
d'avoir les idées claires avant d'aborder la suite, riche en
rebondissements...
Résumé première partie

Encapsulation

77

Le p r i n c i p e fondateur de la p r o g r a m m a t i o n orientée-objet est
l'encapsulation. Au lieu de r é s o u d r e un p r o b l è m e p a r d e s
structures e t des fonctions s a n s lien explicite ( c o m m e e n C ) ,
l ' e n c a p s u l a t i o n p r o p o s e d'utiliser des objets, entités inform a t i q u e s r e g r o u p a n t d o n n é e s e t fonctions i n t i m e m e n t liées.
L e s d o n n é e s d ' u n objet n e p e u v e n t être m a n i p u l é e s q u e p a r
les fonctions créées s p é c i a l e m e n t p o u r cela. E n C + + , u n objet
est u n e i n s t a n c e de classe, q u e l'utilisateur p e u t définir
c o m m e il le veut.
L e s fonctions qui m a n i p u l e n t les d o n n é e s d ' u n objet s o n t
appelées fonctions-membres.
De m u l t i p l e s objets p e u v e n t être créés à partir de la m ê m e
classe, c h a c u n c o n t e n a n t des v a l e u r s différentes. L e s objets
sont issus d ' u n e classe.
class NomClasse
{
private:
// variables et objets
public :
// fonctions de manipulation de ces variables
/ / e t objets
} ;

void

main()

{

NomClasse
NomClasse

objetl;
objet2;

objetl.nom_de_fonction_public();
}

Constructeurs
& destructeurs
* qu'il sort simplement déclaré stàtiquement ou alloué
dynamiquement par
l'opérateur new.

C h a q u e classe doit p o s s é d e r au m o i n s u n e fonction constructeur et u n e fonction destructeur. Si le p r o g r a m m e u r n ' e n définit pas, le c o m p i l a t e u r en g é n è r e p a r défaut.
Le c o n s t r u c t e u r est appelé à c h a q u e création d'objet*, et
p e r m e t a u p r o g r a m m e u r d'initialiser c o r r e c t e m e n t l'objet. L e
destructeur est a p p e l é dès q u ' u n objet sort de sa p r o p r e p o r tée o u est d é s a l l o u é e x p l i c i t e m e n t p a r u n a p p e l à d e l e t e .
78

Pont entre C et C++

Héritage

L'héritage p e r m e t de mettre en relation d e s classes. Q u a n d
u n e classe B hérite d ' u n e classe A, A est a p p e l é e classe de
b a s e et B classe dérivée.
Au n i v e a u s é m a n t i q u e , B « est u n e sorte » de A (voir e x e m ple p a g e suivante). Au n i v e a u du C + + , la classe B « recopie »
(en q u e l q u e sorte) les d o n n é e s et fonctions de la classe A. Un
objet de classe B intègre donc les d o n n é e s et fonctions accessibles de A.

La classe dérivée B p e u t redéfinir des fonctions de la classe
de b a s e A. On parle alors de redéfinition de fonction membre, ce qui signifie q u ' u n m ê m e entête de fonction est partagé p a r toute u n e hiérarchie de classes.
Résumé première partie

Surcharge

79

La surcharge de fonction p e r m e t au p r o g r a m m e u r de d o n n e r
l e m ê m e n o m à d e s fonctions situées d a n s l a m ê m e c l a s s e , o u
e n d e h o r s d e toute classe. L a seule c o n t r a i n t e est q u e l e
c o m p i l a t e u r p u i s s e faire la distinction g r â c e au n o m b r e ou au
t y p e des p a r a m è t r e s de ces fonctions « s u r c h a r g é e s ».
int

carre(int a)

{

return (a * a) ;
}

float
carre(float a)
{
return (a * a ) ;
}
Encore plus
de C++
Vous
connaissez
maintenant les grands principes
du C++. Mais votre voyage au pays des objets n'est
pas fini : il vous reste à découvrir d'autres concepts
qui enrichissent encore plus le langage. Une bonne
connaissance des éléments présentés dans la première partie facilitera votre lecture.
La fin du malloc
new et delete

Nous sommes au regret de vous annoncer le décès de malloc, survenu après de nombreuses années de bons et loyaux services... En
réalité, bien que vous puissiez toujours utiliser malloc, calloc et
free, le C++ met à votre disposition new et delete, leurs talentueux
remplaçants.

Notions de base
L'idée

L e C + + propose deux nouveaux mots-clés, new e t d e l e t e ,
p o u r r e m p l a c e r leurs aînés m a l l o c e t f r e e . P o u r q u o i u n tel
c h a n g e m e n t ? P o u r s'adapter a u x n o u v e l l e s caractéristiques
d u C + + q u e sont l'héritage, les c o n s t r u c t e u r s e t les destructeurs. E n effet, alors q u e m a l l o c e t f r e e s o n t d e u x fonctions d e l a b i b l i o t h è q u e standard d u C , n e w e t d e l e t e s o n t
c o m p l è t e m e n t intégrés au l a n g a g e C + + . Ce sont à la fois d e s
m o t s - c l é s (réservés) et d e s opérateurs.
Le rôle de n e w est d o u b l e : il r é s e r v e l ' e s p a c e m é m o i r e q u ' o n
lui d e m a n d e , p u i s l'initialise en a p p e l l a n t les c o n s t r u c t e u r s
des objets créés.
84

Pont entre C et C++

L e rôle d e d e l e t e est s y m é t r i q u e : i l a p p e l l e les d e s t r u c t e u r s
des objets créés p a r new, p u i s libère l ' e s p a c e m é m o i r e réservé.

Mise en
œuvre C++

Dans les cas cicontre, la variable pt
désigne toujours un
pointeur de T
(déclaré avec
T *pt).

L'opérateur new
C o m m e m a l l o c , l'opérateur n e w r e t o u r n e soit l ' a d r e s s e m é m o i r e n o u v e l l e m e n t allouée, soit 0 (zéro) si l'opération a
échoué.
On p e u t utiliser n e w de trois m a n i è r e s différentes :

pt = new T;
L'opérateur new recherche un espace mémoire pour un
seul é l é m e n t de type T. Ici, T p e u t d é s i g n e r un t y p e standard ( c h a r , i n t , etc.) o u u n e classe. D a n s c e dernier c a s ,
n e w appellera le c o n s t r u c t e u r par défaut de la c l a s s e T.
pt
=
new
T {arguments d'un constructeur de classe T) -,
Ici, au lieu d ' i n v o q u e r le c o n s t r u c t e u r p a r défaut de la
classe T, n e w appelera le c o n s t r u c t e u r c o r r e s p o n d a n t a u x
paramètres indiqués.
pt = new T [taille du tableau] ;
C e t t e s y n t a x e d e m a n d e à n e w de c h e r c h e r un e s p a c e m é m o i r e p o u v a n t contenir t a i 1 2 e _ t a b l e a u é l é m e n t s
consécutifs de type T (T p o u v a n t être un t y p e s t a n d a r d ou
u n e classe). Le c o n s t r u c t e u r p a r défaut de la c l a s s e T est
invoqué pour chaque élément.

A t t e n t i o n ! Détruire
un objet individuel
avec l'instruction
delete [ ] p r o v o q u e
quelque chose
d'indéfini mais de
généralement nuisible
(loi de Murphy). De
même, utiliser delete
p o u r détruire un
tableau entraine des
conséquences t o u t
aussi aléatoires !

L'opérateur delete
I l est fortement r e c o m m a n d é d ' a p p e l e r d e l e t e p o u r libérer
l ' e s p a c e - m é m o i r e alloué p a r new. L a s y n t a x e d e d e l e t e est
simple :
d e l e t e pt;
A p p e l l e le destructeur de pt (si pt est un p o i n t e u r v e r s
u n objet) p u i s libère l ' e s p a c e m é m o i r e p r é a l a b l e m e n t alloué p a r u n new.
d e l e t e [ ] pt;
M ê m e c h o s e , m a i s p o u r libérer u n tableau alloué a v e c
new T [ taille tableau ].
La fin du malloc

85

L e s spécifications officielles d u C + + v o u s g a r a n t i s s e n t q u e
rien d e m a u v a i s n'arrivera s i v o u s a p p e l e z d e l e t e s u r u n
p o i n t e u r nul.
Exemple
R e g a r d o n s m a i n t e n a n t c o m m e n t utiliser n e w e t d e l e t e
p o u r allouer et libérer des c h a î n e s de caractères. P r e n o n s
c o m m e e x e m p l e la classe CDRom, définie ci-dessous :
class CDRom
{
protected :
char
* titre;
public :
CDRom();
CDRom(char *tit);
-CDRom();
} ;

// définition des constructeurs
CDRom: :CDRom()
{

titre = new char[8];
if (titre == 0)
badaboum();
strcpy(titre, "<Aucun>");
}

CDRom::CDRom(char *tit)
{
titre = new char[ strlen(tit) + 1 ];
if (titre == 0)
badaboum();
strcpy(titre, tit);
}
// définition du destructeur
CDRom::-CDRom()
{

delete [] titre;
}

Vous constarerez que nous avons bien respecté la symétrie
n e w / d e l e t e . L a fonction b a d a b o u m s'occupe d e traiter les
erreurs, l o r s q u e n e w n ' e s t p a s p a r v e n u à allouer la m é m o i r e
demandée.
E x a m i n o n s m a i n t e n a n t c o m m e n t utiliser n e w e t d e l e t e
a v e c d e s objets. P u i s q u e n o u s l ' a v o n s s o u s l a m a i n , g a r d o n s

la classe CDRom :
86

Pont entre C et C++

void

main()

{

CDRom
CDRom

*pt_inconnu,
*pt_cd_X;
rebel_assault("Rebel Assault");

pt_inconnu = new CDRom;
pt_cd_X = new CDRom("Virtual Escort");
// blabla
delete pt_inconnu;
delete pt_cd_X;
}

// (1)
// (2)
// (3)
//
//
//
//
//
//

(4)
(5)
(6)
(7)
(8)
(9)

L e s instructions (1) et (2) n ' a p p e l l e n t a u c u n c o n s t r u c t e u r , et
ne r é s e r v e n t de la p l a c e q u e p o u r les p o i n t e u r s e u x - m ê m e s .
La ligne (3) appelle le d e u x i è m e c o n s t r u c t e u r de la c l a s s e
CDRom, et initialise l'objet rebel_assault. R i e n de n e u f ici.
En r e v a n c h e , l'instruction (4) va trouver de la p l a c e en m é m o i r e p o u r u n objet de classe CDRom, i n i t i a l i s e p a r le p r e m i e r
c o n s t r u c t e u r (le constructeur p a r défaut, p u i s q u ' i l ne p r e n d
aucun paramètre).
Vous pouvez
maintenant
utiliser
pt_inconnu p o u r appeler d e s fonctions p u b l i q u e s de la
classe CDRom. L'instruction (5) va créer un objet initialise p a r
le d e u x i è m e constructeur, et r e t o u r n e r l ' a d r e s s e de cet objet.
Enfin, les lignes (7) et (8) appellent le destructeur de la c l a s s e
CDRom p o u r détruire les objets p o i n t é s p a r pt_inconnu et
pt_cd_X. C e s destructeurs v o n t libérer les c h a î n e s de caractères allouées.

L'intérêt par
rapport au C

* C'est une possibilité
réservée aux
experts...

C o m p a r o n s les d e u x lignes s u i v a n t e s :
tab = (char *) malloc (TAILLE * sizeof(char));
tab = new char [TAILLE] ;

// C
// C + +
,

L'intérêt d u n e w devrait v o u s frapper d e plein fouet. C ' e s t
vrai : n o n s e u l e m e n t n e w est p l u s s i m p l e à utiliser, m a i s en
plus, il fait partie i n t é g r a n t e du C + + , d o n c il est p o r t a b l e et
n e n é c e s s i t e p a s l'adjonction d ' u n e librairie o u l'inclusion
d ' u n fichier d'entête. Par ailleurs, l'utilisation du n e w p e r m e t
la vérification de type (ce q u e ne p e r m e t p a s malloc), d o n c
u n e p l u s g r a n d e sûreté d u c o d e . E n outre, n e w a p p e l l e l e
c o n s t r u c t e u r v o u l u , c'est un opérateur, il p e u t être redéfini p a r
v o s soins* et il peut être hérité. Q u e v o u l e z - v o u s de p l u s ?
La fin du malloc

Que celui qui n'a pas
pesté la première fois
qu'il a découvert le
profil de malloc
se lève.

87

C e r t e s , v o u s p o u v e z toujours utiliser m a l l o c e n C + + . M a i s
d a n s c e cas, v o u s d e v r e z appeler v o u s - m ê m e les c o n s t r u c teurs v o u l u s , et, ce qui est n e t t e m e n t p l u s f â c h e u x , v o u s risq u e r e z de p a s s e r au travers de n e w spécifiques à certaines
c l a s s e s . Je m ' e x p l i q u e : si un gentil p r o g r a m m e u r réécrit un
joli n e w p o u r s a classe e t q u e v o u s i g n o r e z s u p e r b e m e n t ses
efforts e n utilisant m a l l o c , n o n s e u l e m e n t v o t r e objet risq u e r a d'être m a l initialise, m a i s , p l u s g r a v e , v o u s v o u s attirerez i n é v i t a b l e m e n t son inimitié.
Un conseil : m e t t e z - v o u s au new, v o u s ne le r e g r e t t e r e z p a s .
La fin du printf
cout, cin et cerr

Nous venons
famille printf.
nération : la
jets issus de
réutilisabilité.

d'enterrer la famille malloc, passons maintenant à
Et donnons maintenant sa chance à la nouvelle
famille cout. Notons que cout, cin et cerr sont des
classes C++, et qu'ils illustrent très bien le concept

la
géobde

Notions de base
L e C + + intègre d a n s s a librairie s t a n d a r d différents m é c a n i s m e s d ' e n t r é e s e t d e sorties. I l s'agit d e s flux c o u t , c i n e t
c e r r , c o u p l é s a u x o p é r a t e u r s > > e t < < . L ' o p é r a t e u r > > perm e t de lire un flux, alors q u e « v o u s a u t o r i s e à écrire dedans. V o i c i le rôle des trois flux s t a n d a r d s du C + + * :
90

Pont entre C et C++

t r e a m , e t c i n u n objet d e classe i s t r e a m . T o u t c e b e a u
m o n d e est défini d a n s l e fichier i o s t r e a m . h . N o u s allons
voir c o m m e n t utiliser les flux C + + e t p o u r q u o i ils s o n t p l u s
intéressants q u e c e u x d e C .

Mise en
œuvre C++

P r e n o n s un e x e m p l e s i m p l e : n o u s allons écrire un petit p r o g r a m m e qui calcule l a m o y e n n e d e s n o t e s d ' u n étudiant.
#include "iostream.h"
void
{
char
float
float
int

maint)
nom[50];
notes[3];
cumul = 0 ;
i;

cout << "Entrez le nom de l'étudiant :
cin >> nom;
for (i=0; i<3; i++)
{

cout << "Entrez la note n°" << i+1 << ' '; // (1)
cin >> notes[ i ] ;
cumul += notes[i];

N o t e z que, t o u t
}

c o m m e p o u r scanf,
vous ne pouvez pas

cout << nom << " a pour moyenne " << cumul/3. << endl;
}

entrer d'espace(s)
pendant la saisie
d'une chaîne. En fait,
p o u r être exact, vous
pouvez le faire, mais
vous rendrez v o t r e
programme
complètement fou, et

/ / C e programme affiche ceci :
// (les caractères gras sont tapés par l'utilisateur)
Entrez le
Entrez la
Entrez la
Entrez la
GaryMad a

nom de l'étudiant : GaryMad
note n°l 0.5
note n°2 7
note n°3 19.5
pour moyenne 9

vous avec.

Remarque
V o u s p o u v e z afficher p l u s i e u r s variables o u c o n s t a n t e s d a n s
l a m ê m e instruction, d u m o m e n t q u e v o u s les s é p a r e z p a r
< < . C ' e s t l e cas e n ( 1 ) , d a n s l a p r e m i è r e instruction d e l a
b o u c l e , o ù n o u s affichons u n e c h a î n e , p u i s l'entier i , p u i s l e
caractère e s p a c e . C e t t e r e m a r q u e est é g a l e m e n t v a l a b l e p o u r
les lectures a v e c c i n e t > > .
La fin du printf

91

M i s e en forme automatique
V o u s n o t e r e z q u e v o u s n ' a v e z pas b e s o i n d e préciser l e form a t d'affichage, celui étant défini p a r défaut p o u r les types
d e b a s e d e C . Si, n é a n m o i n s , v o u s s o u h a i t e z a p p l i q u e r d e s
contraintes sur l'affichage ( n o m b r e de chiffres d é c i m a u x
d ' u n réel par e x e m p l e ) , inutile de revenir à printf, cout
sait le faire très bien. R e p o r t e z v o u s a u x c o m p l é m e n t s de ce
chapitre p o u r savoir c o m m e n t .
Sans adresse
V o u s a v e z noté l'abscence de & d a n s la s y n t a x e du cin. Ce
dernier n ' a p a s b e s o i n de connaître l'adresse de la variable à
lire, c o m m e c'était l e cas p o u r s c a n f .

L'intérêt par
rapport au C

L'intérêt principal de cout, cin, cerr, << et >> est la vérification de type. En effet, c h a q u e objet est affiché en fonction
de sa classe ; il n ' y a p a s de r i s q u e de confusion ou de quip r o q u o . L e s fonctions printf et c o m p a g n i e s o n t i n c a p a b l e s
de vérifier le type des d o n n é e s q u ' e l l e s traitent (ce qui accroît
les r i s q u e s d'erreurs). P l u s j a m a i s les c h a î n e s de c a r a c t è r e s ne
seront affichées c o m m e d e v u l g a i r e s entiers, j e v o u s l e p r o m e t s ...
Par ailleurs, u n e s i m p l e c o m p a r a i s o n de lignes suffit p o u r
q u e la vérité a p p a r a i s s e de m a n i è r e éclatante à l ' h o n n ê t e
h o m m e : cout et ses frères sont r e m a r q u a b l e m e n t p l u s
s i m p l e s à utiliser q u e leurs a i n e s d e la famille printf.
// C standard (infâme, n'est-ce pas ?)
printf("%s%d%c%fn", "Voici i:", entier, '+', réel);
scanf("%f", &reel);
// C++ (mieux, tout de même !)
cout << "Voici i:" << entier << '+' << réel << endl;
cin >> réel;

M a i s l'intérêt ne se limite p a s là : v o u s p o u v e z surtout redéfinir les opérateur << et » p o u r c h a c u n e de v o s c l a s s e s !
A i n s i , les o p é r a t i o n s d ' e n t r é e s / s o r t i e s g a r d e r o n t toujours la
m ê m e s y n t a x e , i n d é p e n d a m m e n t d e s classes utilisées ! Par
e x e m p l e , u n e classe FilmHorreur p o u r r a afficher le n o m
du film et s o n réalisateur, opération réalisée de la m ê m e
m a n i è r e q u e p o u r un entier :
92

Pont entre C et C++

Utilisateurs de MSD O S , prenez garde !
Le n o m de fichier

#include "FilmHorreur.h"
void

// voir dans la marge

main()

{

utilisé dans l'include

FilmHorreur

fait plus de huit
caractères !

film("Doume", "Aïdi software");

cout << film;
}

On s u p p o s e b i e n e n t e n d u q u e la classe est définie, et que
« l'on a fait ce qu'il faut » p o u r q u e l ' o p é r a t e u r « prenne en
c o m p t e l'affichage de la classe F i l m H o r r e u r . V o u s allez
a p p r e n d r e c o m m e n t réaliser ce petit m i r a c l e d a n s les comp l é m e n t s d e c e chapitre.

Compléments
Surcharger
« et »
Avant de lire ce
complément, vous
devez connaître les
fonctions amies
(page 123), ainsi que
les principes de la
surcharge, développés
page 5 1 .

V o u s p o u v e z s u r c h a r g e r les o p é r a t e u r s < < e t > > p o u r affic h e r v o s p r o p r e s objets. En d'autres t e r m e s , il faut que les
fonctions operator<< e t o p e r a t o r » s a c h e n t quoi faire
l o r s q u ' e l l e s sont a p p l i q u é e s à des objets de v o t r e classe.
A v a n t de p o u r s u i v r e les e x p l i c a t i o n s , un petit rappel
s ' i m p o s e : q u a n d on utilise un o p é r a t e u r @, cela revient à app e l e r la fonction operator® c o r r e s p o n d a n t e . V o i l à ce que
cela d o n n e p o u r + :
int résultat, a = 1, b = 1;
résultat = a + b;
// équivaut à
résultat = operatort (a, b ) ;

T r a n s p o s o n s l ' e x e m p l e ci-dessus à « :
NomClasse

mon_objet;

cout << mon_objet;
// équivaut à
operator<< (cout, mon__objet);

Il s'agit ici de l'opérateur << global. R a p p e l o n s q u e cout et
cin sont r e s p e c t i v e m e n t d e s objets de classe ostream et
La fin du printf

93

istream. P o u r s u r c h a r g e r « , i l suffit d o n c d'écrire l a fonction hors-classe suivante :
ostream operator<< (ostream, NomClasse)

La fonction à s u r c h a r g e r est identifiée. Il ne reste p l u s q u ' à
l'écrire. Un p r o b l è m e se p o s e alors : c o m m e n t a c c é d e r a u x
d o n n é e s de l'objet de classe NomClasse ? D e u x solutions :
p a s s e r p a r les fonctions m e m b r e s public d ' a c c è s a u x d o n n é e s , ou b i e n déclarer d a n s la classe NomClasse q u e la
fonction operator<< est u n e amie. D a n s ce dernier c a s , o p e r a t o r « p e u t a c c é d e r l i b r e m e n t a u x d o n n é e s private e t

protected des objets de NomClasse. Ce qui d o n n e :
Vous trouverez page
176 un exemple de
surcharge de << qui
ne déclare pas de
fonction amie.

#include <iostream.h>
class NomClasse
{
private:
int n;
public :
NomClasse(int i) : n(i) {}
int
get_n() const { return n; }
// déclarons que l'opérateur global << est
// un ami de notre classe (NomClasse) :
friend ostreamS operator<<
(ostream& out, const NomClasse& obj);
};

Nous utilisons ici des
passages par

// surchargeons l'opérateur global << pour qu'il
// sache traiter des objets de classe NomClasse :
ostream&
operator<< (ostreamk out,
const NomClasse& obj)
{

référence (voir

// out est l'objet comparable à cout
// obj est l'objet à afficher
return out << '[' << obj.n << ' ] ' << endl;

chapitre 9, page I 13).
}

void
{

maint)

NomClasse

objet(1);

cout << objet;
}
/*

Pour redéfinir >>, utilisez :
istream& operator>> (istreamk in, NomClasse& obj);
94

Pont entre C et C++

*/

// affichage:
// [1]

L a fonction o p e r a t o r < < r e t o u r n e u n e référence sur u n objet
d e classe o s t r e a m , p o u r q u e v o u s p u i s s i e z utiliser d e s instructions à la file :
cout << objl << obj2;
// équivaut à :
// operator<< (operator<<( cout, objl), obj2);

D a n s l a ligne ci-dessus, o p e r a t o r < < ( c o u t , o b j l ) doit
être l u i - m ê m e l'objet d e classe o s t r e a m qui v i e n t d'être appelé ; c'est p o u r cela q u e n o u s r e t o u r n o n s u n e référence v e r s
cet objet.

Formater
les sorties
Ces formatages o n t
été vérifiés sur le
compilateur
T u r b o C + + p o u r PC,
mais devraient fonct i o n n e r sur tous les
autres compilateurs.
* sauf contre indicat i o n dans le texte.

L e s indications de formatage (présentation) d e s sorties doiv e n t être p a s s é e s à c o u t d e l a m ê m e m a n i è r e q u e les d o n n é e s à afficher, à savoir avec l'opérateur < < .
Q u a n d v o u s utilisez u n e instruction d e f o r m a t a g e , v o u s dev e z inclure au d é b u t de votre c o d e le fichier i o m a n i p . h en
plus d u fichier i o s t r e a m . h .
L e s p a r a g r a p h e s qui suivent d o n n e n t des e x e m p l e s p o u r
c h a q u e type d e f o r m a t a g e différent. V o u s t r o u v e r e z e n s u i t e
un tableau récapitulatif de toutes ces fonctions.
Attention : toutes ces instructions de formatage* restent v a lables p o u r tous les c o u t qui les suivent d a n s l e p r o g r a m m e .
A f f i c h e r au m o i n s n caractères
L'identificateur s e t w ( n ) force c o u t à afficher a u m o i n s n
caractères, et plus si nécessaire, s e t w est l'abbréviation de set
zuidth (« spécifie la largeur »). Par défaut les affichages s o n t
c a d r é s à droite. E x e m p l e :
#include "iostream.h"
#include "iomanip.h"
void

main()

{

cout << setw(3) << 1 << endl;
cout << 1.2345 << endl;
}
La fin du printf

95

// affiche :
1
1.2345

P o u r q u e c o u t affiche autant d e caractères q u e n é c e s s a i r e ,
spécifiez u n e l o n g u e u r m i n i m a l e d e 0 ( s e t w ( 0 ) ) . C ' e s t
d'ailleurs la valeur par défaut.
A l i g n e r les sorties à g a u c h e
Utilisez ici l e barbare s e t i o s f l a g s ( i o s : : l e f t ) . E x e m ple :
#include "iostream.h"
finclude "iomanip.h"
void

main()

{

cout << setw(5) << "Wax" << endl;
//(l)
cout << setiosflags(ios::left) << "Wax" << endl; 11(2)
II setw(5) est toujours valable ici !
}

// affiche :
Wax
Wax

La l i g n e (1) i n d i q u e qu'il faut afficher au m o i n s cinq caractères. Par défaut, l ' a l i g n e m e n t se fait à droite, d ' o ù la p r e m i è r e
ligne affichée (deux e s p a c e s et les trois lettres).
La ligne (2) p r é c i s e qu'il faut aligner le texte à g a u c h e . R a p p e l e z - v o u s q u e la c o n s i g n e d'afficher au m o i n s cinq caractères est toujours valable. Cette fois-ci, les trois lettres de
« W a x » sont affichées, suivies de d e u x e s p a c e s .
A l i g n e r les s o r t i e s à droite
C ' e s t l e c o m p o r t e m e n t p a r défaut d e c o u t . S i v o u s v o u l e z l e
rétablir, utilisez s e t i o s f l a g s ( i o s : : r i g h t ) — v o i r p a r a g r a p h e précédent.
A f f i c h e r n chiffres après la v i r g u l e
Utilisez s e t p r e c i s i o n ( n ) :
#include "iostream.h"
#include "iomanip.h"
void

main()
96

Pont entre C et C++

{

cout << setprecision(2) << 1.23456 << endl;
}

// affiche :
1.23

Afficher les zéros après la virgule
Utilisez s e t i o s f l a g s ( i o s : : s h o w p o i n t ) .
#include "iostream.h"
#include "iomanip.h"
void

main()

{

cout << setw(5) << setiosflags(ios::showpoint)
<< 1.2 << endl;
}
// affiche :
1.200

Ne pas afficher les zéros après la virgule
C ' e s t l a fonction r é c i p r o q u e d e l a p r é c é d e n t e . Utilisez s e tiosf lags(ios::showbase)
:
#include "iostream.h"
#include "iomanip.h"
void

main()

{

cout << setw(5) << setiosflags(ios::showpoint)
<< 1.2 << endl;
cout << setiosflags(ios::showbase) << 1.2 << endl;
}

// affiche :
1.200
1.2

Afficher u n ' + ' p o u r les n o m b r e s positifs
Utilisez s e t i o s f l a g s ( i o s : : s h o w p o s ) :
#include "iostream.h"
#include "iomanip.h"
void
{

maint)
La fin du printf

97

cout << setiosflags(ios::showpos) << 1.2 << endl;
}
// affiche :
+ 1.2

R e m p l a c e r le caractère blanc par un autre caractère
N o u s p a r l o n s ici d e s e s p a c e s affichés p o u r c o m b l e r les v i d e s .
P a r e x e m p l e , si v o u s a v e z spécifié s e t w ( 5 ) et q u e v o u s affic h e z le chiffre 1, quatre e s p a c e s seront affichés en p l u s . Si
v o u s n ' a i m e z p a s les e s p a c e s , utilisez s e t f i l l ( c a r ) p o u r
les r e m p l a c e r p a r un caractère de votre c h o i x :
#include "iostream.h"
#include "iomanip.h"
void
main()
{
cout << setfill('-') << setw(5) << 1 << endl;
}

// affiche :
1

A f f i c h e r les n o m b r e s d a n s u n e autre b a s e
S o n t définies trois b a s e s : l'octal (base 8 ) , le d é c i m a l (par défaut) et l ' h e x a d é c i m a l (base 16). il suffit de spécifier
l'abréviation ( o c t , d e c , h e x ) p o u r q u e tous les affichages
suivants se fassent d a n s cette b a s e :
#include "iostream.h"
#include "iomanip.h"
void

main()

{

cout << hex << 12 << endl;
}

// affiche :
c

A f f i c h e r les n o m b r e s e n n o t a t i o n s c i e n t i f i q u e
Un rappel p o u r les n o n - m a t h e u x s ' i m p o s e : la n o t a t i o n
scientifique est de la forme n Ee, où n est un n o m b r e réel entre 0 et 1 n o n inclus, et où e est é g a l e m e n t un n o m b r e réel.
98

Pont entre C et C++

U n e telle n o t a t i o n r e p r é s e n t e le n o m b r e n m u l t i p l i é p a r 10
p u i s s a n c e e. P o u r formater l'affichage en n o t a t i o n scientifiq u e , utilisez s e t i o s f l a g s ( i o s : : scientific). E x e m ple :
#include "iostream.h"
#include "iomanip.h"
void

main()

{

cout << setiosflags(ios::scientific) << 12.3 << endl;
}

// affiche :
.123 e2

A f f i c h e r l e s n o m b r e s e n n o t a t i o n fixe
C ' e s t l'affichage p a r défaut d e s n o m b r e s , il s'obtient en utilisant s e t i o s f l a g s ( i o s : : fixed). E x e m p l e :
#include "iostream.h"
#include "iomanip.h"
void

main()

{

cout << setiosflags(ios::scientific) << 12.3 << endl;
cout << setiosflags(ios::fixed) << 12.3 << endl;
}
// affiche :
.123 e2
12 .3
La fin du printf

Tableau
récapitulatif

99

Ce tableau r e p r e n d les différentes options d'affichage valables pour c o u t .

Effet voulu

cout « ...

au moins n caractères

setw(n)

aligner à gauche

setiosflags(ios::left)

aligner à droite

setiosflags(ios::right)

n caractères après la virgule

setprecision(n)

afficher les 0 après la virgule

setiosf lags(ios: :showpoint)

ne pas afficher les 0 après la virgule

setiosflags(ios::showbase)

'+' devant les nombres positifs

setiosf lags(ios: :showpos)

changer le caractère de remplissage

setfill(c); // c est un char

afficher en décimal, octal ou hexadécimal

dec, oct ou hex

notation scientifique

setiosf lags(ios::scientific)

notation à virgule fixe

setiosflags(ios::fixed)
Le polymorphisme
et la virtualité

Vous connaissez déjà les vertus de l'héritage. Découvrez maintenant celles du polymorphisme, mis en œuvre par la virtualité en
C++. Si vos notions sur l'héritage sont encore un peu floues, vous
gagnerez à relire le chapitre 3 avant d'aborder celui-ci.

Notions de base
L'idée

Le p r i n c i p e de p o l y m o r p h i s m e p e u t s ' a p p a r e n t e r à u n e surc h a r g e e t u n e redéfinition d e f o n c t i o n - m e m b r e é t e n d u e .
R a p p e l o n s q u e la surcharge consiste à p o u v o i r d o n n e r le
m ê m e n o m à p l u s i e u r s fonctions ; le c o m p i l a t e u r faisant la
différence grâce aux p a r a m è t r e s . La redéfinition de fonctionmembre a p p l i q u e ce m ê m e p r i n c i p e à travers u n e arboresc e n c e d'héritage, e t p e r m e t e n p l u s d e d o n n e r e x a c t e m e n t l e
m ê m e entête d e fonction.
Le p o l y m o r p h i s m e va p l u s loin : il p e r m e t de trouver dynamiquement à quel objet s ' a d r e s s e u n e fonction, p e n d a n t
l ' e x é c u t i o n d u p r o g r a m m e . A l o r s q u e l a redéfinition d e
f o n c t i o n - m e m b r e d é t e r m i n a i t cela à la c o m p i l a t i o n , c'est-àdire de m a n i è r e statique, le p o l y m o r p h i s m e m è n e s o n en-
202

Pont entre C et C++

quête et va jusqu'à retrouver la classe réelle d'un objet pointé.
Limites de la redéfinition de fonction-membre
Reprenons l'exemple d'une gestion de textes bruts et mis en
forme. Si l'on utilise un pointeur de T e x t e B r u t qui pointe
réellement vers un T e x t e M i s E n F o r m e , la redéfinition de
fonction-membre seule montre ses limites :
#include <iostream.h>
class TexteBrut

{

|
public:
void

j
Imprimer(int n b ) ;

} ;

void
TexteBrut::Imprimer(int nb)
{
cout << "Imprimer de la classe TexteBrut" << endl;

;

}

class TexteMisEnForme : public TexteBrut

// héritage

{

!

I
public:
void

!
Imprimer(int n b ) ;

} ;

void
TexteMisEnForme::Imprimer(int nb)
{
cout << "Imprimer de la classe TexteMisEnForme"
< < endl;

i

}

|

void
main()
{
TexteBrut
TexteMisEnForme

i
i

}

i

*txt;
joli_t;xt;

txt = &joli_txt;
txt -> Imprimer(1);

// affichage :
// Imprimer de la classe TexteBrut

j

i
i

Ce n'est pas le résultat espéré : il faudrait que la fonction Imp r i m e r définie dans la classe T e x t e M i s E n F o r m e soit appelée, car l'objet réellement pointé appartient à cette classe. Le
polymorphisme permet justement de résoudre ce problème.
Le polymorphisme et la virtualité

Mise en
œuvre C++

103

L e p o l y m o r p h i s m e est m i s e n œ u v r e p a r l a virtualité e n C + + .
R e p r e n o n s l ' e x e m p l e p r é c é d e n t . Il suffit d'ajouter le m o t - c l é
virtual d e v a n t l'entête de la fonction Imprimer, d a n s la
classe TexteBrut. Ce qui d o n n e :
#include <iostream.h>
class TexteBrut
{
public :
virtual void

Imprimer(int n b ) ;

} ;

// ... le reste est identique à l'exemple précédent ...
void
main()
{
TexteBrut
TexteMisEnForme

*txt;
joli_txt;

txt = &joli_txt; // txt pointe sur un TexteMisEnForme
txt -> Imprimer(1);
}
// affichage :
// Imprimer de la classe TexteMisEnForme

E x a m i n o n s le rôle du mot-clé virtual d a n s n o t r e petite architecture de classes.
Le rôle d'une fonction virtuelle
Q u a n d l e c o m p i l a t e u r r e n c o n t r e d e s f o n c t i o n s - m e m b r e s virtuelles, il sait qu'il faut attendre l ' e x é c u t i o n du p r o g r a m m e
p o u r savoir quelle fonction appeler. Il d é t e r m i n e r a en t e m p s
v o u l u la bonne fonction, c'est-à-dire celle qui est définie d a n s
la classe de l'objet réel a u q u e l la fonction est a p p l i q u é e .
M a i s r e v e n o n s à n o t r e e x e m p l e p o u r illustrer c e s p r o p o s : le
petit b o u t de m a i n ( ) a b e a u être petit, il n ' e n est p a s m o i n s
très riche en e n s e i g n e m e n t s . L'utilisation d ' u n p o i n t e u r sur
classe TexteBrut m é r i t e q u e l q u e s é c l a i r c i s s e m e n t s .
204

Pont entre C et C++

P o i n t e u r s sur u n e c l a s s e d e b a s e
* O u i oui, en C c'est
possible, car le C est
un grand laxiste. Mais
en C + + , le contrôle
des types est plus serré, et vos incartades
ne passeront plus :
finies les convertions
automatiques
hasardeuses de
truc_machin vers
bidule_chose. De la
discipline, que diable !

Résumé
* C'est-à-dire m ê m e
n o m b r e et mêmes
types d'arguments.

* Pour la clarté du
listing, on pourra
répéter le mot-clé
virtual devant chaque
fonction concernée,
t o u t au long de la
hiérarchie.

L'intérêt par
rapport au C

En temps normal, quand vous déclarez un pointeur de type
A, vous ne pouvez pas le f i e pointer sur une donnée de
ar
type B*. En tous cas, cela devrait être contraire à votre éthique. M a i s i i nous sommes dans une situation spéciale :
c,
nous déclarons un pointeur sur une classe de base. D a n s ce
cas précis, et le C + + nous y autorise, vous pouvez faire point r ce pointeur vers une classe dérivée de cette classe de base.
e
C'est exactement ce que nous faisons dans l'avant-dernière
ligne du l s i g
itn.
La ligne qui suit (txt -> Imprimer ( 1 ) ) devrait donc f i e
ar
appel à la fonction Imprimer définie dans la classe TexteBrut, puisque txt est un pointeur sur TexteBrut. M a i s
NON, puisque le C + + aura vu que txt pointe maintenant sur
joli_txt, objet de classe TexteMisEnForme. C e t t e ligne
appelera donc la fonction Imprimer de la classe TexteMisEnForme.
D a n s u n e hiérarchie de classe, p o u r déclarer des fonctions
qui o n t l e m ê m e n o m e t les m ê m e s a r g u m e n t s * e t d o n t les
appels sont résolus d y n a m i q u e m e n t , il faut qu'elles soient
virtuelles. Il suffit de déclarer u n e fonction virtuelle d a n s la
classe de b a s e p o u r q u e toutes les fonctions i d e n t i q u e s des
classes dérivées soient elles aussi virtuelles*.
P e n d a n t l'exécution, la fonction appelée d é p e n d de la classe
de l'objet qui l'appelle (et n o n du type de variable qui pointe
sur l'objet). D a n s le cas d'un pointeur sur u n e classe qui appelle u n e fonction virtuelle, c'est la classe de l'objet pointé
réellement par ce pointeur qui d é t e r m i n e la fonction a p p e l é e .
C e m é c a n i s m e c o m m u n à d e n o m b r e u x l a n g a g e s orientésobjets est appelé polymorphisme. La virtualité est sa m i s e en
œuvre C + + .
N o u s avions déjà v u q u ' a v e c l'héritage, l e C + + proposait
q u e l q u e c h o s e de très intéressant, totalement a b s e n t du C.
G r â c e a u p o l y m o r p h i s m e , v o u s d i s p o s e z d ' u n m o y e n nouv e a u , d ' u n e s o u p l e s s e et d ' u n e p u i s s a n c e é t o n n a n t e : utiliser
des fonctions h o m o n y m e s , d o n t les a r g u m e n t s ont l e m ê m e
profil, tout au l o n g d ' u n e hiérarchie de classe, en bénéficiant
de la r é s o l u t i o n d y n a m i q u e des appels.
Le polymorphisme et la virtualité

Rions ensemble. Un
utilisateur de
Macintosh déclarait
récemment :
« Windows p o u r PC,
c'est mettre du rouge

105

E x e m p l e : v o u s d é v e l o p p e z un e n v i r o n n e m e n t g r a p h i q u e .
V o u s le baptisez Fenêtres pour Groupes de Travail 96*. G r â c e à
la virtualité, c h a q u e objet g r a p h i q u e de v o t r e s y s t è m e
(bouton, m e n u déroulant, liste, etc.) p o s s é d e r a u n e fonctionm e m b r e Afficher, a v e c les m ê m e s p a r a m è t r e s . Il v o u s sera
p o s s i b l e d'écrire q u e l q u e c h o s e c o m m e :

à lèvre à un poulet »

ObjetGraphiqueGenerique
*obj[10];
// tableau de 10 pointeurs
// ... affectation d'objets divers aux pointeurs du tableau
// (nous ne détaillons pas cette partie)
for (i = 0; i < 10; i++)
{
obj[i] -> Afficher(paramètres);
}

V o u s n ' a v e z plus à v o u s soucier du t y p e e x a c t de l'objet grap h i q u e p o i n t é p a r obj [ i ] , p u i s q u e l a b o n n e fonction Aff i c h e r sera a p p e l é e a u t o m a t i q u e m e n t .
En C, il aurait fallu p a s s e r p a r d e s p o i n t e u r s sur void*, définir p l u s i e u r s structures figées c o n t e n a n t d e s p o i n t e u r s sur
d e s fonctions, e t autres bidouilles p e u c a t h o l i q u e s . U n e autre
solution consisterait à utiliser des s w i t c h , m a i s cela rendrait
l ' e x t e n s i o n du c o d e existant très é p i n e u s e . Le C n ' é t a n t p a s
c o n ç u p o u r cela, v o u s v o u s e x p o s e r i e z à de m u l t i p l e s prob l è m e s de portabilité, de fiabilité, de m a i n t e n a n c e . C a r la v é ritable force d e frappe d u C + + , c'est l'héritage. S e u l
l'héritage, et s o n c o m p l i c e la virtualité, p e r m e t t e n t de m e t t r e
e n œ u v r e des s y s t è m e s c o m p l e x e s e t — r e l a t i v e m e n t — facile
à modifier.
Le polymorphisme et la virtualité

Rions ensemble. Un
utilisateur de
Macintosh déclarait
récemment :
« Windows p o u r PC,
c'est mettre du rouge

105

E x e m p l e : v o u s d é v e l o p p e z un e n v i r o n n e m e n t g r a p h i q u e .
V o u s le baptisez Fenêtres pour Groupes de Travail 96*. G r â c e à
la virtualité, c h a q u e objet g r a p h i q u e de v o t r e s y s t è m e
(bouton, m e n u déroulant, liste, etc.) p o s s é d e r a u n e fonctionm e m b r e A f f i c h e r , a v e c les m ê m e s p a r a m è t r e s . I l v o u s sera
p o s s i b l e d'écrire q u e l q u e c h o s e c o m m e :

à lèvre à un poulet »

ObjetGraphiqueGenerique
*obj[10];
// tableau de 10 pointeurs
// ... affectation d'objets divers aux pointeurs du tableau
// (nous ne détaillons pas cette partie)
for (i = 0; i < 10; i++)
{
obj[i] -> Afficher(paramètres) ;
)

V o u s n ' a v e z plus à v o u s soucier du t y p e e x a c t de l'objet grap h i q u e p o i n t é p a r o b j [ i ] , p u i s q u e l a b o n n e fonction A f f i c h e r sera a p p e l é e a u t o m a t i q u e m e n t .
E n C , i l aurait fallu p a s s e r p a r d e s p o i n t e u r s sur v o i d * , définir p l u s i e u r s structures figées c o n t e n a n t d e s p o i n t e u r s sur
d e s fonctions, e t autres bidouilles p e u c a t h o l i q u e s . U n e autre
solution consisterait à utiliser des s w i t c h , m a i s cela rendrait
l ' e x t e n s i o n du c o d e existant très é p i n e u s e . Le C n ' é t a n t p a s
c o n ç u p o u r cela, v o u s v o u s e x p o s e r i e z à de m u l t i p l e s p r o b l è m e s de portabilité, de fiabilité, de m a i n t e n a n c e . C a r la v é ritable force d e frappe d u C + + , c'est l'héritage. S e u l
l'héritage, et s o n c o m p l i c e la virtualité, p e r m e t t e n t de m e t t r e
e n œ u v r e des s y s t è m e s c o m p l e x e s e t — r e l a t i v e m e n t — facile
à modifier.
106

Pont entre C et C++

Compléments
Destructeurs
virtuel

Il est tout à fait p o s s i b l e de définir un d e s t r u c t e u r virtuel
p o u r u n e classe. A quoi sert un d e s t r u c t e u r virtuel ? A détruire l'objet r é e l l e m e n t pointé, et n o n un objet i m a g i n a i r e
qui aurait l e m ê m e type q u e celui d u p o i n t e u r . U n petit
exemple :
#include <iostream.h>
class Véhicule
{
public :
virtual -Véhicule!)
{ cout << "-Véhicule" << endl; }
>;

class Camion : public Véhicule
{
public :
virtual -Camion()
{ cout << "-Camion" << endl; }
};

void main()
{
Véhicule *pt_inconnu;
Camion
*mon_beau_camion = new Camion;
pt_inconnu = mon_beau_camion;
delete pt_inconnu;
}

// affichage :
/ / -Camion
•
// -Véhicule

Le delete en dernière ligne appelle le d e s t r u c t e u r de Camion (puis le d e s t r u c t e u r de Véhicule, car les d e s t r u c t e u r s
s o n t a p p e l é s en série de la classe d é r i v é e à la classe de b a s e ) .
Si les d e s t r u c t e u r s n ' é t a i e n t p a s virtuels, seul le d e s t r u c t e u r
de Véhicule aurait été a p p e l é .
P o u r e x p l i q u e r cela a u t r e m e n t , pt_inconnu est d é c l a r é
c o m m e un p o i n t e u r sur Véhicule, m a i s p o i n t e en réalité sur
un objet de classe Camion. C o m m e les d e s t r u c t e u r s s o n t ici
virtuels, le p r o g r a m m e a p p e l l e le destructeur qui à la classe
de l'objet r é e l l e m e n t pointé, c'est-à-dire Camion.
Le polymorphisme et la virtualité

Classes
abstraites et
fonctions
virtuelles pures

107

Le concept de classe abstraite (ou classe abstraite de base) est
c o m m u n à de n o m b r e u x l a n g a g e s orientés-objets. De quoi
s'agit-il ?
U n e classe est dite abstraite si elle est c o n ç u e d a n s le b u t de
servir de c a d r e g é n é r i q u e à ses classes dérivées. De p l u s , u n e
classe abstraite ne p e u t e n g e n d r e r aucun objet. C ' e s t un p o i n t
capital, qui p e r m e t de s o u l i g n e r q u e la raison d'être d ' u n e
classe abstraite réside d a n s ses classes dérivées.
Fonctions virtuelles pures
L e C + + autorise l a m i s e e n œ u v r e des classes abstraites, p a r
l e biais d e f o n c t i o n s - m e m b r e s virtuelles p u r e s . E n C + + , u n e
classe est déclarée abstraite q u a n d elle contient au moins une
fonction virtuelle pure.
U n e fonction virtuelle p u r e est u n e f o n c t i o n - m e m b r e d o n t
seul le profil (valeur retournée, n o m et p a r a m è t r e s ) est défini. On déclare u n e fonction virtuelle en ajoutant « = 0; »
après l'entête d e l a fonction, e n plus d u m o t - c l é v i r t u a l .
Conséquences
Q u a n d v o u s déclarez u n e fonction virtuelle p u r e d a n s u n e
classe, voilà ce qui se p a s s e :
la classe est décrétée « classe abstraite » : elle ne p e u t d o n c
pas être utilisée p o u r créer un objet.
la fonction virtuelle p u r e ne peut être définie : elle n ' a
q u ' u n profil, u n entête, m a i s pas d e c o r p s
les classes futures qui hériteront de cette classe abstraite
devront définir les f o n c t i o n s - m e m b r e s virtuelles p u r e s de
la classe abstraite.
I n t é r ê t du v i r t u e l p u r
L'intérêt d e s fonctions virtuelles p u r e s et d e s c l a s s e s abstraites de b a s e , c'est de s'assurer q u e les classes d é r i v é e s r e s p e c teront l'interface d e votre classe. V o u s i m p o s e z u n e n t ê t e
p o u r u n e fonction g é n é r i q u e , et v o u s forcez les classes dériv é e s à redéfinir cette fonction. C ' e s t un m o y e n s u p p l é m e n taire d e garantir u n e b o n n e h o m o g é n é i t é d e votre
architecture de classes.
108

Pont entre C et C++

E x e m p l e court
D a n s le listing qui suit, n o u s déclarons la f o n c t i o n - m e m b r e
• a f f i c h e r virtuelle p u r e , e n ajoutant = 0 après s o n entête.
N o u s n e p o u v o n s d o n c p a s créer d'objet d e cette classe. E n
r e v a n c h e , n o u s p o u v o n s créer d e s objets de la classe Dériv é e qui définit l a fonction a f f i c h e r .
class AbstraiteBase
{
protected : // blabla
public :
virtual void afficher(int x, int y) = 0;
// c'est ce « = 0 » qui rend la fonction
// virtuelle pure
} ;

class Dérivée :
{
protected :
public :
virtual
//

public AbstraiteBase
// blabla
void afficher(int x, int y ) ;
(virtuelle simple)

} ;

void

Dérivée::afficher(int x, int y)

{

// traitement
}
void

main()

{

AbstraiteBase toto;
Dérivée
tata;

// impossible !!!
// possible.

tata.afficher(15, 1 2 ) ; // possible.
}

Exemple complet
Voici u n petit e x e m p l e c o m p l e t m e t t a n t e n é v i d e n c e l'intérêt
de la virtualité p u r e . A v a n t d ' e n d é c o u v r i r le listing, quelques é c l a i r c i s s e m e n t s :
D a n s l e c a d r e d u d é v e l o p p e m e n t d ' u n e interface g r a p h i q u e ,
n o u s a v o n s choisi de définir u n e classe Obj etGraphique,
qui contient le m i n i m u m i n d i s p e n s a b l e à tous les objets grap h i q u e s de notre interface. Par objet g r a p h i q u e , n o u s entend o n s tout ce qui est utilisé d a n s l'interface (bouton,
a s c e n s e u r s , m e n u s , m a i s aussi z o n e p o u v a n t contenir
d'autres objets, z o n e de dessin, etc.) D a n s un souci de sim-
Le polymorphisme et la virtualité

109

plification, n o u s retiendrons trois caractéristiques : l'objet
g r a p h i q u e parent, les c o o r d o n n é e s en x et en y de l'objet
d a n s l ' e s p a c e de c o o r d o n n é e s a s s o c i é e s à l'objet parent. A
q u o i sert l'objet p a r e n t ? A savoir d a n s quel autre objet est situé l'objet courant. P a r e x e m p l e , u n b o u t o n aura c o m m e o b jet p a r e n t u n e fenêtre.
D a n s l e c a d r e d e l ' e x e m p l e , n o u s décrirons é g a l e m e n t l a
classe B o u t o n O n O f f , c o r r e s p o n d a n t à l'objet « c a s e à coc h e r ». P o u r offrir un m i n i m u m de r é a l i s m e s a n s c o m p l i q u e r ,
n o u s s u p p o s e r o n s q u e n o u s travaillons e n m o d e texte, e t
utiliserons les primitives d e T u r b o C + + sur P C p o u r p o s i tionner le c u r s e u r à l'écran. R a s s u r e z - v o u s , il n ' y a rien de
sorcier là dedans. Voici le listing :
#include <conio.h>

// propre a Turbo C++,
// permet de positionner le curseur

enum Boolean
{
False = (1==0),
True = (1==1)
} ; // type booléen
classe ObjetGraphique

//

class ObjetGraphique
{
protected :
ObjetGraphique *pere;
int
x, y;

// Objet pere
// coord. par rapport
// au pere

public :
// constructeur
ObjetGraphique(ObjetGraphique *pi = 0,
int xi = 0, int yi = 0)
: pere(pi), x(xi), y(yi)
{ }

Nous utilisons ici
plusieurs techniques
propres au C + + et
indépendantes des

// fonctions d'accès
ObjetGraphique
*get_pere() { return pere; )
int
get_x() { return x; }
int
get_y() { return y; }

fonctions virtuelles :
les paramètres par
défaut (voir page 65),
la liste d'initialisation
(voir page 47), et la

// fonctions de modification
virtual Boolean
set_x(int me) = 0;
virtual Boolean
set„y(int ny) = 0;

définrtion inline des
fonctions (voir
page 21),

// fonctions de haut niveau
virtual void
Redessine() = 0;
} ;
110

Pont entre C et C++

classe BoutonOnOff

//

Les constantes sont
traitées page 67.

class BoutonOnOff : public ObjetGraphique
{
private :
const
char CAR_ON;
//caractère affiché si ON
const
char CAR_OFF; //caractère affiché si OFF
Boolean état;
//bouton ON (True) ou OFF
public :
// constructeur
BoutonOnOff(ObjetGraphique *pi = 0,
int xi = 0, int yi = 0,
Boolean ei = False)
: ObjetGraphique(pi, xi, y i ) ,
etat(ei), CAR_ON('x'), CAR_OFF('0')
{ }
// fonctions virtuelles pures a définir
// fonctions de modification
virtual Boolean
set_x(int nx) ;
virtual Boolean
set_y(int n y ) ;
// fonctions de haut niveau
virtual void
Redessine!);
// fonctions nouvelles de BoutonOnOff
Boolean get_etat() { return état; }
void
set_etat(Boolean ne)
{ état = ne; Redessine(); }
} ;

// définitions des fonctions de BoutonOnOff
Boolean
BoutonOnOff::set„x(int nx)
{

i f (nx != x)
{
x = nx ;
Redessine();
}
return True;
}

Boolean

BoutonOnOff;:set v(int ny)

{

if (ny != y)
{
y = ny;
Redessine( ) ;
}

return True;
}

void

BoutonOnOff::Redessine()

{

// positionnons le curseur en x, y
gotoxy(x, y ) ;
// affichons un caractère à l'endroit du curseur
Le polymorphisme et la virtualité

111

putch( état ? CAR_ON : CAR_OFF );
}

//

programme principal

void

main()

{

BoutonOnOff

bouton(0, 10, 1 0 ) ;

bouton.Redessine();
getch();
// attend qu'une touche soit frappée
bouton.set_etat(True);
)

Ce programme met en évidence l'intérêt de la virtualité pure.
D a n s la classe de base O b j e t G r a p h i q u e , trois fonctions
sont déclarées virtuelles pures : s e t _ x , s e t _ y , et R e d e s s i n e . Pourquoi ?
D a n s l'idée, p o u r fournir un c a d r e g é n é r a l i d e n t i q u e à
tous les objets g r a p h i q u e s . En forçant les classes d é r i v é e s
à définir ces trois fonctions, on s'assure q u e leurs e n t ê t e s
d e v r o n t se c o n f o r m e r à un certain profil. Un p r o g r a m m e u r sachant utiliser u n objet g r a p h i q u e s a u r a a u t o m a t i q u e m e n t utiliser tous les autres, y c o m p r i s c e u x qui ne
sont pas e n c o r e écrits.
s e t _ x e t s e t _ y d é p e n d e n t bel e t b i e n d e c h a q u e objet
g r a p h i q u e , car il c o n v i e n t de s'assurer q u e les n o u v e l l e s
v a l e u r s sont correctes, n o t a m m e n t , o n p e u t l ' i m a g i n e r ,
p a r r a p p o r t à l'objet p è r e qui contient n o t r e objet graphique.
R e d e s s i n e d é p e n d aussi d e c h a q u e objet g r a p h i q u e . I l
n e p r e n d a u c u n p a r a m è t r e car u n objet doit être c a p a b l e
de se r e d e s s i n e r en fonction de ses d o n n é e s i n t e r n e s (ici
ses c o o r d o n n é e s en x et y ) .
Les références

Seul le passage par valeur est autorisé en C standard. Même lorsque vous passez un pointeur à une fonction, vous passez la valeur
de ce pointeur. Le C++ introduit une nouvelle possibilité : le passage par référence (connu depuis longtemps en Pascal).
Par
ailleurs, ce même mécanisme de référence vous permet de déclarer
des synonymes de variables : il s'agit des références libres.

Notions de base
L'idée

Q u a n d v o u s p a s s e z à u n e fonction un p a r a m è t r e p a r référence, cette fonction reçoit un s y n o n y m e du p a r a m è t r e réel.
En o p é r a n t sur le p a r a m è t r e p a s s é p a r référence, la fonction
p e u t d o n c modifier d i r e c t e m e n t l e p a r a m è t r e réel. V o u s
n ' a v e z p a s b e s o i n d'utiliser l'adresse d u p a r a m è t r e p o u r l e
modifier, c o m m e v o u s d e v r i e z le faire en C s t a n d a r d .

Mise en

Le caractère & est utilisé d a n s l'entête de la fonction p o u r sig n a l e r q u ' u n p a s s a g e p a r référence est d é c l a r é :

œuvre C++

void
plus_l(int
Snombre)
// passage par référence attendu
{
nombre++;
114

Pont entre C et C++

)

void
{

main()

int i = 3 ;
plus_l(i);
// i vaut maintenant 4
}

C o m m e v o u s le constatez, l'appel se fait de m a n i è r e très
s i m p l e . L a fonction p l u s _ l sait q u e l e p a r a m è t r e entier
q u ' e l l e attend est u n e référence vers l e p a r a m è t r e réel i . E n
c o n s é q u e n c e , toute modification d e n o m b r e d a n s l a fonction
p l u s _ l s e répercute sur l a variable i d u p r o g r a m m e principal.
Déclarer une référence libre
V o u s p o u v e z utiliser les références en d e h o r s de toute fonction : en déclarant q u ' u n e variable est « s y n o n y m e » d'une
autre, c'est-à-dire q u e l ' u n e est u n e référence v e r s l'autre.
D è s lors, u n e modification sur l ' u n e q u e l c o n q u e de ces variables affectera le c o n t e n u de l'autre. P a r e x e m p l e :
void main()
{
int
nombre = 1 ;
int
&bis = nombre; // bis est une référence à nombre
int
*pointeur;
bis = 1 0 ;
nombre = 20;
pointeur = &bis;
(*pointeur) = 3 0 ;

// bis et nombre valent 10
// bis et nombre valent 20
// bis et nombre valent 3 0

}

L'intérêt par
rapport au C

* les constructeurs
copie sont vus au
chapitre 2, page 3 I.

Le fait d'utiliser le caractère & p o u r autre c h o s e q u e ce qu'il
signifie en l a n g a g e C doit v o u s paraître étrange. S o y e z rassuré : v o u s p o u v e z p r e s q u e t o t a l e m e n t v o u s p a s s e r d'utiliser
les références, tout en bénéficiant des substanciels a v a n t a g e s
du C + + . P o u r q u o i presque totalement ? Eh b i e n , p o u r être
franc, il faut a v o u e r q u e les c o n s t r u c t e u r s de copie* ont b e soin de types p a s s é s p a r référence.
V o u s r e m a r q u e r e z tout d e m ê m e q u e , p a s s é e l a p r e m i è r e
a n g o i s s e , les références se révèlent très p r a t i q u e s !
Les références

115

Compléments
Références
et objets
provisoires
* Petite précision de
vocabulaire : dans la
ligne int &ref = ini; ref

P o u r q u ' u n e référence soit c o r r e c t e m e n t utilisée, il faut q u e
l'objet référence et l'objet initial* soient de m ê m e type. S i n o n ,
plusieurs cas p e u v e n t se présenter :
L'objet initial est u n e c o n s t a n t e
int &ref = 1 ;
const int &ref = 1 ;

// (1) NON (voir ci-dessous)
// (2) OUI

désigne l'objet
référence et ini
l'objet initial.

L'objet initial peut être converti v e r s l'objet r é f é r e n c e
int
const

initial = 5;
float
&ref = initial;
float
&ref = initial;

// (1) NON
// (2) OUI

L e s d e u x cas ci-dessus e n g e n d r e n t les m ê m e s r e m a r q u e s :
(1) La référence n ' e s t p a s constante. V o t r e c o m p i l a t e u r doit
n o r m a l e m e n t générer u n e erreur d e type.
(2) La référence est constante. Un objet t e m p o r a i r e est créé ;
la référence le désigne.

Fonction
retournant une
référence

D a n s s o n désir infini de r e p o u s s e r toujours plus loin les limites d u C , l e C + + p e r m e t , grâce a u x références, d'utiliser les
fonctions c o m m e des variables ordinaires a u x q u e l l e s o n p e u t
affecter u n e valeur. Ainsi, q u a n d u n e fonction r e t o u r n e u n e
v a l e u r par référence, on p e u t d i r e c t e m e n t agir s u r cette v a l e u r
d e retour. M a i s u n petit e x e m p l e v a u t m i e u x q u ' u n e explication o b s c u r e :
// variables globales
int
t a b [ ] = { 0 , 1, 2, 4, 8 } ;
const
int nb_elem = 5 ; // nbr d'éléments de tab
int &fonction(int j) // retourne une référence
{
if (j >= 0 && j < nb_elem)
return tab[j];
else
return tab[0];
}

void main()
116

Pont entre C et C++

{

fonction (0) = Ï0 ;
// tab[0] vaut -ïb
fonction(l) = fonction(4); // tab[l] vaut 8
}

La p r e m i è r e ligne du m a i n est e x é c u t é e c o m m e ceci : a p p e l à
f o n c t i o n , qui retourne u n e référence vers t a b [ 0 ] . D o n c ,
d a n s l ' e x p r e s s i o n f o n c t i o n (0) = 10;, tout se p a s s e
c o m m e si f o n c t i o n ( 0 ) était r e m p l a c é p a r t a b [ 0 ]. On affecte d o n c b i e n la valeur 10 à t a b [ 0 ].
L a d e u x i è m e ligne fonctionne selon l e m ê m e principe.
Les templates
ou la mise en œuvre
de la généricité

Toujours plus, toujours : dans sa
C + + nous offre les templates, qui
les fonctions ou les classes par des
sants, mais souvent délicats à mettre
teurs relativement récents autorisent
et fiable.

débauche d'effets spéciaux, le
vous permettent de paramétrer
types. Les templates sont puisen œuvre : seuls les compilaleur usage de manière simple

Notions de base
L'idée
Juste une remarque
de vocabulaire : la

généricité est le
concept orientéobjet, alors que les

templates sont la
solution apportée par
le C + + pour mettre
en œuvre ce concept.

J u s q u ' à présent, les fonctions p o u v a i e n t avoir c o m m e p a r a m è t r e s des variables o u des objets. M a i s j a m a i s v o u s n ' a v e z
eu la possibilité de les p a r a m é t r e r p a r d e s types. C ' e s t j u s t e m e n t le rôle et l'intérêt de la généricité et des t e m p l a t e s .
D e s classes p e u v e n t é g a l e m e n t être déclarées « t e m p l a t e s »,
si v o u s j u g e z q u ' e l l e s doivent d é p e n d r e d ' u n type. Par
e x e m p l e , u n e classe qui gère u n e liste d'objets q u e l c o n q u e s
n ' a p a s à se soucier du type réel de ces é l é m e n t s : elle ne doit
s ' o c c u p e r q u e de les classer, d'en ajouter ou d ' e n s u p p r i m e r
à sa liste, etc. U n e telle classe a d o n c intérêt à être p a r a m é t r é e
118

Pont entre C et C++

p a r le type des é l é m e n t s q u ' e l l e s t o c k e . Il serait j u d i c i e u x
d'en faire u n e classe « t e m p l a t e ».

Mise en
œuvre C++

Il est i m p o r t a n t de distinguer d e u x types de t e m p l a t e s : celles qui c o n c e r n e n t les fonctions et celles qui c o n c e r n e n t les
classes. E x a m i n o n s d ' a b o r d les t e m p l a t e s de fonctions.

Templates
de fonctions

Exemple
P r e n o n s l ' e x e m p l e classique de la fonction qui r e t o u r n e le
m i n i m u m de d e u x chiffres. Il s e m b l e l é g i t i m e de v o u l o i r
r e n d r e cette fonction i n d é p e n d a n t e du t y p e d e s objets.
V o y o n s la m i s e en œ u v r e d ' u n e telle fonction, g r â c e a u x
templates :
tinclude <iostream.h> / / pour l'affichage avec cout
template <class TYPE> TYPE min(TYPE a, TYPE b)
{
return ( a < b ) ? a : b;
}
main()
{
int
char

il = 10, i2 = 20;
cl = 'x', c2 = ' a ;
1

cout << "min entier : " << min(il, i2) << endl;
cout << "min char
: " << min(cl, c2) << endl;
}

Commentaires
Observez
bien
la
déclaration
de
A v a n t le type de retour, v o u s d e v e z

la
fonction.
préciser tem-

piate <class NomType> où NomType est un identificateur de votre cru, q u e v o u s utiliserez e n s u i t e p o u r
d é s i g n e r le type « p a r a m è t r e » de votre fonction.
V o u s êtes obligés d'utiliser ce n o m de type d a n s au moins
un p a r a m è t r e de votre fonction. Ici, les d e u x é l é m e n t s à
c o m p a r e r sont de t y p e MonType, et la fonction r e t o u r n e
é g a l e m e n t un objet de type MonType.
À l'appel, le c o m p i l a t e u r reconnaît le type des p a r a m è t r e s
et r e m p l a c e d a n s la fonction toutes les o c c u r e n c e s de
Les templates

119

MonType par le t y p e v o u l u (ici, int d a n s le p r e m i e r appel e t c h a r d a n s l e d e u x i è m e ) .
T e m p l a t e s de f o n c t i o n s : cas g é n é r a l

Templates
de classes

* Il faudrait bien
entendu gérer un

Exemple
L e s t e m p l a t e s v o u s seront é g a l e m e n t utiles d a n s d e s c l a s s e s .
R e p r e n o n s l ' e x e m p l e de la classe qui gère u n e liste d'objets
de type (ou de classe) q u e l c o n q u e . P o u r simplifier le p r o p o s ,
n o u s utiliserons u n tableau p o u r stocker les objets*. C o m m e n t la déclarer ?

nombre variable
d'objets, avec
allocations et
désallocations « à la
demande ». Cela
compliquerait la classe
et nuirait à
l'explication des

tinclude <iostream.h> // pour affichage avec cout
#include <stdlib.h>
// pour la fonction exit
template <class TYPE>
class Liste
{
private:
enum { MAX = 3 ,} ;
// constante
TYPE
elem[MAX];

templates. Par ailleurs,

public :

la gestion d'erreurs
est réduite ici à sa

TYPE
void
void

plus simple
expression. N o u s
verrons au chapitre
I 3, page I 37,
comment implanter
une véritable gestion
des erreurs.

Liste() { }
get_elem(int n ) ;
set_elem(int n, TYPE e ) ;
affiche();

} ;

template <class TYPE> TYPE Liste<TYPE>::get_elem(int n)
// retourne le nième élément classé
{

if (n >= 1 && n <= MAX)
return elem[ n-1 ];
else
{
cerr <<"Liste::get_elem - Indice hors limite :"
<< n << endl;
120

Pont entre C et C++

exit(1);
}
}

template <class T Y P E >
void
Liste<TYPE>: :set_elem(int n, TYPE e)
// stocke e à la nième place dans la liste
{
if ( n >= 1 && n <= MAX)
elem[n-l] = e;
else
{
cerr <<"Liste : :set_elem - Indice hors limite :"
<< n << endl;
exit(1);
}
}

template <class TYPE>
{
int
i;

void

Liste<TYPE>::affiche()

for (i = 0; i < MAX ; i + +)
cout << "Num " << i+1 << ':' << elem[i] << endl;
}

main()
{

Liste<int>

liste;

liste.set_elem(1, 1 0 ) ;
liste.set_elem(3, 3 0 ) ;
liste.set_elem(2, 2 0 ) ;
liste.affiche();
// affichage :
// Num 1 : 10
// Num 2 : 20
// Num 3 : 30
}

Q u e l q u e s e x p l i c a t i o n s s u r cet e x e m p l e
N o u s a v o n s choisi d e stocker les é l é m e n t s d a n s u n tableau
de taille MAX, MAX étant u n e c o n s t a n t e spécifique à la classe
Liste, définie à l'aide d ' u n enum (voir p a g e 7 1 ) .
La déclaration de la classe i n d i q u e q u e c'est u n e t e m p l a t e par a m é t r é e p a r le type TYPE :
template <class TYPE>

class Liste
Les templates

121

N o u s utilisons d o n c le type g é n é r i q u e TYPE d a n s la c l a s s e ,
p o u r i n d i q u e r ce q u e n o u s s t o c k o n s d a n s le t a b l e a u e l e m :
TYPE

elem[MAX];

N o t e z é g a l e m e n t la définition des f o n c t i o n s - m e m b r e s en deh o r s de la classe :
template <class TYPE> TYPE Liste<TYPE>: :get_elem(int n)

TYPE est le type de retour de la fonction, d o n t le n o m de
classe est Liste<TYPE>. La seule c h o s e qui c h a n g e vraim e n t par r a p p o r t à u n e f o n c t i o n - m e m b r e n o r m a l e , c'est le

préfixe « template <class TYPE>».
Utilisation d'une classe t e m p l a t e
L'utilisation d ' u n e classe t e m p l a t e se fait en d o n n a n t le n o m
d u type entre c h e v r o n s . D a n s l ' e x e m p l e ci-dessus, cela
donne :
Liste<int>

liste;

où int aurait pu être r e m p l a c é p a r n ' i m p o r t e quel autre
type o u classe.
T e m p l a t e s de classes : cas g é n é r a l
Voici c o m m e n t l'on déclare u n e classe t e m p l a t e :
122

Pont entre C et C++

Et voici c o m m e n t l'on définit u n e f o n c t i o n - m e m b r e d ' u n e
classe t e m p l a t e :

Compléments
Attention à
l'ambiguïté

I l p e u t v o u s arriver d'avoir b e s o i n d ' i m b r i q u e r d e s t e m p l a tes. Par e x e m p l e , v o u s v o u l e z gérer u n e liste d'arbres, où la
classe Liste et la classe A r b r e sont des t e m p l a t e s . Il n ' y a en
théorie p a s de p r o b l è m e , m a i s je tiens à attirer v o t r e attention
sur la m a n i è r e de déclarer un objet Liste :
Liste<Arbre<int>>

ma_liste_d_arbres;

// NON

D a n s cette déclaration, l e type d e v o t r e liste t e m p l a t e n ' e s t
autre q u ' u n e classe A r b r e , e l l e - m ê m e t e m p l a t e , d o n t l e
t y p e est i n t . C e t t e déclaration p r o v o q u e i n s t a n t a n é m e n t
u n e erreur de c o m p i l a t i o n , b i e n q u e , à p r e m i è r e v u e , elle
s e m b l e correcte. La seule c h o s e qui c l o c h e , c'est la p r é s e n c e
d e » . E n C + + , les d e u x caractères s u p é r i e u r s r e p r é s e n t e n t
un opérateur. Il y a d o n c m é p r i s e . P o u r y r e m é d i e r , t a p e z un
e s p a c e entre les d e u x c h e v r o n s . L a b o n n e déclaration d e v i e n t
donc :
Liste< Arbre<int> >

ma_liste_d_arbres;

// OUI
Les classes
et fonctions amies

L'amitié n'est pas une vertu réservée aux humains. Les classes et
les fonctions C++ peuvent, elles aussi, se lier d'amitié. Heureusement, vous seul pouvez les autoriser à s'enticher ainsi les unes des
autres.

Notions de base
L'idée
* Rappelons que
l'encapsulation c o n siste à manipuler des
objets composés de
données et de fonctions de manipulation
de ces données. Les
données sont cachées
au monde extérieur.

L o r s q u ' u n e fonction est déclarée a m i e ( f r i e n d ) d ' u n e c l a s s e ,
elle p e u t a c c é d e r directement à toutes les d o n n é e s p r i v a t e
o u p r o t e c t e d d e cette classe.
L o r s q u ' u n e classe est déclarée a m i e d ' u n e autre c l a s s e , ce
s o n t toutes les fonctions de la p r e m i è r e classe qui p e u v e n t
a c c é d e r a u x d o n n é e s p r i v é e s d e l a d e u x i è m e classe.
C'est bien beau, mais si vous avez bien compris le premier
chapitre de ce livre, v o u s devriez b o n d i r de v o t r e siège, et
v o u s écrier « M a i s c'est u n e entorse i n s u p p o r t a b l e à la sacrosainte règle de l'encapsulation* ! » Et v o u s auriez raison.
A u s s i s o m m e s - n o u s en droit de n o u s d e m a n d e r pourquoi le
créateur du C + + a p e r m i s q u e l'on viole — le m o t n ' e s t p a s
224

Pont entre C et C++

trop fort — l ' e n c a p s u l a t i o n ? E s s e n t i e l l e m e n t p o u r d e s rais o n s d'architecture, d u e s a u fait q u e l e C + + n ' e s t p a s u n lang a g e e n t i è r e m e n t orienté objet (il co-existe a v e c le C ) , m a i s
aussi p o u r p e r m e t t r e à un p r o g r a m m e u r d ' a c c o r d e r des
« droits » entre ses classes internes, et é v e n t u e l l e m e n t p o u r
des raisons d ' o p t i m i s a t i o n (voir aussi les q u e s t i o n s - r é p o n s e s ,
a u chapitre 16). V o u s t r o u v e r e z l'utilité d e s classes a m i e s
d a n s l ' e x e m p l e d e s u r c h a r g e d e l'opérateur « , p a g e 92. C e l a
dit, méfiez-vous et n'utilisez les « a m i e s » q u e si v o u s y êtes
contraint...

Mise en
œuvre C++

La déclaration de l'amitié d ' u n e classe A p o u r u n e autre entité se fait n ' i m p o r t e où d a n s la classe A, g r â c e au m o t - c l é

friend:
class A
{
friend void fonction_B();
friend class C;
//

// (1) pour une fonction
1/(2) pour une classe

...

> ;

La l i g n e (1)
signifie q u e fonction_B p e u t a c c é d e r a u x
d o n n é e s private ou protected de la classe A. R e m a r q u e z
q u e si f onction_B était u n e fonction m e m b r e d ' u n e autre
c l a s s e , il faudrait spécifier de quelle classe elle est m e m b r e ,
avec u n e ligne du style :
friend void AutreClasse: :fonction_B();

La l i g n e (2) signifie q u e toutes les fonctions de la c l a s s e C ont
le droit d ' a c c é d e r d i r e c t e m e n t a u x d o n n é e s private ou

protected de la classe A.
C o m m e v o u s l'avez constaté, la déclaration f riend... se fait
d a n s la classe qui accorde le droit a u x autres de v e n i r tripatouiller ses p r o p r e s d o n n é e s . R é c a p i t u l o n s :
Classes et fonctions amies

Attention !

125

Utilisez avec la p l u s g r a n d e p r é c a u t i o n les fonctions
f r i e n d : elles tordent le c o u à l ' e n c a p s u l a t i o n , et sont d o n c
d a n g e r e u s e s p o u r la stabilité de votre application. Elles ne
sont intéressantes q u e p o u r o p t i m i s e r des c l a s s e s , o u p o u r
accorder d e s droits entre classes internes qui n ' i n t é r e s s e r o n t
p a s d'autres p r o g r a m m e u r s . E n r é s u m é , bidouillez v o s c l a s ses à v o u s , m a i s faites du travail p r o p r e sur les classes qui
seront u t i l i s é e s / r e p r i s e s p a r d'autres.
G a r d e z b i e n p r é s e n t à l'esprit q u ' u n e modification d a n s les
d o n n é e s p r i v é e s d ' u n e classe qui a déclaré d'autres c l a s s e s
a m i e s doit se répercuter sur ces autres classes ! A u s s i , m é fiez-vous et i n d i q u e z toujours c l a i r e m e n t d a n s la d o c u m e n tation quelles classes sont a m i e s d e qui, p o u r q u e c e u x qui s e
p e n c h e r o n t sur v o t r e travail d a n s q u e l q u e s m o i s ( v o u s y
c o m p r i s ) ne s'arrachent p a s les c h e v e u x en se c o g n a n t la tête
c o n t r e les m u r s .
L'héritage multiple

L'héritage
déjà pour
différence
classes !
tors...

simple vous permet d'utiliser des classes qui existent
en créer d'autres. L'héritage multiple est identique, à la
près que vous pouvez hériter simultanément de plusieurs
Ce qui n'est pas sans engendrer quelques problèmes re-

Notions de base
L'idée

L ' h é r i t a g e m u l t i p l e v o u s p e r m e t d e créer des classes d é r i v é e s
qui héritent e n m ê m e t e m p s d e p l u s i e u r s classes d e b a s e . I l
n e s'agit pas d ' u n héritage s i m p l e e n c h a î n e m a i s bel e t b i e n
d'un héritage m u l t i p l e s i m u l t a n é :
128

Pont entre C et C++

D a n s le cas p r é s e n t é à droite du s c h é m a ci-dessus, la classe C
hérite du c o n t e n u des classes A et B (mais cela d é p e n d touj o u r s d e s spécificateurs p r o t e c t e d , p r i v a t e e t p u b l i c ) .

Mise en
œuvre C++

U n héritage multiple s e spécifie c o m m e u n héritage s i m p l e ,
en séparant p a r d e s virgules les classes de b a s e s désirées.
P o u r déclarer l'héritage multiple du s c h é m a p r é c é d e n t , il
faudrait écrire :
class A
{
// blabla
public :
void

fonction_a();

} ;

class B
{
// blabla
public :
void

fonction_b();

} ;

class C : public A, public B
{

// blabla
public :
void

fonction_c();

} ;

E n admettant que f o n c t i o n _ a , f o n c t i o n _ b e t f o n c t i o n ^ ont été définies, v o u s pourriez écrire q u e l q u e chose
comme :
void

main()

{

C

objet_c;

objet_c.fonction_a(); // appel à A::fonction_a()
objet_c.fonction_b() ; // appel à B: :fonction_b()
objet_c.fonction_c(); // appel à C: :fonction_c()
}

Il n ' y a là rien de b i e n surprenant. On pourrait m ê m e croire
q u e c'est simple. M a i s c o m m e d e n o m b r e u x c o n c e p t s C + + , l e
d e s s u s d e l'iceberg c a c h e u n certain n o m b r e d e complications
difficiles à déceler au p r e m i e r a b o r d . . .
L'héritage multiple

129

O r d r e d'appel des constructeurs
D a n s le cas d ' u n héritage m u l t i p l e , la création d ' u n objet de
la classe dérivée appelle les c o n s t r u c t e u r s des classes de b a s e
d a n s l'ordre de déclaration de l'héritage. P a r e x e m p l e :
class A
{
} ;

class B
{
} ;

class C : public B, public A
// la ligne ci-dessus détermine l'ordre d'appel
// des constructeurs
{
} ;

void

main()

{

C
objet_c;
// appel à B ( ) , puis A ( ) , puis C()
}

Les listes
d'initialisations sont
présentées page 47.

Par ailleurs, si v o u s p r é c i s e z u n e liste d'initialisation*, l'ordre
d ' a p p e l des c o n s t r u c t e u r s d é p e n d r a toujours de l'ordre de
d é c l a r a t i o n des classes de b a s e s . P a r e x e m p l e :
class C : public B, public A
{
public :
C() ;
} ;

C::C() : A(param), B(param) // liste d'initialisation
{
}

void

main()

{

C

objet_c; // appel à B(param), A(param) puis C()

}

L'intérêt par
rapport au C

Le l a n g a g e C ne p r o p o s e p a s d ' é q u i v a l e n t de la n o t i o n
d'héritage s i m p l e , il est d o n c v a i n de c h e r c h e r un é q u i v a l e n t
d'héritage m u l t i p l e . Le principal atout de ce dernier est q u ' i l
p e r m e t d e m o d é l i s e r d e façon p l u s juste l e m o n d e réel.
730

Pont entre C et C++

Par e x e m p l e , si v o u s é l a b o r e z u n e classification des a n i m a u x ,
v o u s p o u v e z être a m e n é à considérer la b a l e i n e c o m m e un
m a m m i f è r e m a i s aussi c o m m e u n a n i m a l v i v a n t s o u s l'eau.
L'héritage multiple vous permettra de garder un schéma cohérent :

exemple

d'héritage multiple

Il faut n é a n m o i n s t e m p é r e r cette excitation : l'héritage m u l tiple est c o m p l e x e à m a n i p u l e r ; n o u s a v o n s vu à q u e l point
il faut faire attention a u x a m b i g u ï t é s difficiles à déceler. E s s a y e z autant q u e p o s s i b l e d'éviter l'héritage multiple.
N ' o u b l i e z p a s q u e m ê m e s i v o u s l e m a î t r i s e z parfaitement, c e
n ' e s t peut-être p a s le cas d e s futurs utilisateurs de v o s classes. Or la c o m p r é h e n s i o n d ' u n e hiérarchie de classes est vitale p o u r c o n c e v o i r du c o d e fiable et c o m p r é h e n s i b l e .
L'héritage multiple

131

Compléments
Duplication
de données
d'une classe
de base

A t t a r d o n s - n o u s sur c e qui s e p a s s e e n m é m o i r e d a n s certains
cas d ' h é r i t a g e s multiples. A d m e t t o n s q u e les d e u x classes d e
b a s e d ' u n héritage m u l t i p l e héritent e l l e s - m ê m e s d ' u n e
m ê m e classe de b a s e :

héritage multiple « double »

Traduisons ce schéma en C + + :
class Base
{
int variable_base;
// blabla
} ;

class A : public Base
{
int variable_A;
// blabla
};

class B : public Base
{
int variable_B;
// blabla
} ;

class C : public A, public B
{
int variable_C;
// blabla
};

C o n s é q u e n c e s : un objet de classe C c o n t i e n d r a d e u x fois les
d o n n é e s héritées de la classe Base. P o u r q u o i ? P a r c e q u e à la
fois l'objet A et l'objet B c o n t i e n n e n t les d o n n é e s héritées de
la classe Base. Or la classe C hérite de la c l a s s e A et de la
132

Pont entre C et C++

classe B. C bénéficie donc de d e u x copies distinctes des donn é e s héritées de la classe Base. En m é m o i r e , cela r e s s e m b l e à
ceci :

C o m m e n t , dès lors, a c c é d e r à l ' u n e ou l'autre c o p i e d e s donn é e s de la classe Base ? T o u t s i m p l e m e n t en
utilisant
l'opérateur de résolution de p o r t é e : :. Il suffit de spécifier
le n o m de la classe à laquelle appartient la d o n n é e v o u l u e .
A i n s i , p o u r a c c é d e r à variable_base hérité d a n s la classe
B, il faut utiliser B: :variable_base. De m ê m e , p o u r acc é d e r à la c o p i e de variable_base s t o c k é e d a n s la classe
A, il faut utiliser A : : va r i ab 1 e_ba s e.
Voici un e x e m p l e illustrant la duplicité d e s d o n n é e s et leur
utilisation :
class Base

* Pour un exemple

public :
int variable_base; // désolé *

simple et court, la
variable est déclarée

class A : public Base

public. C'est un
exemple à ne pas

// blabla

suivre (en temps
normal). Il faudrait
bien entendu la
déclarer private ou

class B : public Base
// blabla

protected et définir
des fonctions public
d'accès et de
modification.

class C : public B, public A
// blabla

void main()
L'héritage multiple

133

{

C

objet_c;

objet_c.variable_base = 1;
objet_c.B: :variable_base = 1;
objet_c.A::variable_base = 2;

// (1) erreur!!!
// (2) mieux
// (3) mieux

}

L a ligne (1) n e précise p a s à quelle v a r i a b l e _ b a s e elle recourt. L e c o m p i l a t e u r n e c o m p r e n d p a s e t v o u s d e m a n d e d e
lever l ' a m b i g u ï t é .
En r e v a n c h e , les lignes (2) et (3) sont s a n s é q u i v o q u e . R a p p e lons
que
les
variables
B : :variable_base
et
A : : v a r i a b l e _ b a s e s o n t différentes e t b i e n distinctes e n
mémoire.
Masquage du
polymorphisme

S o u s c e titre s e c a c h e u n p r o b l è m e é p i n e u x e n g e n d r é par
l'héritage multiple. Le fait d'être obligé de p r é c i s e r e x a c t e m e n t à quelle fonction on fait référence va à l ' e n c o n t r e m ê m e
du p r i n c i p e de virtualité. P r e n o n s un e x e m p l e où cette faiblesse est m i s e en é v i d e n c e :

N o u s considérons qu'un périphérique est à la fois une ressource et un objet réel, physique. N o u s définissons une
fonction virtuelle I n f o pour les classes R e s s o u r c e , O b j e t R e e l e t P é r i p h é r i q u e . Voici l e listing C + + qui correspond à cette architecture :
class Ressource
{
public :
virtual void

Info();

};

void

Ressource::Info() { }

class ObjetReel
134

Pont entre C et C++

{
public :
virtual void Info();
} ;

void

ObjetReel: : Info() { }

class Périphérique : public Ressource, public ObjetReel
{

} ;

class Imprimante : public Périphérique
{

public :
virtual void Info();
};

void

Imprimante::Info() { }

void
main()
{
Périphérique
Imprimante

* inconnu;
hp5 00;

inconnu = &hp500;
inconnu -> Info();
inconnu -> Ressource::Info();

// (1)
// (2) inattendu
// (3) OK

)

Le problème

* En effet inconnu
est un pointeur de
Périphérique, mais il
pointe en réalité sur
un objet de classe
Imprimante.

Il existe un moyen
d'éviter ce problème :
c'est l'héritage virtuel,
détaillé ci-après.

Voici le problème : en temps normal, grâce au mécanisme de
virtualité, la ligne (2) du m a i n devrait appeler la fonction
I n f o définie dans la classe I m p r i m a n t e * . M a i s hélas, à
cause de l'héritage multiple, c'est la fonction-membre définie
par la classe P é r i p h é r i q u e qui est appelée, à notre grand
dam !
P o u r plus de clarté, je vais reprendre l'explication d'une autre manière.
Quand une classe, ici P é r i p h é r i q u e , utilise l'héritage
multiple, cela peut poser un problème d'ambiguité : il suffit
que les classes de base héritées (ici R e s s o u r c e et O b j e t R e e l ) possèdent des fonctions homonymes (ici I n f o ) et
toc, le compilateur C + + renonce au polymorphisme. Il appelle donc la fonction correspondant au type statique du
pointeur (ici P é r i p h é r i q u e ) . O r , la souplesse de la virtualité réside justement dans cette espèce de flou permis au pro-
L'héritage multiple

135

g r a m m e u r , qui p e u t utiliser des p o i n t e u r s d e classes p o u r
a p p e l e r d e s fonctions virtuelles. L a b o n n e fonction étant app e l é e p e n d a n t l'exécution d u p r o g r a m m e , s e l o n l'objet réell e m e n t pointé.

Héritage virtuel

Il vaut mieux lire les
paragraphes
précédents avant de

C o m m e n o u s l ' a v o n s v u p r é c é d e m m e n t , les d o n n é e s d ' u n e
classe d e b a s e p e u v e n t être d u p l i q u é e s d a n s certains cas
d ' h é r i t a g e multiple. M a l h e u r e u s e m e n t , tel n ' e s t p a s forcém e n t le désir du p r o g r a m m e u r . R e m é d i a n t à ce p r o b l è m e ,
l'héritage virtuel p e r m e t de n'avoir qu'une seule occurrence
d e s d o n n é e s héritées d ' u n e classe d e b a s e .

s'aventurer dans
celui-ci.

héritage multiple en diamant

P o u r q u e la classe C ne p o s s è d e q u ' u n seul e x e m p l a i r e d e s
d o n n é e s de la classe Base, il faut q u e les classes A et B héritent v i r t u e l l e m e n t de la classe Base. Voici l ' e x e m p l e C + + qui
c o r r e s p o n d à cette explication (c'est le m ê m e e x e m p l e q u e le
p a r a g r a p h e p r é c é d e n t , à l ' e x c e p t i o n des m o t s - c l é s virtual
h a b i l e m e n t placés) :
class Base
{
public :
int variable_base;
// blabla
};

class A : virtual public Base
{
int variable_A;
// blabla
};

class B : virtual public Base
{
int variable_B;
// blabla
} ;
136

Pont entre C et C++

class C : public A, public B
{
int variable_C;
// blabla
} ;

O b s e r v e z m a i n t e n a n t sur c e s c h é m a l'état d e l a m é m o i r e
q u a n d un objet de classe C est créé :

Voilà, il n ' y a p l u s q u ' u n e seule o c c u r r e n c e d e s d o n n é e s de
B a s e . C o n t r a i r e m e n t a u p a r a g r a p h e p r é c é d e n t o ù v o u s étiez
obligé de spécifier le « c h e m i n » de v o s d o n n é e s , v o u s n ' a v e z
p l u s à v o u s en soucier ici. Illustrons cette j o v i a l e c o n c l u s i o n
p a r un b o u t de p r o g r a m m e :
void main()
{
C
objet_c;
,objet_c.variable_base = 1 ;
>

// parfait !
Les exceptions

Le traitement des erreurs est depuis toujours une vraie plaie pour
les programmeurs.
Le C++
nous propose le mécanisme des
« exceptions » pour coordonner la lutte contre ces erreurs indésirables. Attention cependant : les exceptions sont encore peu répandues, tant au niveau des compilateurs que des programmeurs C++.
C'est donc en quelque sorte un secteur « en chantier »...

Notions de base
L'idée

U n p r o g r a m m e , m ê m e b i e n écrit, p e u t rencontrer d e s erreurs
d ' e x é c u t i o n s : disque plein, saturation de la m é m o i r e , etc.
C o m m e n t traiter ces p r o b l è m e s ? L e C + + r é p o n d s i m p l e m e n t
par le c o n c e p t d'exception.
Qu'est-ce qu'une « exception » ?
U n e e x c e p t i o n est u n e variable, d e n ' i m p o r t e quel type, qui
s y m b o l i s e u n e erreur. Q u a n d u n e partie d e p r o g r a m m e rencontre un p r o b l è m e , elle p e u t lancer u n e e x c e p t i o n , qui p o u r ra être interceptée p a r un autre b o u t de p r o g r a m m e .
L'intérêt de ce s y s t è m e est de séparer la détection d'erreur
d e s t r a i t e m e n t s associés. P a r e x e m p l e , q u a n d v o u s é c r i v e z
u n e hiérarchie de classes, il est j u d i c i e u x de définir les e x -
138

Pont entre C et C++

ceptions q u e v o s classes p o u r r o n t lancer e n cas d e problème.
De s o n côté, l'utilisateur de v o s classes aura c o n n a i s s a n c e de
la liste de ces e x c e p t i o n s , et p o u r r a e n v i s a g e r différents trait e m e n t s p o u r é r a d i q u e r le mal.

Mise en
œuvre C++

A t t e n t i o n : les
exceptions f o n t
uniquement référence
aux événements

Utiliser des exceptions existantes
P o u r ce faire, il faut b i e n distinguer d e u x b l o c s de c o d e : celui
qui réalisera les opérations « d a n g e r e u s e s », c'est-à-dire susceptibles de générer des e x c e p t i o n s , et celui q u i traitera ces
e x c e p t i o n s si elles surviennent. Le p r e m i e r bloc est précédé
d u mot-clé t r y , l e s e c o n d d u mot-clé c a t c h . V o y o n s u n petit e x e m p l e :

internes au
programme, et n o n
aux événements
extérieurs c o m m e par
exemple un clic souris
ou l'appui sur une

#include <iostream.h>
#include <except.h>
void main()
{
try

t o u c h e du clavier.

{

int *p = new int[1E99];
// difficile !
cout << "Fin normale" << endl;
}

catch(xalloc)
{

cout << "Fin chaotique" << endl;
L'exemple de xalloc
fonctionne avec

}
)

T u r b o C + + sur PC.
Consultez la
documentation de

// affichage :
// Fin chaotique

votre compilateur
p o u r obtenir la liste
des exceptions
définies.

E

A l l o u e r 1 9 9 entiers est u n e opération délicate, en tout cas
p o u r m o n ordinateur. Il faut savoir é g a l e m e n t q u ' e n cas
d ' é c h e c d'allocation m é m o i r e , u n e e x c e p t i o n d e t y p e x a l l o c
est g é n é r é e . A p r è s l e bloc t r y , n o u s détaillons u n bloc
c a t c h qui intercepte j u s t e m e n t les e x c e p t i o n s x a l l o c .
L ' e x é c u t i o n m o n t r e b i e n q u e l'exception a été g é n é r é e et traitée.
F o n c t i o n n e m e n t de try et catch
Q u a n d u n e e x c e p t i o n est détectée d a n s u n bloc t r y ,
l ' e x é c u t i o n de ce dernier est s t o p p é e , p u i s d é r o u t é e s u r le
bloc c a t c h c o r r e s p o n d a n t . A l a fin d u bloc c a t c h , o n n e
retourne pas d a n s le bloc t r y fautif. En r e v a n c h e , le pro-
Les exceptions

139

g r a m m e suit son cours comme si le bloc t r y avait été exécuté.
Q u a n d u n e e x c e p t i o n est r e n c o n t r é e , les d e s t r u c t e u r s d e s
objets du bloc t r y sont appelés avant d'entrer d a n s le bloc
catch.
U n b l o c t r y doit o b l i g a t o i r e m e n t être suivi d ' a u m o i n s u n
bloc c a t c h .
Plusieurs b l o c s c a t c h p e u v e n t être décrits à la file, si chac u n i n t e r c e p t e u n type d ' e x c e p t i o n différent.
S i a u c u n bloc c a t c h n'intercepte l'exception é m i s e , l a
fonction t e r m i n a t e est appelée. Par défaut, elle fait appel à l a fonction a b o r t qui m e t fin a u p r o g r a m m e . C e
p o i n t est a b o r d é p l u s e n détail d a n s les c o m p l é m e n t s d e
ce chapitre.
Précisions sur catch
U n bloc c a t c h doit être p r é c é d é d ' u n bloc t r y . L a s y n t a x e
d e c a t c h p e u t être l ' u n e des trois s u i v a n t e s :
catch (Type)

Intercepte les exceptions de type Type, ainsi que les
exceptions de classes dérivées si Type est une
classe.

catch (Type obj)

Idem que ci-dessus, mais lèxception est représentée

catch (...)

dans le bloc catch par un objet réel : obj.
Intercepte toutes les exceptions non-traitées par les
blocs catch précédents.

Intercepter plusieurs exceptions
I l suffit p o u r cela d'écrire plusieurs b l o c s c a t c h après l e bloc
t r y . C o m m e n o u s l ' a v o n s v u d a n s l e tableau c i - d e s s u s , v o u s
p o u v e z intercepter toutes les e x c e p t i o n s , s a n s distinction, e n
utilisant c a t c h ( . . . ) .
tinclude <iostreain.h>
#include <except.h>
void main()
{
try
{

// quelque chose qui va déclencher
// Exceptionl, Exception2 ou une autre exception
}
catch(Exceptionl)
{
140

Pont entre C et C++

cout << "Fin chaotique 1" << endl;
}

catch(Exception2)
{
cout << "Fin chaotique 2" << endl;
}

catch (...)
{
cout << "Fin très chaotique" << endl;
}
}

Créer et lancer ses propres exceptions
I n t e r c e p t e r les erreurs des a u t r e s , c'est b i e n , m a i s p r é v o i r les
s i e n n e s , c'est m i e u x . N o u s a v o n s v u q u ' u n e e x c e p t i o n p o u v a i t être de n ' i m p o r t e q u e l type : u n e v a r i a b l e ou un objet. La
p r e m i è r e é t a p e c o n s i s t e d o n c à créer les t y p e s c o r r e s p o n d a n t
à v o s e x c e p t i o n s , c'est-à-dire a u x e r r e u r s q u e v o u s p o u r r e z
d é c l e n c h e r . E n s u i t e , q u a n d v o u s é c r i v e z u n e fonction, v o u s
p o u r r e z « lancer » u n e de v o s e x c e p t i o n s g r â c e au m o t - c l é

throw:
throw obj;
throw;

Lance l'exception obj.
Relance la dernière exception lancée.

throw s a n s p a r a m è t r e p e u t s'utiliser q u a n d v o u s n ' a r r i v e z
p a s à r é s o u d r e le p r o b l è m e d a n s un b l o c catch. V o u s relancez donc la dernière exception, en espérant qu'il existe un
autre b l o c catch d a n s la ou les fonctions a p p e l a n t e s .
Exemple
D a n s l ' e x e m p l e qui suit, la classe MaClasse va l a n c e r u n e

e x c e p t i o n de classe MonErreur, dès q u e la fonction FonctionBoguee est a p p e l é e .
#include <iostream.h>
#include <except.h>
// définissons une classe exception (en fait une classe
// comme une autre)
class MonErreur
{
public :
MonErreur()
{cout << "Constructeur de MonErreur" << endl;}
};
Les exceptions

141

// définissons une classe qui va lancer des exceptions
class MaClasse
{

public :
void FonctionBoguee!);
};

* Dans la ligne cicontre, MonErreurO
crée un objet
temporaire de classe
MonErreur. C'est

void
MaClasse::FonctionBoguee()
{
// admettons que cette fonction soit boguée;
// elle générera donc une exception :
throw MonErreur();
// * voir dans la marge
}
void main()
{
try

donc bel et bien un

{

MaClasse obj;

objet qui est spécifié
après t h r o w .

obj.FonctionBoguee();
}

catch (MonErreur)
{
cout << "Panique" << endl;
}
}

// affichage :
// Constructeur de MonErreur
// Panique

C e t exemple nous montre que l'exception générée dans
F o n c t i o n B o g u e e , de la classe M a C l a s s e , est interceptée
dans le m a i n par l'instruction c a t c h ( M o n E r r e u r ) .
N o u s avons donc mis en évidence le fait que c'est le concepteur de la classe M a C l a s s e qui détecte les erreurs et lance
une exception ; alors que c'est l'utilisateur de cette classe, ici
la fonction main, qui envisage les solutions selon l'exception
rencontrée.
L'intérêt par
rapport au C

Le C ne p r o p o s e a u c u n s y s t è m e de g e s t i o n des erreurs. La
p r a t i q u e la p l u s c o u r a n t e c o n s i s t e à r e n v o y e r u n e v a l e u r signifiant q u ' u n e erreur est i n t e r v e n u e . C e l a oblige à tester
cette valeur à c h a q u e appel, ce qui alourdit et ralentit c o n s i d é r a b l e m e n t l e p r o g r a m m e . Par ailleurs, q u e faire q u a n d u n e
telle erreur est détectée ?
G r â c e a u x e x c e p t i o n s , les erreurs sont d e s objets à p a r t entière, d é c l e n c h a b l e s puis récupérables en c a s c a d e , du b l o c le
142

Pont entre C et C++

p l u s p r o c h e — en fait le p l u s c o m p é t e n t p o u r traiter
l'erreur — a u x b l o c s les p l u s g é n é r a u x . M a i s p l u s e n c o r e ,
c'est u n e tentative de formaliser les t r a i t e m e n t s d'erreur, un
c a d r e général qui p e r m e t a u x p r o g r a m m e u r s d e m i e u x s'y
retrouver.

Résumé

L e C + + p r o p o s e d e gérer les erreurs p a r l e m é c a n i s m e d e s
e x c e p t i o n s . U n e e x c e p t i o n n ' e s t rien d'autre q u ' u n e v a r i a b l e
qui r e p r é s e n t e u n e erreur. T r o i s m o t s - c l é s p e r m e t t e n t d e les
mettre en œuvre : t r y , c a t c h et t h r o w . Q u a n d une exception est l a n c é e p a r t h r o w q u e l q u e part d a n s u n b l o c t r y ,
l ' e x é c u t i o n de ce bloc est s t o p p é e et d é r o u t é e v e r s le b l o c
c a t c h correspondant. S i aucun bloc c a t c h n'intercepte
l'exception, l a fonction t e r m i n a t e est a p p e l é e (voir c o m p l é m e n t s ) . P a r défaut, elle m e t s o b r e m e n t fin a u p r o g r a m m e
par un appel à a b o r t ( ).

Exemple
complet

N o u s allons m a i n t e n a n t détailler u n c a s c o n c r e t d'utilisation
d e s e x c e p t i o n s : u n e classe tableau qui n ' a c c e p t e p a s q u ' o n
é c r i v e en d e h o r s de ses b o r n e s .
#include <iostream.h>
#include <except.h>
class ErreurBorne
{
protected:
int borne_fautive;
public:
ErreurBorne(int b)
{ borne_fautive = b; }
int get_borne_fautive()
{ return borne_fautive; }
} ;

class Tableau
{
protected:
enum { MAX = 10 }; // constante
int tab[MAX];
public :
int get_MAX() { return MAX; }
int &operator[](int i ) ;
} ;
Les exceptions

pour que l'utilisateur

int&Tableau: :operator[] (int i)
{
if (i<0 || i>=MAX)
throw ErreurBorne(i);
else
return tab[i];

puisse consulter mais

143

}

* L'opérateur [ ]
retourne ici une
référence (un
synonyme) du tableau

aussi modifier sa
valeur dans une
expression du style
t[n] = i. V o i r le

// * voir dans la marge

void
main()
{
Tableau t;
int
i;

chapitre sur les

try
{

références, page I I 3.

t[9] = 1;
t[99] = 2;

// instruction fautive

i = t[9];
// ligne jamais exécutée
}
catch (ErreurBorne err)
{
cout << "Erreur de borne : "
<< err.get_borne_fautive() << endl;
}
}

// affichage :
// Erreur de borne : 99

Compléments
Spécifier des
exceptions

V o u s p o u v e z i n d i q u e r a u c o m p i l a t e u r e t a u x futurs utilisateurs d e v o s classes quelles e x c e p t i o n s v o s fonctions s o n t
susceptibles de lancer. P o u r ce faire, il faut n o m m e r c e s e x c e p t i o n s d a n s un throw, a p r è s l'entête n o r m a l . E x e m p l e :

Attention : il faut que
le throw soit spécifié
dans l'entête de
déclaration et dans

void
int
void
void

fl(int a)
throw (MonErreur);
f
2
(
)
throw (MonErreur, MaCrainte);
f3(char *s)
throw(); // aucune exception
f4(float f ) ;
// toutes les exceptions

l'entête de définition
de la fonction.

V o i l à la signification de ces trois lignes : f 1 ne p e u t lancer
q u e d e s e x c e p t i o n s de classe MonErreur ou de c l a s s e s dériv é e s . f2 p e u t lancer des e x c e p t i o n s MonErreur ou Ma-
144

Pont entre C et C++

C r a i n t e ou de classes dérivées, f 3 ne p e u t l a n c e r a u c u n e
exception, f 4 p e u t se p e r m e t t r e de lancer toutes les e x c e p tions, c a r a u c u n e limitation n ' e s t e x p r i m é e .
Fonction unexpected
S i , m a l g r é tout, u n e fonction lance u n e e x c e p t i o n qui ne fig u r e p a s d a n s l e t h r o w d e son entête, l a fonction u n e x p e c t e d (inattendue) est appelée. Par défaut, cette fonction fait
appel à la fonction t e r m i n â t e qui fait e l l e - m ê m e appel à
a b o r t . V o u s p o u v e z , s i v o u s l e désirez, r e m p l a c e r l a fonction u n e x p e c t e d p a r défaut p a r u n e fonction d e v o t r e cru.
I l suffit d'utiliser s e t _ u n e x p e c t e d avec c o m m e seul param è t r e un n o m de fonction respectant l'entête s u i v a n t :
PFV VotreNomDeFonction(void);

où P F V est un type défini p a r :
typedef void(*PFV) () ;

Ce qui c o r r e s p o n d à un p o i n t e u r sur u n e fonction qui ne
p r e n d a u c u n p a r a m è t r e e t qui r e t o u r n e v o i d .
Exemple complet :
#include <iostream.h>
#include <except.h>
// si PFV est inconnu, définissons-le
#ifndef PFV
typedef void( *PFV) ();
#endif
class MonErreur {};
class MonDilemne {};
class MaClasse
{
public :
void FonctionBoguee)) throw(MonDilemne);
};

void
MaClasse::FonctionBoguee() throw(MonDilemne)
{
throw MonErreur(); //n'est pas autorisé par l'entête
}

PFV
{

Monlnconnue()
Les exceptions

145

/ / n e doit pas retourner à son appelant
// doit sortir du programme
cout << "Je suis dans l'inconnue..." << endl;
exit(1);
}

void main()
{
try
{
MaClasse ob j ;
set_unexpected((PFV)Monlnconnue);
obj.FonctionBoguee();
}

catch (MonErreur)
{
cout << "Panique" << endl;
}
}

// affichage :
/ / J e suis dans l'inconnue

Exception non
interceptée

Il p e u t arriver q u ' u n e e x c e p t i o n l a n c é e ne c o r r e s p o n d e à auc u n bloc c a t c h qui suit l e bloc t r y . D a n s c e c a s , l a fonction
t e r m i n a t e est a p p e l é e . Par défaut, elle m e t fin a u p r o g r a m m e p a r u n appel à a b o r t ( ) . V o u s p o u v e z c e p e n d a n t
définir v o t r e p r o p r e fonction terminate, à l'aide de
s e t _ t e r m i n a t e . V o t r e fonction doit c o r r e s p o n d r e à l'entête
suivant (où P F V est défini c o m m e i n d i q u é ci-dessus) :
PFV VotreNomDeFonction(void);

V o i c i u n e x e m p l e d e fonction t e r m i n a t e p e r s o n n e l l e :
#include <iostream.h>
#include <except.h>
// si PFV est inconnu, définissons-le
#ifndef PFV
typedef void( *PFV ) ();
#endif
class MonErreur {};
class MonDilemne {};
class MaClasse
{
public :
void FonctionBoguee() throw(MonErreur);
146

Pont entre C et C++

) ;

void
MaClasse::FonctionBoguee()
{
throw MonErreur();

throw(MonErreur)

}

PFV

MonTerminator()

{

// ne doit pas lancer d'exception
// ne doit pas retourner à son appelant
cout << "On dirait qu'il y a un problème" << endl;
exit(1);
}

void main()
{
try
{
MaClasse obj;
set„terminate((terminate_function)MonTerminator) ;
obj.FonctionBoguee();
}

catch (MonDilemne.)
{
cout << "Panique" << endl;
}

}

// affichage :
// On dirait qu'il y a un problème

Exceptions
en cascade

Q u e s e passe-t-il q u a n d u n e e x c e p t i o n est l a n c é e d a n s u n
b l o c catch ? R é p o n s e en d e u x t e m p s :
Le b l o c A c o n t e n a n t le b l o c catch est s t o p p é .
Si ce bloc A était l u i - m ê m e d a n s un bloc try, l ' e x c e p t i o n
serait traitée p a r un d e s blocs catch c o r r e s p o n d a n t au
bloc try qui contient A.
L a b r u m e d e ces explications sera peut-être d i s s i p é e p a r
l ' e x e m p l e s u i v a n t : u n e e x c e p t i o n de classe MonDilemne est
l a n c é e d a n s un b l o c catch.
#include <iostream.h>
#include <except.h>
class MonErreur { ) ;
class MonDilemne {};
class MaClasse
{
Les exceptions

public :
void FonctionBoguee() throw(MonErreur);
} ;

void
MaClasse::FonctionBoguee() throw(MonErreur)
{
throw MonErreur(); // autorisé par l'entête
}
void fonction!)
{
try
{
MaClasse obj ;
obj.FonctionBoguee();
}
catch (MonErreur)
{
cout << "Erreur" << endl;
throw MonDilemne();
}
catch (MonDilemne)
{
// ce catch n'est pas appelé
cout << "Dilemne dans fonction" << endl;
}

}
void
{
try
{

main()

fonction!);

// déclenchera un MonDilemne

}

catch (MonDilemne)
{

// ce catch est appelé
cout << "Dilemne dans le main" << endl;
}
}

// affichage :
// Erreur
// Dilemne dans le main

147
La compilation séparée

Bien que la compilation séparée ne constitue pas une nouveauté du
C++, un chapitre lui est consacré car beaucoup de programmeurs
C en ignorent les principes. Par ailleurs, le C++ diffère légèrement
du C dans ce domaine.

Notions de base
L'idée

La c o m p i l a t i o n séparée p e r m e t de répartir le c o d e - s o u r c e
d ' u n e application d a n s plusieurs fichiers. C e t t e t e c h n i q u e
p r o c u r e u n e meilleure clarté d a n s l'organisation du projet.
V o u s g a g n e z aussi du t e m p s : seuls les fichiers m o d i f i é s d e puis la dernière c o m p i l a t i o n sont r e c o m p i l é s .
A v a n t d ' a b o r d e r la m i s e en œ u v r e de la c o m p i l a t i o n s é p a r é e ,
rappelons brièvement le fonctionnement d'un compilateur.
P r i n c i p e d'un c o m p i l a t e u r
D e u x g r a n d e s étapes sont n é c e s s a i r e s p o u r transformer votre
c o d e - s o u r c e en e x é c u t a b l e : la c o m p i l a t i o n et l'édition de
liens (parfois appelé « l i n k a g e » p a r certains fous qui refusent d ' e m p l o y e r les t e r m e s français officiels — mea culpa...).
La compilation d ' u n fichier C ou C + + d o n n e un fichier objet,
250

Pont entre C et C++

U n e « librairie » est
en quelque sorte un
ensemble de fichiers
.o que vous incluez à
l'édition des liens.

en général suffixe p a r . o. C e s fichiers . o sont e n s u i t e « liés »
entre e u x , é v e n t u e l l e m e n t avec d e s librairies*, p o u r former
l ' e x é c u t a b l e p r o p r e m e n t dit.
D a n s l ' e x e m p l e ci-dessous, d e u x fichiers s o u r c e s suffixes p a r
. c p p s o n t c o m p i l é s s é p a r é m e n t puis « linkés » a v e c u n e librairie i n d é p e n d a n t e p o u r d o n n e r l ' e x é c u t a b l e m a i n . e x e .
S i , après u n e p r e m i è r e compilation, v o u s n e modifiez q u e l e
fichier o u t i l s . c p p , v o u s n ' a u r e z b e s o i n d e r e c o m p i l e r q u e
o u t i l s . c p p ; v o u s p o u r r e z réutiliser l'ancien m a i n . o p o u r
l ' é d i t i o n des liens.

Mise en œuvre

P o u r réaliser effectivement u n e c o m p i l a t i o n s é p a r é e , il faut
savoir d e u x c h o s e s :
quoi m e t t r e , et d a n s quels fichiers
c o m m e n t lancer la c o m p i l a t i o n

Quoi mettre, et
dans quels
fichiers ?

U n projet C + + s e d é c o m p o s e g é n é r a l e m e n t e n b e a u c o u p
d ' é l é m e n t s de natures différentes : des c l a s s e s , d e s fonctions
globales (hors-classes), des c o n s t a n t e s , d e s structures, etc.
C o m m e n t savoir où placer c h a q u e é l é m e n t ?
La compilation séparée

On parlera ici de
fichiers .cpp pour
désigner les fichiers
contenant du codesource C + + . Votre
compilateur utilise
peut-être une autre
extension (.cxx ou .C

151

Principe général
D a n s la majorité d e s c a s , les fichiers fonctionnent p a r c o u ple : un fichier s u f f i x e p a r . h p o u r déclarer u n e classe ou d e s
entêtes de fonctions, et un fichier s u f f i x e p a r . cpp p o u r la
définition d e ces fonctions ( m e m b r e s o u n o n ) .
A d m e t t o n s q u e v o u s écriviez d a n s un fichier A q u e l c o n q u e .
Si v o u s a v e z b e s o i n d'utiliser u n e classe ou u n e fonction définie ailleurs, il faut inclure au d é b u t de A le fichier . h qui
déclare ce d o n t v o u s a v e z b e s o i n .
P o u r utiliser u n e autre i m a g e , le . h est l ' a m b a s s a d e u r de v o tre travail, il p r é s e n t e c o m m e n t utiliser ce q u e v o u s a v e z fait,
alors q u e le . cpp décrit c o m m e n t c'est implanté.

par exemple).

Conseils
La déclaration d ' u n e classe se fait g é n é r a l e m e n t d a n s un
fichier . h qui p o r t e s o n n o m .
La définition d e s f o n c t i o n s - m e m b r e s d ' u n e c l a s s e , ainsi
q u e la définition des c o n s t a n t e s et variables de classe
(static) se fait d a n s un fichier . cpp p o r t a n t le n o m de
la classe.
L e s objets, variables ou fonctions globales (c'est-à-dire accessibles d e p u i s n ' i m p o r t e quelle fonction d u p r o g r a m m e )
sont définis d a n s un fichier d o n t le n o m r e s s e m b l e à glo-

bal . cpp
La déclaration de ces objets g l o b a u x se fait d a n s un fichier
d o n t le n o m r e s s e m b l e à extern.h, où c h a c u n de ces
objets est p r é c é d é du m o t - c l é extern.
L e s fonctions hors-classe, C standard, figurent d a n s d e s
fichiers . c
La déclaration de ces fonctions se fait d a n s des fichiers . h
Si v o u s utilisez de n o m b r e u s e s classes, v o u s g a g n e r e z à reg r o u p e r plusieurs classes d a n s un seul fichier . h. Il est cep e n d a n t conseillé de g a r d e r un seul fichier . cpp p a r classe
— les corps des fonctions p r e n n e n t en effet plus de p l a c e q u e
les déclarations de classes.

V o u s trouverez ci-dessous u n s c h é m a qui r é s u m e ces c o n seils à travers un e x e m p l e . L e s listings c o m p l e t s sont situés
u n p e u plus loin d a n s c e chapitre.
252

Pont entre C et C++

Compilation

séparée

-

exemple

Remarques
A v e c l a c o m p i l a t i o n séparée, l e p r o g r a m m e u r est contraint
de respecter certaines règles :
C h a q u e fichier . c p p utilisant d e s é l é m e n t s d ' u n autre fichier doit inclure le fichier . h déclarant les é l é m e n t s en
question.
Ici, m a i n . c p p inclut les fichiers e x t e r n . h , f o n c _ c . h e t
C l a s s i . h , car i l v a utiliser d e s variables e t c o n s t a n t e s
La compilation séparée

153

globales déclarées d a n s e x t e r n . h , des fonctions C déclarées d a n s f o n c _ c . h , e t des classes d é c l a r é e s d a n s

Classl . h .
Q u a n d v o u s modifiez l'entête d ' u n e fonction d a n s u n fichier . cpp, il faut veiller à mettre à j o u r le fichier . h correspondant.
L ' o r d r e d'inclusion des fichiers . h est i m p o r t a n t . S i , p a r
e x e m p l e , v o u s utilisez un objet global de classe Classl
déclaré d a n s e x t e r n , h, il faut q u e p a r t o u t Classl. h
soit inclus a v a n t e x t e r n . h (sinon l e t y p e Classl sera
inconnu dans e x t e r n . h).
Listing
Voici le listing qui détaille l'architecture p r é s e n t é e d a n s le
s c h é m a de la p a g e p r é c é d e n t e :
// fichier main.cpp
#include "extern.h"
extern "C"
{
// voir compléments
#include "fonc_c.h"
}

#include "Classl.h"
void
main()
{
Classl
objetl;
_statut = 0;
}

// fichier global.cpp
// constantes globales
const float PI = 3.1415;
// variables globales
int
_statut;

Cette manière
d'opérer (avec
#ifndef) est expliquée
dans les compléments, page I 57.

// fichier extern.h
tifndef _EXTERN_H
#define _EXTERN_H
// constantes globales
extern const float
// variables globales
extern int
_statut;

PI;
754

Pont entre C et C++

#endif

// fichier Classl.h
#ifndef _CLASS1_H
#define _CLASS1_H
class Classl
{
protected:
// ...
public :
Classl ( ) ;
void FoncMemb ( ) ;
} ;

#endif

// fichier Classl.cpp
#include "Classl.h"
// constructeurs
Classl::Classl()
{
}

// autres fonctions-membre de Classl
void
Classl::FoncMemb()
{
}

// fichier fonc_c.h
#ifndef _FONC_C_H
#define _FONC_C_H
#define CONST_C
void

100

fonction_c(int a ) ;

#endif

// fichier fonc c.c
#include "fonc_c.h"
int fonction_c(int a)
{
return a;
}
La compilation séparée

Comment
lancer la
compilation ?

155

L e s m é t h o d e s d e c o m p i l a t i o n varient b e a u c o u p selon les s y s t è m e s . C e r t a i n s c o m p i l a t e u r s s ' o c c u p e n t de p r e s q u e tout : il
v o u s suffit d'indiquer quels fichiers font partie de v o t r e p r o jet. D ' a u t r e s en r e v a n c h e , r e p o s e n t sur l'utilitaire m a k e .
Make
C e t utilitaire v o u s p e r m e t lancer u n e c o m p i l a t i o n s é p a r é e
d ' u n e seule instruction. P o u r cela, il est n é c e s s a i r e d'écrire un
script de compilation, a p p e l é makefile. Ce script idendifie les
fichiers à c o m p i l e r , ainsi q u e les d é p e n d e n c e s entre ces fichiers. Si v o t r e makefile est écrit c o r r e c t e m e n t , seuls les fichiers n é c e s s a i r e s seront r e c o m p i l é s .
Le format d ' u n e paire de lignes d ' u n fichier makefile est le
suivant (ici, les crochets i n d i q u e n t des é l é m e n t s o p t i o n n e l s ) :
fichier_cible : fichier_l [fichier_2 ...]
instruction à exécuter

Si une ligne de votre
Makefile est t r o p
longue, vous pouvez
la couper en deux à
condition que la
première ligne se
termine par un
backslash ().

C e qui signifie : p o u r obtenir f i c h i e r _ c i b l e , j ' a i b e s o i n
d e f i c h i e r _ l , f i c h i e r _ 2 , etc. e t d ' e x é c u t e r l'instruction
de la d e u x i è m e ligne. M a k e en déduit q u e si l'un d e s fichiers
f i c h i e r _ l , f i c h i e r _ 2 , etc. est plus récent (au n i v e a u d e s
dates d e dernière modification) q u e f i c h i e r _ c i b l e , i l faut
e x é c u t e r l'instruction de la d e u x i è m e ligne. Ce format sert
p r i n c i p a l e m e n t à d e u x g e n r e s d'instructions (on s u p p o s e r a
q u e v o t r e c o m p i l a t e u r C + + s'appelle CC) :
•

c o m p i l e r un . c p o u r obtenir un . o
fichier.o : fichier.c fichier.h autre_fichier.h
CC -c fichier.c

C e s lignes signifient q u e s i f i c h i e r . c , f i c h i e r . h o u a u t r e _ f i c h i e r . h ont été modifiés à u n e date p o s t é r i e u r e d e
celle d e f i c h i e r . o , i l faut r e c o m p i l e r f i c h i e r . c p o u r o b tenir un n o u v e a u f i c h i e r , o. L ' o r d r e de c o m p i l a t i o n dép e n d b i e n e n t e n d u d e votre c o m p i l a t e u r .
En fait, on i n d i q u e après les : le n o m du fichier s o u r c e c o n cerné, ainsi q u e la liste d e s fichiers . h qu'il utilise.
156

Pont entre C et C++

• lier les . o et les librairies p o u r former l ' e x é c u t a b l e
exécutable : fichier.o autre_fichier.o lib.a
CC -o exécutable fichier.o autre_fichier.o -llib.a

C e qui v e u t d i r e : s i f i c h i e r , o , a u t r e _ f i c h i e r . o o u
l i b . a s o n t p l u s r é c e n t s q u e e x é c u t a b l e , i l faut refaire u n e
édition d e s liens. N o u s utilisons ici u n e librairie à titre
d ' e x e m p l e ; il est tout à fait p o s s i b l e q u e v o u s n ' e n n ' a y e z
p a s b e s o i n . Il faudrait d a n s ce cas écrire :
exécutable : fichier.o autre_fichier.o
CC -o exécutable fichier.o autre_fichier.o

Exemple
Voici l'allure du fichier makefile
l ' e x e m p l e p r é s e n t é p a g e 152.

permettant

de

compiler

# fichier makefile
appli : main.o global.o classl.o fonc_c.o
CC -o appli main.o global.o classl.o fonc_c.o
main.o : main.cpp extern.h fonc_c.h classl.h
CC -c main.cpp
global.o : global.cpp
CC -c global.cpp
classl.o : classl.cpp classl.h
CC -c global.cpp
fonc_c.o : fonc_c.c fonc_c.h
CC -c fonc_c.c

L a n c e r la c o m p i l a t i o n (enfin!)
A s s u r e z - v o u s q u e le fichier makefile est d a n s le r é p e r t o i r e où
sont situés les fichiers-sources de v o t r e projet, et t a p e z
make -f nom_fichier_makefile

Si v o u s n o m m e z le fichier makefile M a k e f i l e (avec un M
m a j u s c u l e ) , il v o u s suffira de taper m a k e p o u r lancer la
c o m p i l a t i o n s é p a r é e de votre projet.
La compilation séparée

157

N o t e z q u e certains s y s t è m e s a c c e p t e n t i n d i f f é r e m m e n t m a k e f i l e o u M a k e f i l e (avec o u s a n s m a j u s c u l e ) , d o n n a n t l a
préférence au fichier qui c o m m e n c e p a r u n e m a j u s c u l e si les
d e u x sont p r é s e n t s d a n s le répertoire courant.

Compléments
Comment
éviter les
redéclarations ?

L e c o m p i l a t e u r n ' a i m e p a s les déclarations m u l t i p l e s d ' u n
m ê m e é l é m e n t (objet, variable, c o n s t a n t e , fonction, etc.) Il
faut d o n c veiller à ne p a s inclure d e u x fois le m ê m e fichier
. h d a n s le m ê m e fichier. C e l a arrive facilement d a n s un c a s
de t y p e « p o u p é e s r u s s e s » :

E n traitant a p p l i . c p p , l e p r é - p r o c e s s e u r r e m p l a c e l a ligne
#include
" e x t e r n . h " p a r l e c o n t e n u d u fichier e x tern.h.
On
obtient d o n c
deux
lignes
#include
" toto . h", qui inclueront d e u x fois le fichier toto . h, ce qui
va g é n é r e r des erreurs de redéclaration.
Il existe un m o y e n s i m p l e d'éviter ces r e d é c l a r a t i o n s , tout en
p e r m e t t a n t l'inclusion m u l t i p l e du m ê m e . h : utiliser les directives d e p r é c o m p i l a t i o n # i f n d e f , # d e f i n e e t # e n d i f .
Le truc consiste à ne p r e n d r e en c o m p t e le c o n t e n u du . h
q u e la p r e m i è r e fois q u e le p r é c o m p i l a t e u r le r e n c o n t r e . Voici
un fichier . h qui utilise cette t e c h n i q u e :
// début du fichier extern.h
#ifndef _EXTERN_H
#define EXTERN_H
// corps complet du fichier
#endif
// fin du fichier extern.h
158

Pont entre C et C++

En fait, on associe à c h a q u e fichier . h un s y m b o l e p o r t a n t
s o n n o m (ici _EXTERN_H). A U début, c e s y m b o l e n ' e x i s t e
pas. La p r e m i è r e fois q u e le p r é c o m p i l a t e u r traite le fichier, il
r e n c o n t r e la ligne #ifndef _EXTERN_H. C o m m e le s y m b o l e _EXTERN_H n ' e x i s t e p a s , le p r é c o m p i l a t e u r traite le fichier j u s q u ' à rencontrer u n # e n d i f
(ou u n #elif
d'ailleurs). Le p r é - p r o c e s s e u r traite d o n c tout le c o r p s du fichier j u s q u ' à n o t r e #endif final.
D a n s cette partie d e c o d e prise e n c o m p t e , l a p r e m i è r e ligne
est
# de fi ne
_EXTERN_H,
qui
définit
le
symbole
_EXTERN_H, ce qui servira au p r o c h a i n p a s s a g e à r e n d r e la
c o n d i t i o n # i f n d e f _EXTERN_H fausse, et à éviter d'inclure
u n e n o u v e l l e fois l e fichier e x t e r n . h .

Static, inline et
portée

* C'est-à-dire en
dehors de t o u t e
fonction

Le g a i n de t e m p s et de clarté n ' e s t p a s le seul intérêt de la
c o m p i l a t i o n s é p a r é e . L a d é c o m p o s i t i o n e n p l u s i e u r s fichiers
a aussi d e s c o n s é q u e n c e s sur la p o r t é e de certaines v a r i a b l e s
et fonctions :
Static global
T o u t e fonction déclarée s t a t i c ainsi q u e toute v a r i a b l e déclarée s t a t i c e n global*, n ' e s t accessible q u e d a n s l e fichier
où elle est définie. E x e m p l e :
PORTÉE DE
VAR ET FONCI

On ne p e u t a c c é d e r ni à var, ni à foncl d e p u i s
f i c h 2 . cpp, car ils ont été déclarés static d a n s f i c h l . c p p .
Ils ne sont a c c e s s i b l e s q u ' à l'intérieur de f ichl. cpp.
La compilation séparée

159

Attention ! Ici, s t a t i c n ' a p a s l e m ê m e sens q u e lorsqu'il
qualifie les v a r i a b l e s - m e m b r e s ou f o n c t i o n s - m e m b r e s ! R a p p e l o n s q u e d a n s ces derniers c a s , s t a t i c signifie qu'il s'agit
de variables ou fonctions de classe, c o m m u n e s à tous les o b jets issus de la classe.
F o n c t i o n s inlines
S i v o u s déclarez u n e fonction i n l i n e e n d e h o r s d ' u n e
classe, la p o r t é e de celle-ci est a u t o m a t i q u e m e n t r é d u i t e au
fichier d a n s lequel elle est déclarée.
Constantes globales
U n e c o n s t a n t e déclarée en global n ' e s t visible q u e d a n s le fichier où elle est définie, à m o i n s de spécifier e x p l i c i t e m e n t
c o n s t d a n s l a décla'ration externe.
E x e m p l e 1, où PI est déclaré l o c a l e m e n t à f i c h l . c p p :

E x e m p l e 2, où PI est visible à la fois d a n s f i c h l . c p p et
fich2.cpp :
7 60

Pont entre C et C++

Travailler
avec d'autres
langages

L e C + + p e r m e t l ' u s a g e d e fonctions p r o v e n a n t d ' a u t r e s lang a g e s : le C b i e n e n t e n d u , m a i s aussi le P a s c a l , le Fortran, etc.
C o m m e c h a q u e l a n g a g e a d o p t e s a p r o p r e l o g i q u e d'édition
d e s liens ( n o t a m m e n t e n c e qui c o n c e r n e l a m a n i è r e d e
stocker les p a r a m è t r e s de fonctions sur la pile d ' e x é c u t i o n ) , il
faut l'indiquer d ' u n e m a n i è r e ou d ' u n e autre d a n s le c o d e
C + + . V o u s p o u v e z l e faire grâce à l a c l a u s e e x t e r n " n o m " ,
où nom représente un type d'édition de liens ( c o n s u l t e z la
d o c u m e n t a t i o n d e votre c o m p i l a t e u r ) .
Il existe trois m a n i è r e s d'utiliser e x t e r n "nom" :
extern

"nom" élément;

déclare que l'édition des liens de élément (un
entête de fonction ou une variable) se fait
selon la convention nom.

extern

"nom"{

bloc de déclarations;

Idem que ci-dessus, mais tous les éléments
déclarés dans bloc sont concernés.

}
extern "nom" {
#include "fichier.h"

tions faites dans fichier.h sont concernées.

Idem que ci-dessus, mais toutes les déclara-

)
Prenons l'exemple d'un programme C + + ayant besoin de
fonctions C. On utilise e x t e r n " C " :
// fichier sériai.h (C standard)
int
int

serial_output(int, unsigned char);
serial_input(int, unsigned char*);

// fichier main.cpp (C++) possibilité 1
extern "C" int serial_output(int, unsigned char);
extern "C" int serial_input(int, unsigned char*);
void main { /*...*/ }

Utilisons la d e u x i è m e possibilité p o u r déclarer les d e u x
fonctions sans répéter e x t e r n "C" :
// fichier main.cpp (C++) possibilité 2
extern "C"
{
int
int
}

serial_output(int, unsigned char);
serial_input(int, unsigned char*);
La compilation séparée

161

void main { /*...*/ }

Enfin, a d o p t o n s la dernière solution, la p l u s é l é g a n t e , la p l u s
é c o n o m i q u e , bref, la m e i l l e u r e d a n s n o t r e e x e m p l e :
// fichier main.cpp (C++) possibilité 3
extern "C"
{
#include "sériai.h"
}
void main { /*...*/ }

U t i l i s e r du c o d e C + + à partir du C
P o u r utiliser d u c o d e C + + à partir d ' u n c o d e C , s u i v e z
l'exemple suivant :
// fichier C
extern "C" double racine(double d)
{
// code C++
Math
monObjetMath;
return monObjetMath.racine(d);
}
void
main()
{
double res;
res = racine(22.);
}
Guide
de survie
Les concepts présentés dans les deux premières parties vous laissent peut-être perplexe : comment utiliser un tel arsenal pour venir à bout d'un problème
réel ? Cette partie expose quelques conseils de base
pour concevoir une application en
C++, ainsi
qu'une série de questions/réponses sur le langage.
Conseils

Ce chapitre aborde quelques conseils de programmation orientéeobjets en C++. Il ne s'agit pas d'énoncer des règles d'or, mais plutôt de donner quelques pistes pour débuter.
Par la
suite,
l'expérience aidant, vous pourrez concevoir et coder dans un style
qui ne vous sera dicté par personne...

Les étapes d'un projet
U n projet i n f o r m a t i q u e p e u t s e d é c o m p o s e r e n q u a t r e p h a s e s
grossières :
1.
2.
3.
4.

D é t e r m i n a t i o n du cahier des c h a r g e s fonctionnel
Conception
Codage
Maintenance

M a l g r é tout le soin p o u r les séparer, ces p h a s e s ont t e n d a n c e
à se m é l a n g e r de m a n i è r e subtile. C ' e s t p o u r q u o i les tentativ e s d e formalisation d e l a p r o g r a m m a t i o n ont s o u v e n t
é c h o u é , car il s'agit là d ' u n e activité p r o f o n d é m e n t h u m a i n e ,
v o i r e artistique.
166

Pont entre C et C++

L a p r e m i è r e p h a s e consiste à d é t e r m i n e r c e q u e v o u s (ou v o s
clients) a t t e n d e n t de l'application à d é v e l o p p e r . Il est en effet
capital d e n e j a m a i s p e r d r e d e v u e les b e s o i n s a u x q u e l s l e
p r o g r a m m e doit r é p o n d r e . M a l h e u r e u s e m e n t (ou h e u r e u s e m e n t ) , c e s b e s o i n s s o n t susceptibles d ' é v o l u e r tout a u l o n g
d u projet, m e t t a n t e n c a u s e c e qui était c o n s i d é r é c o m m e acquis d è s l e début. N o u s n e n o u s p e n c h e r o n s p a s p l u s sur
cette p h a s e qui n e d é p e n d p a s d u C + + .
A t t a r d o n s - n o u s m a i n t e n a n t sur l a c o n c e p t i o n p r o p r e m e n t
dite.

Conception
La c o n c e p t i o n orientée-objets consiste e s s e n t i e l l e m e n t à
trouver les classes qui m o d é l i s e n t v o t r e p r o b l è m e et les relations qui se tissent entre elles.

Trouver les
classes

C ' e s t u n e p h a s e essentielle. Pourtant, il est difficile de d o n n e r
d e s c o n s e i l s g é n é r a u x tant les solutions d é p e n d e n t d u p r o b l è m e . O n isole p l u s facilement les classes q u a n d o n p r e s s e n t
(du v e r b e pressentir) les relations qui les u n i r o n t ; aussi la
lecture de la section suivante pourra-t-elle v o u s aider.
Q u e l q u e s r e m a r q u e s : u n e classe p e u t être v u e c o m m e u n e
entité, u n e idée a u n i v e a u d e votre projet. R a p p e l o n s q u ' u n e
classe est un tout, formé de d o n n é e s et de fonctions qui traitent ces d o n n é e s . P o u r créer u n e c l a s s e , il faut d o n c q u e d e s
liens étroits existent entre ses c o m p o s a n t s . Si cela n ' e s t p a s le
cas, il se p e u t q u ' e l l e se d é c o m p o s e en s o u s - c l a s s e s .
Un autre m o y e n consiste à formuler le p r o b l è m e en français.
V o u s a u r e z alors d e b o n n e s c h a n c e s p o u r q u e les n o m s
c o m m u n s r e p r é s e n t e n t des objets, e t q u e les v e r b e s s y m b o l i sent d e s f o n c t i o n s - m e m b r e s (c'est l a m é t h o d e d e B o o c h ) .
À ce n i v e a u de c o n c e p t i o n , ne p r e n e z p a s en c o m p t e les classes liées à l ' i m p l é m e n t a t i o n , m a i s c o n c e n t r e z - v o u s sur celles
qui r é p r é s e n t e n t d i r e c t e m e n t la réalité du p r o b l è m e . Inutile,
Conseils

167

p a r e x e m p l e , de p e n s e r à des listes ou des tableaux à ce niveau.

Architecturer
les classes

C ' e s t p r a t i q u e m e n t la p h a s e la p l u s difficile. Il existe princip a l e m e n t quatre sortes de relations entre d e u x c l a s s e s A et
B:
1.
2.
3.
4.

A
A
A
A

est u n e sorte de B
est c o m p o s é de B
utilise B
n ' a a u c u n lien avec B (eh oui!)

La différence entre les points d e u x et trois n ' e s t p a s toujours
é v i d e n t e , m a i s n o u s allons voir cela d a n s les p a r a g r a p h e s qui
suivent. N o u s ne détaillerons p a s le dernier point, d o n t le
rôle est s i m p l e m e n t d e v o u s rappeler q u ' i l n e faut p a s a b s o l u m e n t s ' a c h a r n e r à trouver un lien entre d e u x c l a s s e s .
A est u n e s o r t e de B
Il s'agit d ' u n e relation d'héritage. La f o r m u l a t i o n « est u n e
sorte de » fonctionne très b i e n p o u r l'identifier. Si m a l g r é
tout cela ne suffit p a s , il faut p e n s e r q u e la classe d é r i v é e (ici
A) p e u t être u n e spécialisation de la classe de b a s e .
D a n s certains cas, u n e relation n e p e u t p a s s e m o d é l i s e r s o u s
forme « est u n e sorte de », b i e n q u ' i n t u i t i v e m e n t v o u s sentiez q u ' i l existe u n e relation d'héritage entre les d e u x . Il est
alors p o s s i b l e q u e les d e u x entités soient « s œ u r s », c'est-àdire q u ' e l l e s aient des p o i n t s c o m m u n s et héritent toutes
d e u x d ' u n e m ê m e classe d e b a s e .
Exemple
Q u e l l e s sont les relations entre camion et voiture ? U n e v o i t u r e
« est-elle u n e sorte » de c a m i o n ou est-ce l'inverse ? D a n s ce
c a s , s e l o n l e p r o b l è m e , o n p e u t p e n c h e r p l u t ô t p o u r l a solution « un c a m i o n est u n e sorte de v o i t u r e », car les v o i t u r e s
s o n t p l u s fréquentes, et elles ont été i n v e n t é e s a v a n t les c a m i o n s . L e s c a m i o n s sont d o n c u n e spécialisation d e s v o i t u res. On p e u t aussi créer u n e classe véhicule, ou v é h i c u l e
terrestre, qui servira de classe de b a s e à v o i t u r e et c a m i o n .
168

Pont entre C et C++

A est c o m p o s é de un ou p l u s i e u r s B
C e t t e relation r e p r é s e n t e u n objet qui p e u t s e d é c o m p o s e r e n
d'autres objets, qui p o s s è d e n t c h a c u n leur vie p r o p r e . D e tels
objets B sont déclarés c o m m e d o n n é e s - m e m b r e s de l'objet
principal A .
Exemple
U n e v o i t u r e est c o m p o s é e d ' u n m o t e u r e t d e q u a t r e p n e u s .
C e m o t e u r e t ces p n e u s sont des objets qui p e u v e n t e x i s t e r
indépendamment.
C e n ' e s t v i s i b l e m e n t p a s u n e relation d ' h é r i t a g e , car u n p n e u
(ou un m o t e u r ) n ' e s t p a s « u n e sorte de » v o i t u r e .

A utilise B
Il existe d e u x m a n i è r e s de représenter cette relation en t e r m e
de l a n g a g e orienté-objet. Soit l'objet B est défini c o m m e donn é e - m e m b r e de l'objet A, soit A utilise d e s objets B g l o b a u x
o u p a s s é s c o m m e p a r a m è t r e s d e fonctions.
P a r r a p p o r t à la relation p r é c é d e n t e (A est c o m p o s é de B ) , la
n u a n c e se situe au n i v e a u du s e n s : ici, les objets B ne font
Conseils

169

p a s partie d e l'objet A . Ils sont c o m p l è t e m e n t i n d é p e n d a n t s ,
m a i s s o n t utilisés p a r A p o u r r é s o u d r e un p r o b l è m e .
Exemple
Dans
un
environnement
graphique,
le
gestionnaire
d ' é v é n e m e n t s utilise les informations en p r o v e n a n c e de la
souris. Il n ' e s t ni c o m p o s é d ' u n e souris, p a s p l u s qu'il n ' e s t
l u i - m ê m e u n e sorte d e souris.

B i e n e n t e n d u , en t e r m e d ' i m p l é m e n t a t i o n , il se p e u t q u e la
classe g e s t i o n n a i r e d ' é v é n e m e n t ait c o m m e d o n n é e - m e m b r e
un objet de classe souris. M a i s il se p e u t é g a l e m e n t qu'il
c o m m u n i q u e a u t r e m e n t avec l'objet souris, p a r e x e m p l e par
l'intermédiaire d ' u n e autre classe qui centralise les entrées.

Détailler
les classes

L e s classes sont c o n n u e s et leurs relations identifiées. Il faut
m a i n t e n a n t détailler les f o n c t i o n s - m e m b r e s q u ' e l l e s contiennent. Ce qu'il est b o n de g a r d e r à l'esprit :
M i n i m i s e r les é c h a n g e s e n t r e les c l a s s e s
P l u s v o s classes sont i n d é p e n d a n t e s , m o i n s elles é c h a n g e n t
d'informations a v e c les autres, et p l u s facile sera la m a i n t e n a n c e d e v o t r e projet. R é d u i s e z autant q u e p o s s i b l e l e n o m bre de p a r a m è t r e s d a n s les f o n c t i o n s - m e m b r e s .
En dévoiler un m i n i m u m
D a n s l e m ê m e ordre d'idées, u n e classe doit s'efforcer d e cac h e r un m a x i m u m de ses p r o p r e s d o n n é e s , et surtout la manière d o n t ces d o n n é e s sont s t o c k é e s . I m a g i n e z toujours ce
qui se passerait si v o u s c h a n g i e z la m a n i è r e de r e p r é s e n t e r
ces d o n n é e s . L'interface avec l'extérieur, c'est-à-dire les fonctions p u b l i c , n e doit p a s c h a n g e r — e n tout c a s aussi p e u
que possible.
2 70

Pont entre C et C++

Évaluer
l'ensemble

C o n f r o n t e z plusieurs scénarios à votre architecture de class e s , e t vérifiez q u e tout p e u t être m i s e n œ u v r e . V o u s g a g n e rez à p a s s e r en r e v u e les fonctionnalités a t t e n d u e s ( p h a s e 1).
Si v o t r e c o n c e p t i o n s'avère trop c o m p l e x e à utiliser, r e m e t tez-là en c a u s e et e s s a y e r d ' e n i m a g i n e r u n e autre. Il faut savoir q u e s i v o u s a v e z des p r o b l è m e s d e c o m p r é h e n s i o n d e
l'architecture à ce n i v e a u , ils ne feront q u e s'amplifier à
l'étape suivante.

Codage C++
* C'est une façon
de parler.

Quels outils ?

V o t r e projet est détaillé sur le papier, il ne reste plus* q u ' à le
p r o g r a m m e r . A v e c l e C + + , v o u s d i s p o s e z d e n o m b r e u x outils d o n t l'utilisation s i m u l t a n é e n ' e s t p a s toujours é v i d e n t e .
Ce p a r a g r a p h e va tenter de v o u s faciliter la tâche.
C e t t e é t a p e consiste à choisir les classes-outils ou les bibliot h è q u e s q u e v o u s utiliserez p o u r m e t t r e e n œ u v r e v o t r e c o n ception. C e n e s o n t p a s d e s classes c o n c e p t u e l l e s , m a i s d e s
classes p r o p r e s a u x t e c h n i q u e s d e p r o g r a m m a t i o n . Par
e x e m p l e , toutes les classes « c o n t e n e u r » (tableaux, listes, arb r e s , g r a p h e s ) en font partie. Elles servent à s t o k e r d'autres
objets.
Si v o u s d e v e z c o n c e v o i r d e s classes-outils p o u r votre projet,
p e n s e z à la réutilisabilité, et faites en sorte q u ' e l l e s soient
aussi universelles q u e possible. V o u s a u r e z peut-être p l u s d e
m a l , m a i s v o u s g a g n e r e z d u t e m p s sur v o t r e p r o c h a i n projet.
B i e n e n t e n d u , c e g e n r e d e classe-outils n é c e s s i t e u n e d o c u m e n t a t i o n : c o m m e n t e z - l e s ou décrivez leur f o n c t i o n n e m e n t
à part, si p o s s i b l e a v e c des e x e m p l e s d'utilisation.
Factoriser des classes
R e p r e n e z votre g r a p h e d'héritage. V o u s p o u v e z e s s a y e r d e
trouver d e s p o i n t s c o m m u n s entre c l a s s e s , s u f f i s a m m e n t
n o m b r e u x p o u r former u n e classe d e b a s e . L ' a v a n t a g e d ' u n e
Conseils

171

telle factorisation réside d a n s la m i n i m i s a t i o n d e s r e d o n d a n c e s . D o n c , les modifications éventuelles n ' a u r o n t à se faire
q u ' à un endroit si elles c o n c e r n e n t la classe de b a s e . C e t t e
é t a p e ne figure p a s d a n s la c o n c e p t i o n , car il s'agit là d ' u n e
m é t h o d e d e p r o g r a m m a t i o n qui n ' a p a s f o r c é m e n t d e s e n s
conceptuel.

* Rappelons que le
C+ + fart la
conversion de
Dérivée* vers Base*.

Mise en œuvre
de la
réutilisabilité

Exemple
Vous concevez un environnement graphique composé de
divers objets : fenêtres, b o u t o n s , etc. Si c h a q u e objet c o m p o r t e u n identifiant, u n e référence vers l'objet p a r e n t , e t j e n e
sais q u o i d'autre, il e s t j u d i c i e u x de c r é e r u n e classe ObjetG e n e r i q u e qui r e g r o u p e r a tous ces p o i n t s c o m m u n s . Ici n o u s
bénéficions d ' u n autre atout : tous les objets fenêtres, b o u tons et autres p o u r r o n t êtres d é s i g n é s p a r un p o i n t e u r s u r
ObjetGenerique*.

L ' u n d e s c h e v a u x d e bataille d e s l a n g a g e s orientés-objets est
l a réutilisabilité d u c o d e produit. L e C + + est p a r t i c u l i è r e m e n t
b i e n a r m é p o u r m e n e r à b i e n cette croisade. O u t r e l'héritage
q u e n o u s a v o n s détaillé d a n s la partie c o n c e p t i o n , p a r l o n s ici
du p o l y m o r p h i s m e , de la généricité et des e x c e p t i o n s .
L e p o l y m o r p h i s m e e t les f o n c t i o n s v i r t u e l l e s
U n e fonction virtuelle doit garder le m ê m e s e n s d a n s toute la
b r a n c h e d'héritage. U n e x e m p l e parfait serait u n e fonction d e
rotation virtuelle définie p o u r un objet g r a p h i q u e de b a s e .
U n b o n m o y e n d ' i m p o s e r u n m o u l e p o u r u n e série d e c l a s s e s
consiste à créer u n e classe de b a s e abstraite, où des fonctions
virtuelles p u r e s sont déclarées. C e g e n r e d e c l a s s e fixe u n c a dre général d'utilisation : les classes dérivées d o i v e n t définir
les fonctions virtuelles p u r e s , en r e s p e c t a n t leur entête.
N o t r e e x e m p l e s'appliquerait b i e n à cela : u n e classe abstraite
ObjetVirtuel, qui ne créerait d o n c a u c u n objet, m a i s qui définirait les entêtes des fonctions affichage, rotation, etc. On
i m a g i n e facilement q u e des classes R e c t a n g l e o u C e r c l e héritent d'ObjetVirtuel (voir s c h é m a p a g e suivante).
172

Pont entre C et C++

La généricité et les templates
Les templates permettent de paramétrer des classes par des
types de d o n n é e s , ces derniers p o u v a n t être d'autres classes.
L e s c o n t e n e u r s (les classes de s t o c k a g e ) sont tout i n d i q u é s
p o u r utiliser les templates. U n b o n exercice serait d e c o n c e voir u n e classe Liste template, qui accepterait c o m m e param è t r e le type des objets q u ' e l l e stocke.
Les exceptions
Q u a n d v o u s c o n c e v e z u n e classe, v o u s n e s a v e z p a s forcém e n t qui v a l'utiliser, n i q u a n d o n v a l'utiliser. E n a d o p t a n t
l e m é c a n i s m e d ' e x c e p t i o n , v o u s p o u v e z signaler des erreurs
( t h r o w ) à l'utilisateur de la classe qui agira en c o n s é q u e n c e s
( t r y / c a t c h ) . D o c u m e n t e z e t signalez c l a i r e m e n t les c l a s s e s
d ' e x c e p t i o n s q u e v o u s p o u v e z lancer. L ' u n d e s m o y e n s c o n siste à faire suivre l'entête d ' u n e fonction p a r les e x c e p t i o n s
q u ' e l l e est susceptible de lancer (voir p a g e 1 4 3 ) .

Implantation
des classes

E n c o n c e v a n t e t c o d a n t u n e classe C + + , i l est intéressant d e
c o n s i d é r e r q u e l q u e s points :
Respectez la cohérence des fonctions d'accès
U n e classe c o m p t e très s o u v e n t d e s fonctions d ' a c c è s p o u r
consulter ou modifier d e s d o n n é e s - m e m b r e s (à m o i n s q u e
Conseils

173

v o u s n ' a y e z déclaré des d o n n é e s - m e m b r e s public, m a i s
v o u s devriez écarter d ' e m b l é e cette possibilité). P e n s e z à
garder u n e c o h é r e n c e p o u r distinguer ces fonctions d e s a u tres. Faites p a r e x e m p l e p r é c é d e r les fonctions de c o n s u l t a tion p a r g e t _ e t les fonctions d e modification p a r s e t _ , e n
les faisant suivre du libellé exact des d o n n é e s - m e m b r e s . Prat i q u e m e n t tous les e x e m p l e s de ce livre r e s p e c t e n t ce format.
R e n d e z « const » les fonctions qui ne m o d i f i e n t rien
S i u n e f o n c t i o n - m e m b r e d ' u n e classe n e modifie a u c u n e
d o n n é e - m e m b r e , p e n s e z à l a r e n d r e c o n s t . V o u s garantissez ainsi a u x utilisateurs de votre classe q u e l'objet ne sera
p a s modifié en faisant appel à cette fonction.
Tous les exemples de
ce livre ne respectent
pas cette convention,
par souci d'allégement
et de clarté.

class
Awesome
{
protected:
int a;
public :
int get_a() const { return a; };
} ;

Préférez protected à private
R a p p e l o n s q u e des d o n n é e s private ne s o n t p a s héritées.
L e s d o n n é e s protected, en r e v a n c h e , s o n t accessibles a u x
classes dérivées m a i s restent inaccessibles a u x autres c l a s s e s .
A u s s i est-il g é n é r a l e m e n t plus intéressant d'utiliser pro-

tected.
Respectez la symétrie des constructeurs et destructeurs
Si un c o n s t r u c t e u r alloue de la m é m o i r e ou d e s r e s s o u r c e s
diverses, faites en sorte q u e le destructeur les libère. Attention : r a p p e l e z - v o u s q u e la s y n t a x e de delete est différente
p o u r u n e s i m p l e variable (delete v a r ) q u e p o u r u n ta-

b l e a u (delete [ ] t a b ) .
I n i t i a l i s e z t o u t e s les d o n n é e s - m e m b r e s , d a n s c h a q u e c o n s tructeur
Il n ' y a rien de p l u s d é s a g r é a b l e q u e de confier la tâche
d'initialisation à un c o n s t r u c t e u r irresponsable. Le rôle du
Ï74

Pont entre C et C++

c o n s t r u c t e u r est de p r é p a r e r un n o u v e l objet, et ce rôle c o m p r e n d son initialisation.
F a u t - i l r e d é f i n i r le c o n s t r u c t e u r c o p i e ?
Il suffit q u ' u n e d o n n é e - m e m b r e soit un p o i n t e u r et v o u s aurez c e r t a i n e m e n t b e s o i n d u c o n s t r u c t e u r c o p i e . C e dernier s e
c h a r g e d'initialiser un n o u v e l objet à partir d ' u n objet de
m ê m e classe déjà existant. R a p p e l o n s l e format d ' u n c o n s tructeur c o p i e :
NomClasse::NomClasse(const NomClass &a_copier)
{/*...*/}

L e c o n s t n ' e s t p a s obligatoire, m a i s fortement conseillé :
v o u s viendrait-il à l'idée de modifier l'objet à c o p i e r ?
Faut-il r e d é f i n i r l ' o p é r a t e u r d ' a f f e c t a t i o n = ?
Si l ' u n e d e s d o n n é e s - m e m b r e s est un pointeur, redéfinissez
l'opérateur = p o u r v o u s a s s u r e r q u e les é l é m e n t s d é s i g n é s
p a r le p o i n t e u r s o n t b i e n r e c o p i é s . A t t e n t i o n : l ' o p é r a t e u r =
n ' e s t p a s hérité p a r les sous-classes !
F a u t - i l r e d é f i n i r d e s o p é r a t e u r s de c o m p a r a i s o n ?
D a n s d e n o m b r e u x c a s , i l v a u t m i e u x q u e v o u s redéfinissiez
l'opérateur de c o m p a r a i s o n = = . Si v o u s êtes a m e n é s à trier
d e s objets de votre classe, redéfinissez é g a l e m e n t < ou >, car
o n n ' e s t j a m a i s m i e u x servi q u e p a r s o i - m ê m e . V o u s s a u r e z
ce s u r quoi la c o m p a r a i s o n s'effectue : a d r e s s e d e s objets, ou
b i e n v a l e u r d ' u n identifiant, o u e n c o r e é q u i v a l e n c e l o g i q u e ,
etc.
Questions-Réponses

Si vous n'avez pas trouvé les réponses à vos questions dans les
chapitres précédents ou dans l'index, tentez votre chance ici. Même
si vous ne vous posez pas de question, il peut être instructif de lire
les
réponses...
Q u ' e s t - c e q u ' u n c o n s t r u c t e u r par défaut ?
C ' e s t u n c o n s t r u c t e u r qui n e p r e n d a u c u n p a r a m è t r e , o u
d o n t les p a r a m è t r e s p o s s è d e n t tous u n e v a l e u r p a r défaut. L e
C + + g é n è r e u n c o n s t r u c t e u r p a r défaut s i v o u s n ' a v e z défini
a u c u n constructeur.
Un c o n s t r u c t e u r p a r défaut est appelé « en silence » p o u r les
objets qui ne s o n t p a s e x p l i c i t e m e n t initialisés. Il est é g a l e m e n t a p p e l é q u a n d v o u s allouez un n o u v e l objet a v e c new
obj ; ou un tableau a v e c new obj [TAILLE] ;.
C o m m e n t appeler un constructeur d'une classe de base
dans le constructeur d'une classe dérivée
Utilisez u n e liste d'initialisation (plus de détails p a g e 4 7 ) .
class Dérivée : public Base
{
public :
Dérivée ( ) : B a s e O { /* ... */ }
} ;
î 76

Pont entre C et C++

J ' a i r e d é f i n i l ' o p é r a t e u r = m a i s il n ' e s t pas a p p e l é d a n s la
d é c l a r a t i o n Classe obj2 = objl; Q u ' a i - j e fait de m a l ?
R i e n . D a n s l ' e x e m p l e cité, o b j 2 est initialisé avec un autre
objet ( o b j 1 ) . D a n s c e c a s , c e n ' e s t p a s l'opérateur = q u i est
a p p e l é , m a i s l e c o n s t r u c t e u r copie. L e rôle d e c e dernier c o n siste p r é c i s é m e n t à initialiser un objet à partir d ' u n autre o b jet existant.
Il s'agit ici d ' u n e initialisation de variable, et n o n d ' u n e affectation, qui serait réalisée p a r l'opérateur -.
P o u r q u o i l ' o p é r a t e u r « doit-il être d é c l a r é f r i e n d ?
Il ne doit p a s forcément être déclaré f r i e n d , m a i s cela facilite
les c h o s e s . Q u e l q u e s explications : l'opérateur « s ' a d r e s s e à
l'objet c o u t d e l a classe o s t r e a m . C o m m e n o u s n e p o u v o n s
p a s ajouter notre o p é r a t e u r « d i r e c t e m e n t d a n s la classe
o s t r e a m , il faut surcharger l'opérateur « global. O r , ce
dernier n ' a p a s accès a u x d o n n é e s c a c h é e s d e notre classe.
D e u x solutions s'offrent à n o u s : déclarer n o t r e o p é r a t e u r «
f r i e n d d a n s notre classe, o u n e p a s l e faire [ha h a ] . D a n s c e
dernier c a s , l e c o r p s d e l'opérateur < < d e v r a s e c o n t e n t e r
d'utiliser les fonctions p u b l i q u e s de notre classe.
Voici comment se passer du f r i e n d :
#include <iostream.h>
class NomClasse
{
private:
int n;
public :
NomClasse(int i) : n(i) {)
int get_n() const { return n; }
// vous voyez, on se passe du friend :
// friend ostream &operator<<
//
(ostream& out, const NomClasse& obj);
} ;

// nous devons utiliser get_n() au lieu de n :
ostream &operator<< (ostreamk out, const NomClassek obj)
{ return out << '[' << obj.get_n() << ' ] ' << endl; }
void
main()
{
NomClasse

obj1(1), obj2(2);
Questions-Réponses

177

cout << objl << obj 2;
}

// affichage:
// [1]
// [2]

P o u r q u o i u t i l i s e r cout et c i n au l i e u de p r i n t f et s c a n f ?
E n utilisant c o u t , c i n , c e r r e t les autres objets définis d a n s
i o s t r e a m . h , v o u s bénéficiez d e plusieurs a v a n t a g e s :
1. V o u s n ' a v e z p a s b e s o i n de préciser le t y p e d e s objets q u e
v o u s utilisez (plus de % d o n c m o i n s d'erreurs p o s s i b l e s ) ,
car c o u t , c i n e t c e r r intègrent les vérifications d e type !
2. V o u s p o u v e z redéfinir « et » p o u r qu'ils traitent v o s
classes. D e cette m a n i è r e , elles s'utiliseront c o m m e les typ e s prédéfinis, ce qui est un g a g e de c o h é r e n c e .
3. printf et s c a n f sont c o n n u s p o u r leur lenteur, d u e à
l ' a n a l y s e s y n t a x i q u e qu'ils sont obligés d ' o p é r e r sur la
c h a î n e d e format. R a s s u r e z - v o u s , c o u t e t ses frères s o n t
plus rapides.
4 . Utiliser c o u t a u lieu d e p r i n t f est plus chic.
Q u e l est l'intérêt d e d é c l a r e r d e s o b j e t s a u b e a u m i l i e u
d'une fonction plutôt qu'au début ?
P e r s o n n e l l e m e n t , je préfère déclarer tous les objets au d é b u t
d ' u n e fonction. C e l a évite de parcourir tout le c o d e p o u r
trouver un objet précis. M a i s les déclarations tardives ont
leur c h a r m e : elles p e u v e n t accélérer le p r o g r a m m e . En effet,
la création d ' u n objet est c o û t e u s e p u i s q u ' i l faut allouer la
m é m o i r e e t appeler l e constructeur. I m a g i n e z u n e fonction
c o m p o r t a n t un bloc A e x é c u t é u n e fois sur cent. Si A utilise
dix objets, v o u s a u r e z intérêt à les déclarer d a n s A plutôt
q u ' a u d é b u t de la fonction.
C e l a dit, c'est u n e q u e s t i o n de g o û t p e r s o n n e l .
C o m m e n t faire p o u r a l l o u e r n octects a v e c n e w ?
char
*pointeur;
pointeur = new char[n];

// alloue n octets
1 78

Pont entre C et C++

Dois-je b a n i r friend de ma religion ?
O u i , autant q u e possible. N ' u t i l i s e z f r i e n d q u e s i v o u s n e
p o u v e z p a s raisonnablement faire a u t r e m e n t . P a r e x e m p l e ,
i m a g i n o n s q u ' u n e classe p r é c i s e (et u n e seule) a b e s o i n
d ' a c c é d e r a u x d o n n é e s c a c h é e s d ' u n e autre classe. D a n s c e
cas d e figure, f r i e n d garantit q u e seule l a c l a s s e v o u l u e
p e u t a c c é d e r a u x d o n n é e s c a c h é e s . C e l a évite d ' a v o i r à déclarer d e s fonctions d'accès « p u b l i q u e s » qui ne serviraient
q u ' à u n e seule autre classe, et q u e les autres d e v r a i e n t i g n o rer.
Q u e s i g n i f i e c o n s t d a n s u n e d é f i n i t i o n du s t y l e void f()
const {I* ... */ i ?
L e m o t clé c o n s t , inséré ici entre les p a r a m è t r e s d ' u n e fonction et s o n corps, signifie q u e cette fonction ne modifie p a s
les d o n n é e s - m e m b r e s de la classe à laquelle elle appartient :
class
Awesome
{
protected:
int a ;
public :
int get_a() const { return a; };
};

D a n s cet e x e m p l e , la fonction g e t _ a ( ) ne modifie p a s la
d o n n é e - m e m b r e a . Elle peut d o n c être déclarée c o n s t .
Q u e l l e est la capitale du K a z a k h s t a n ?
Alma-Ata.
P o u r q u o i d i a b l e n ' a r r i v é - j e pas à a c c é d e r a u x d o n n é e s de
ma c l a s s e de b a s e d e p u i s ma c l a s s e d é r i v é e ?
V o u s avez peut-être déclaré ces d o n n é e s p r i v a t e a u lieu d e
p r o t e c t e d . R a p p e l o n s q u e les d o n n é e s p r i v a t e n e sont
p a s accessibles d a n s les classes dérivées. Il se p e u t é g a l e m e n t
q u e votre héritage soit p r i v a t e o u p r o t e c t e d a u lieu d e
p u b l i c . V o y e z l e chapitre sur l'héritage, s p é c i a l e m e n t
page 39.
Index

Symboles

«
surcharger 92
surcharger 57

»
surcharger 92
[]

comment allouer n octets? 177
new et delete 83
amies
classes et fonctions 123

B
base
classe de... 38
bases du C + + 11
C

surcharger 142
A
accès
aux membres dans un héritage
39
affectation
opérateur= 57
affichage
cout, cin et cerr 89
formater 94
surcharger « 92
allocation mémoire

catch 138
cerr 89
surcharger « 92
cin 89
surcharger » 92
classe 13
abstraite de base 179
amie 123
comment les architecturer? 167
comment les trouver? 166
conseils d'implantation 172
182

Pont entre C et C++

conversion de pointeurs 104
de base 38
déclaration 25
définir 15
dérivée 38
différences avec les objets 14
template 119
commentaires 63
ruse de sioux 64
compilateur
principes de base 149
compilation séparée 149
conseils 151
make 155
conception
conseils 166
conseils
de codage 170
de conception 166
constantes 67
et compilation séparée 159
paramètres 70
propres à une classe 71
constructeurs 27
copie 31
et héritage 43
ordre d'appel dans un héritage
multiple 129
par défaut 175
vue d'ensemble 27
conversion
constructeur 35
de classe vers un type 61
de type vers une classe 35
dérivée* vers base* 42; 104
copie
constructeur 31
par défaut 58
cout 89
surcharger « 92
tableau récapitulatif 99
D
dec 97

déclaration
de classe 25
n'importe où 73
définir
classe 15
fonction-membre 16
delete 83
syntaxe 84
démarrer
en C + + 20
dérivée
classe... 38
destructeurs 27
et héritage 44
virtuels 106
vue d'ensemble 27
E

encapsulation 14
erreurs
gestion des... 137
exceptions 137
créer ses propres... 140
en boucle 146
intercepter plusieurs... 139
non interceptée 145
spécifier dans l'entête 143
extern "nom" 160
F

fichiers
séparation en plusieurs... 149
fonctions
amies 123
définir fonction-membre 16
inline 21
redéfinition de fonction-membre
41
retournant une référence 115
spécifier les exceptions dans un
entête 143
templates 118
virtuelles 101
virtuelles pures 107
Index

formater les sorties 94
friend
classes et fonctions 123
et o p é r a t e u r « 176
et religion 178
G

généricité 117
guide de survie 163
H

héritage
accès aux membres 39
conseils 167
conversion de pointeurs 104
et constructeurs 43
et destructeurs 44
exemple complet 45
multiple 127
multiple, duplication de données 131
multiple, masquage de la virtualité 133
résumé 42; 78
simple 37
virtuel 135
hex 97

I
implantation
conseils 172
initialisation
listes 47
inline 21
iomanip.h 94
ios::fixed 98
ios::left 95
ios::right 95
ios::scientific 98
ios::showbase 96
ios::showpoint 96
ios::showpos 96
iostream.h Voir cout et cin

183

L

langages
travailler avec d'autres... 160
listes d'initialisations 47
M
make 155
malloc
la fin du... 83
modèles Voir templates
N
new 83
syntaxe 84
notation scientifique 97
O
objet
accéder à la partie public 17
créer 17
différences avec les classes 14
pourquoi comparer deux objets
identiques 61
provisoire et références 115
oct 97
opérateurs Voir Symboles, au début
de l'index
de conversion de classe vers un
type 61
surcharge (vue d'ensemble) 54

P
paramètres par défaut 65
passage par référence 113
patrons Voir templates
pointeurs sur classe de base 104
polymorphisme 101
printf
la fin du... 89
protected 40
conseils 173
184

Pont entre C et C++

d'opérateurs 54
de l'opérateur « 92
de l'opérateur = 57
de l'opérateur » 93
de l'opérateur [] 142

Q
questions 175
R
redéclarations
comment éviter les... 157
redéfinition d'une fonction-membre 41
références 113
et objets provisoires 115
retournées par une fonction 115
résumé 1ère partie 75
réutilisabilité 171
S
set_terminate 145
set_unexpected 144
setfill 97
setiosflags 95
setprecision 95
setw 94
sioux
ruse de 64
static 24
portée 158
surcharge 51

T
template
ambiguïté 122
templates 117
terminate 145
this 23
pourquoi retourner *this? 60
throw 140
try 138

U
unexpected 144
V
virtualité 101
destructeurs 106
héritage virtuel 135
masquage dans un héritage
multiple 133
pure 107

Pont entre cet c++

  • 2.
    Table des Matières Notesau lecteur 7 LES BASES DU C++ 1) Les objets et les classes Notions de base L'idée Mise en œuvre C + + ' L'intérêt par rapport au C Résumé Pour bien démarrer en C + + Compléments Fonctions inline Le mot-clé this Éléments static dans une classe Déclaration de classe anticipée 13 13 13 14 19 20 20 21 21 23 24 25 2) Les c o n s t r u c t e u r s et les destructeurs Les notions de base L'idée Mise en œuvre C + + constructeurs Mise en œuvre C + + destructeurs Compléments Constructeur copie Constructeurs de conversion 27 27 27 28 30 32 32 36 3) L'héritage simple Notions de base L'idée Mise en œuvre C + + Redéfinition d'une fonction-membre Conversion de Dérivée* vers Base* Résumé 37 37 37 38 41 42 43
  • 3.
    4 Pont entre Cet C + + L'intérêt par rapport au C Compléments Héritage et constructeurs Héritage et destructeurs Exemple complet Listes d'initialisations 43 44 44 44 45 47 4) La surcharge Notions de base L'idée Mise en œuvre C + + L'intérêt par rapport au C Compléments Surcharge d'opérateurs Surcharge de l'opérateur = Opérateurs de conversion de types 51 51 51 52 53 54 54 57 61 5) Les petits + du C++ Les commentaires Ruse de sioux Paramètres par défaut Les constantes Déclarations 63 63 64 65 67 73 RÉSUMÉ Encapsulation Constructeurs & destructeurs Héritage Surcharge 75 77 77 78 79 ENCORE PLUS DE C++ 6) La fin du malloc : new et delete Notions de base L'idée Mise en œuvre C + + L'intérêt par rapport au C 83 83 83 84 86 7) La f i n du printf : cout, cin et cerr Notions de base L'idée Mise en œuvre C + + L'intérêt par rapport au C Compléments Surcharger « et » 89 90 90 90 91 93 93
  • 4.
    Table des matières Formaterles sorties Tableau récapitulatif 5 95 100 8) Le p o l y m o r p h i s m e et la virtualité Notions de base L'idée Mise en œuvre C + + Résumé L'intérêt par rapport au C Compléments Destructeurs virtuels Classes abstraites et fonctions virtuelles pures 102 102 102 104 105 105 107 107 108 9) Les références Notions de base L'idée Mise en œuvre C + + L'intérêt par rapport au C Compléments Références et objets provisoires Fonction retournant une référence 114 114 114 115 115 116 116 117 10) Les templates ou la mise en œuvre de la généricité Notions de base L'idée Mise en œuvre C + + Templates de fonctions Templates de classes Compléments Attention à l'ambiguïté 118 119 119 119 119 121 124 124 11) Les classes et f o n c t i o n s amies Notions de base L'idée Mise en œuvre C + + Attention ! 125 126 126 126 127 12) L'héritage m u l t i p l e Notions de base L'idée Mise en œuvre C + + L'intérêt par rapport au C Compléments Duplication de données d'une classe de base Masquage du polymorphisme 130 130 130 131 133 134 134 136
  • 5.
    6 Pont entre Cet C++ Héritage virtuel 138 13) Les e x c e p t i o n s Notions de base L'idée Mise en œuvre C + + L'intérêt par rapport au C Résumé Exemple complet Compléments Spécifier des exceptions Exception non interceptée Exceptions en cascade 140 140 140 141 145 145 145 147 147 148 150 14) La c o m p i l a t i o n séparée Notions de base L'idée Mise en œuvre Quoi mettre, et dans quels fichiers ? Comment lancer la compilation ? Compléments Comment éviter les redéclarations ? Static, inline et portée Travailler avec d'autres langages 153 153 153 154 155 159 161 161 162 164 GUIDE DE SURVIE 15) Conseils Les étapes d'un projet Conception Trouver les classes Architecturer les classes Détailler les classes Évaluer l'ensemble Codage C + + Quels outils ? Mise en œuvre de la réutilisabilité Implantation des classes 169 169 170 170 171 173 174 174 174 175 177 16) Questions-Réponses 179 Index 185
  • 6.
    16 Pont entre Cet C++ d o n n é e s d ' u n objet, données-membres. P a r opposition, les autres d o n n é e s et fonctions sont qualifiées de hors-classe. R e p r e n o n s notre e x e m p l e . Voici c o m m e n t n o u s aurions p u définir la classe L i v r e : class Livre { private : char char char public : void void void N o t e z la présence du point-virgule après l'accolade fermante. Le C + + n'aime pas forcer la main des programmeurs. Il est possible de définir des données public, c'està-dire accessibles titre[20]; auteur[20]; éditeur[20]; Saisir(); Afficher(); Imprimer(); } ; C o m m e n t définir une f o n c t i o n - m e m b r e C o m m e v o u s p o u v e z le constater, n o u s n ' a v o n s fait q u e déclarer les fonctions S a i s i r , A f f i c h e r e t I m p r i m e r . I l faut m a i n t e n a n t les définir c o m p l è t e m e n t . La s y n t a x e est identiq u e au l a n g a g e C, à ceci près : il faut indiquer au compilateur à quelle classe est rattachée u n e fonction. Le format de définition d'une fonction-membre (c'est-à-dire inclue dans u n e classe) est le suivant : directement de l'extérieur de l'objet C'est une pratique à déconseiller, car elle viole l'idée m ê m e d'encapsulation. type_retourné NomDeVotreClasse: : f o n c t i o n { p a r a m è t r e s ) { // corps de la fonction } Ce qui d o n n e d a n s n o t r e e x e m p l e : void Livre::Saisir() { puts("Entrez le titre du livre : " ) ; gets(titre); puts("Entrez le nom de l'auteur : " ) ; gets(auteur); puts("Entrez l'éditeur :"); gets(éditeur); } void Livre::Afficher() { printf("Titre : %s par %s (%s)", titre, auteur, éditeur);
  • 7.
    Les objets etles classes 17 ) void Livre::Imprimer() { fprintf(stdprn, "Titre : %s par %s (%s)", titre, auteur, éditeur); } L e s fonctions d ' u n e classe p e u v e n t l i b r e m e n t a c c é d e r à toutes ses d o n n é e s - m e m b r e s (ici : t i t r e , a u t e u r e t é d i t e u r ) . N ' i m p o r t e quelle fonction d ' u n e classe p e u t é g a l e m e n t a p p e ler u n e autre fonction de la m ê m e classe. Voilà ! N o t r e prem i è r e classe est écrite ! V o y o n s m a i n t e n a n t de quelle m a n i è r e n o u s p o u v o n s l'utiliser. C r é e r un objet P o u r utiliser u n e classe, il faut d ' a b o r d créer un objet qui a u r a c o m m e type cette classe. L a s y n t a x e est l a m ê m e q u e p o u r déclarer une variable en C : Nom_Classe nom_objet; D a n s l ' e x e m p l e du livre, cela d o n n e : Livre mon_livre; B i e n e n t e n d u , toutes les formes de déclaration du C s o n t acceptées : tableaux, pointeurs, tableaux de p o i n t e u r s , etc. Par exemple : Livre Livre ma_bibliotheque[20]; // tableau de 20 objets-livres *sur_ma_table_de_chevet; // pointeur sur un objet-livre A c c é d e r à la partie p u b l i q u e d'un objet M a i n t e n a n t q u e n o u s s a v o n s c o m m e n t déclarer un objet, il reste à découvrir c o m m e n t l'utiliser. En clair : c o m m e n t saisir, afficher ou i m p r i m e r un objet « livre ». V o u s ne serez p a s d é p a y s é : l'accès a u x f o n c t i o n s - m e m b r e s p u b l i q u e s d ' u n o b jet se fait c o m m e si v o u s accédiez à u n e d o n n é e d ' u n e structure classique du l a n g a g e C :
  • 8.
    18 Pont entre Cet C++ objet.fonction_publigue(paramètres); pointeur_sur_objet -> fonction_publique(paramètres); Il est t e m p s d'écrire un petit p r o g r a m m e qui va saisir puis afficher dix livres : void main() { Livre int bouquin[10]; i; for (i = 0; i < 10; i++) bouquin[i].Saisir(); for (i = 0; i < 10; i++) bouquin[i].Afficher(); } C ' e s t aussi s i m p l e que cela. V o u s s a v e z m a i n t e n a n t c o m m e n t écrire des classes simples, et c o m m e n t utiliser des objets. Précision i m p o r t a n t e sur l'accès a u x d o n n é e s private V o u s v o u s r a p p e l e z q u e les d o n n é e s o u fonctions p r i v a t e d ' u n e classe ne sont p a s visibles à partir d e s fonctions d'un autre objet. M a i s u n e petite précision s ' i m p o s e : u n e fonction m e m b r e d ' u n e classe A peut accéder directement à toutes les d o n n é e s (y c o m p r i s p r i v a t e ) d'autres objets de classe A. C e c i n ' e s t p a s f o r c é m e n t évident p u i s q u ' e l l e m a n i p u l e dans ce cas les d o n n é e s d ' u n autre objet. Elle p e u t p o u r t a n t accéd e r a u x d o n n é e s e t fonctions p r i v a t e d e cet autre objet car il est de la m ê m e classe. E x e m p l e : class A { private : int a; public : // ... void fontion_a(); } ; class B { private : int b; public : // }; ...
  • 9.
    L e sobjets et les classes void 19 A::fonction_a() { A B autre_a; objet_b; a = 1; autre_a.a = 1 ; objet_b.b = 1; // // // // OK : on reste dans cet objet OK : autre_a est de classe A NON : b est private dans une autre classe } L'intérêt par rapport au C V o u s l ' a v e z v u , u n objet r e g r o u p e des d o n n é e s e t d e s fonctions qui o p è r e n t sur ces d o n n é e s . Q u e l est l'intérêt de rais o n n e r e n objets plutôt q u ' e n fonctions o u e n structures, c o m m e c'est le cas en C ? L e c o d e g a g n e e n sécurité. V o u s s a v e z q u e les d o n n é e s d ' u n objet ne sont m a n i p u l é e s q u e p a r ses p r o p r e s fonctions, les f o n c t i o n s - m e m b r e s . V o u s p o u v e z d o n c contrôler l'intégrité des d o n n é e s et cibler v o s r e c h e r c h e s en c a s de bogue. L e s p r o g r a m m e s g a g n e n t e n clarté. S i l'architecture d e s objets est b i e n conçue*, v o u s c o m p r e n e z r a p i d e m e n t le rôle de tel ou tel objet. U n e fonction ne se p r o m è n e p a s d a n s le vide, elle est rattachée à un objet, d o n c à l ' e n s e m b l e des d o n n é e s q u ' e l l e m a n i p u l e . La clarté des p r o g r a m m e s et leur c l o i s o n n e m e n t en objets p e r m e t t e n t u n e m a i n t e n a n c e et u n e évolutivité p l u s facile. G r â c e a u x trois a v a n t a g e s p r é c é d e n t s , des é q u i p e s d e dév e l o p p e u r s p e u v e n t p l u s facilement e n v i s a g e r l'écriture de très gros p r o g r a m m e s . * Les pointeurs de fonction ne rendent jamais un programme plus clair... C e r t a i n s p o u r r a i e n t p r é t e n d r e q u e l'on p e u t s i m u l e r le principe de classe en C. Il suffirait, après tout, de c r é e r u n e structure c o n t e n a n t des pointeurs de fonction, p o u r s t o c k e r les fonctions m e m b r e s . C e r t e s , m a i s s'agissant d ' u n e g y m n a s t i q u e périlleuse, v o u s p e r d e z le bénéfice de la clarté d e s p r o g r a m m e s * . D e plus, rien n e v o u s interdit d ' a c c é d e r d i r e c t e m e n t a u x d o n n é e s de votre structure, ce qui tord le c o u à l'une d e s règles f o n d a m e n t a l e s de l ' e n c a p s u l a t i o n : cac h e r les d o n n é e s . Par ailleurs, v o u s serez d a n s l'incapacité de s i m u l e r les autres caractéristiques m a j e u r e s d u C + + , q u e n o u s d é c o u v r i r o n s d a n s les chapitres suivants.
  • 10.
    20 Pont entre Cet C++ Résumé N o u s v e n o n s de p r é s e n t e r la n o t i o n la p l u s i m p o r t a n t e du C + + et des l a n g a g e s orientés-objets : l'encapsulation. L e C + + p e r m e t d e créer e t m a n i p u l e r d e s objets. U n objet s e c o m p o s e de données et de fonctions de traitement qui lui sont p r o p r e s . C e r t a i n e s de ces d o n n é e s et de ces fonctions sont c a c h é e s , c'est-à-dire q u ' e l l e s ne sont accessibles q u e de l'intérieur de l'objet, à partir de ses fonctions p r o p r e s . P o u r créer u n objet e n C + + , i l faut d ' a b o r d définir u n m o u l e , a p p e l é classe e n C + + . L e s objets sont e n s u i t e créés c o m m e des variables classiques, à partir de leur classe. Les d o n n é e s et fonctions définies d a n s u n e classe s o n t a p p e lées respectivement données membres et fonctions membres, par o p p o s i t i o n a u x d o n n é e s et fonctions hors-classe. Par e x e m p l e , n ' i m p o r t e quelle fonction de la librairie s t a n d a r d du C ( c o m m e p r i n t f ) est u n e fonction hors-classe. Pour bien démarrer en C++ En tant q u e p r o g r a m m e u r en l a n g a g e C, la p h i l o s o p h i e objets v o u s déroutera peut-être a u début. V o u s n e d e v e z p l u s p e n ser en t e r m e de fonctions, m a i s en t e r m e d'objets, r e g r o u p a n t leurs p r o p r e s d o n n é e s e t leurs p r o p r e s fonctions. Par e x e m ple, si v o u s v o u l e z afficher le p l a t e a u d ' u n j e u , ne p e n s e z p a s à quelque chose c o m m e a f f i c h e r _ p l a t e a u (plateau) m a i s plutôt à p l a t e a u . a f f i c h e r ( ) , o ù p l a t e a u est u n objet c o n t e n a n t , par e x e m p l e , la p o s i t i o n des p i o n s du j e u . Bref, v o u s partez d ' u n objet et lui e n v o y e z un m e s s a g e (sous forme de fonction). R a s s u r e z - v o u s , c'est un réflexe qui viendra r a p i d e m e n t e t q u e v o u s trouverez d e p l u s e n p l u s agréable. B i e n q u e la p h i l o s o p h i e objet n é c e s s i t e u n e c o n c e p tion p l u s s o i g n é e q u ' a u p a r a v a n t , elle p r o c u r e un réel plaisir et b e a u c o u p d ' a v a n t a g e s .
  • 11.
    Les objets etles classes 21 Compléments R a p p e l o n s q u e v o u s p o u v e z sauter cette section d a n s u n p r e m i e r t e m p s , et p a s s e r d i r e c t e m e n t au chapitre suivant. S i , toutefois, v o u s désirez e n connaître u n p e u p l u s sur les classes, c o n t i n u e z votre lecture. Fonctions inline L e s fonctions inline sont une facilité d u C + + p e r m e t t a n t d'optimiser la vitesse d'exécution des programmes. L ' e x é c u t i o n d ' u n e fonction inline est en effet p l u s rapide q u e celle d ' u n e fonction définie n o r m a l e m e n t : le c o m p i l a t e u r r e m p l a c e les appels à de telles fonctions p a r le c o d e de c e s fonctions. Cette r e m a r q u e en e n g e n d r e u n e autre : il v a u t m i e u x q u e les fonctions inline soient très courtes (une ligne ou d e u x ) p o u r éviter d e cloner i n u t i l e m e n t d e n o m b r e u s e s lignes d e c o d e . M i s e en œuvre C + + E x a m i n o n s l ' e x e m p l e suivant : class UnNombre { private : int public : int void void nbr; get_nbr (); set_nbr (int); Afficher(); } ; C o m m e v o u s p o u v e z le voir, cette classe déclare trois fonctions p u b l i q u e s , qui ne sont e n c o r e définies nulle part. En théorie, il faudrait définir c h a c u n e de ces fonctions en d e h o r s de la classe, de cette m a n i è r e : int UnNombre: :get_nbr() { return nbr; } Le C + + m e t à notre disposition les fonctions inline, p o u r définir c o m p l è t e m e n t u n e fonction à l'intérieur de la définition de sa classe. Ainsi, au lieu de définir à part d e s fonctions très
  • 12.
    22 Pont entre Cet C++ c o u r t e s , v o u s p o u v e z les inclure d i r e c t e m e n t d a n s la classe. C ' e s t c e q u e n o u s allons faire p o u r les fonctions s e t _ n b r e t get_nbr: class UnNombre { private : int public : int void void nbr; get_nbr() set_nbr(int n2) Afficher(); { return nbr; } { nbr = n2; } } ; C o m m e v o u s p o u v e z l e constater, n o u s a v o n s défini complèt e m e n t les d e u x p r e m i è r e s fonctions p u b l i q u e s de cette classe (en gras d a n s l ' e x e m p l e ) . C e s fonctions sont a p p e l é e s inline. L a troisième, A f f i c h e r , n ' e s t q u e déclarée — n o t e z l e pointv i r g u l e après la déclaration—. Il faudra la définir en dehors de la classe, c o m m e n o u s a v i o n s l'habitude de le faire. Q u a n d on définit u n e fonction inline, il est inutile d'accoler le n o m de la classe et les caractères « : : » a v a n t le n o m de la fonction, p u i s q u ' e l l e est définie d a n s le c o r p s de sa classe. Ce système est efficace si vous utilisez la compilation séparée. Le mot-clé inline Il existe un autre m o y e n de bénéficier du m é c a n i s m e inline, sans définir les fonctions d i r e c t e m e n t d a n s la classe : il suffit d'utiliser le mot-clé inline en tête de la définition de la fonction. Q u a n d v o u s spécifiez q u ' u n e fonction est inline, v o u s d o n n e z s i m p l e m e n t u n e indication a u c o m p i l a t e u r , qui est libre de la suivre ou p a s . D'ailleurs certains compilateurs refusent de rendre inline d e s fonctions qui c o n t i e n n e n t des mots-clés c o m m e for ou while. Si, par e x e m p l e , n o u s avions v o u l u q u e Afficher soit une fonction inline, sans p o u r autant inclure s o n c o d e d a n s la déclaration de la classe UnNombre, il aurait fallu écrire : N o u s détaillerons cela au chapitre 14. inline void UnNombre:: Afficher() { printf("Le nombre est : %d", nbr); }
  • 13.
    Les objets etles classes 23 La déclaration de classe restant identique, m a i s il faut cette fois-ci indiquer au c o m p i l a t e u r le n o m de la classe à laquelle appartient la fonction inline (c'est le rôle de « U n N o m b r e : : »), p u i s q u e cette fonction n ' e s t p a s déclarée d i r e c t e m e n t d a n s la classe. Remarque : des fonctions hors-classes peuvent elles-aussi êtres définies inline. Le mot-clé this Résumé L e C + + p e r m e t a u p r o g r a m m e u r d e définir d e s fonctionsm e m b r e inline. C e s fonctions se c o m p o r t e n t c o m m e toutes les autres fonctions d ' u n e classe, à la seule e x c e p t i o n q u e lors d e l a compilation, c h a q u e appel à u n e fonction i n l i n A e s t r e m p l a c é p a r le corps de cette fonction. Il en résulte un g a i n de t e m p s à l'exécution. On réserve c e p e n d a n t cet a v a n t a g e a u x fonctions très courtes, afin q u e la taille finale de l ' e x é c u t a b l e ne soit p a s trop é n o r m e . C h a q u e classe p o s s è d e a u t o m a t i q u e m e n t u n e d o n n é e c a c h é e u n p e u spéciale : l e pointeur t h i s . Utilisé d a n s u n e fonction d e classe, t h i s est u n pointeur sur l'objet c o u r a n t à partir d u q u e l on a appelé la fonction m e m b r e . E x e m p l e : class A { private: int i ; public : void f ( ) ; ) ; void A : :f() { this -> i = 1; // équivaut à i = 1; } t h i s est de type « p o i n t e u r sur l'objet c o u r a n t qui a a p p e l é l a fonction » . D a n s l ' e x e m p l e , t h i s est d e t y p e p o i n t e u r d e A . V o u s e n déduirez q u e ( * t h i s ) représente l'objet c o u rant, et v o u s a u r e z raison. L'utilité d e t h i s est patente p o u r certains opérateurs, o u p o u r tester l'égalité de d e u x objets (voir p a g e 6 1 ) .
  • 14.
    24 Pont entre Cet C++ Éléments static dans une classe On ne peut pas utiliser le mot-clé this dans une fonction static, puisque celle-ci ne dépend pas d'un objet précis. V o u s p o u v e z déclarer des objets, variables ou fonctions s t a t i c d a n s u n e classe. C e s é l é m e n t s seront alors c o m m u n s à tous les objets de cette classe. Ainsi, u n e variable s t a t i c aura la m ê m e valeur p o u r tous les objets de cette classe. S i l'un des objets c h a n g e cette variable s t a t i c , elle sera m o d i f i é e p o u r tous les autres objets. On distingue ainsi les variables et fonctions d'instance (celles qui sont spécifiques à un objet), et les variables et fonctions de classe ( c o m m u n e s à tous les objets de cette classe). C e s dernières doivent être déclarées s t a t i c . I l faut initialiser c h a q u e variable s t a t i c e n d e h o r s d e l a déclaration d e classe, sans répéter l e mot-clé s t a t i c (voir l ' e x e m p l e ci-dessous). L e s f o n c t i o n s - m e m b r e s déclarées s t a t i c d o i v e n t être a p p e lées à partir du n o m de la classe, c o m m e ceci : N o m C l a s s e : : f ( ), sans faire référence à un objet précis. Elles p e u v e n t d o n c être a p p e l é e s s a n s q u ' a u c u n objet de la classe n'existe. #include <iostream.h> Pour c o m p r e n d r e cet exemple, vous devez d'abord lire le chapitre 2 sur les constructeurs. class Flam { protected: static int nb_objets; int donnée; public : Flam() : donnée(0) { nb_objets++; } static affiche_nb_objets() { cout << nb_objets << endl; } }; int Flam::nb_objets = 0; // initialisation void main() { // appel de la fonction static à partir de la classe Flam::affiche_nb_objets(); Flam a, b, c; // déclarons 3 objets // appel de la fonction static à partir d'un objet a.affiche_nb_objets(); } // affichage // 0 // 3
  • 15.
    Les objets etles classes 25 D a n s cet e x e m p l e , le constructeur de Flam ajoute 1 à la v a riable de classe nb_objets (déclarée static au s e n s C + + ) . En créant trois objets a, b et c, n o u s a p p e l o n s trois fois ce constructeur. On p e u t appeler u n e fonction static en la faisant p r é c é d e r du n o m de la classe suivi de d e u x d o u b l e p o i n t s ou à partir d ' u n objet. Il n ' y a p a s de différence entre les d e u x . D i f f é r e n c e e n t r e le m o t - c l é static du C et du C + + A t t e n t i o n : d a n s ce q u e n o u s v e n o n s d ' e x p l i q u e r , le m o t - c l é static sert à déclarer d e s variables o u fonctions de classe. En C standard, r a p p e l o n s q u e static signifie q u e l ' é l é m e n t n ' e s t accessible que dans s o n fichier s o u r c e . Déclaration de classe anticipée T o u t c o m m e v o u s p o u v e z déclarer u n e structure à un endroit et la définir c o m p l è t e m e n t ailleurs, v o u s a v e z la p o s sibilité de le faire p o u r des classes. La s y n t a x e est s i m p l e : class nom_de_classe;. I m a g i n o n s q u ' u n e classe A c o n tienne un objet de classe B et r é c i p r o q u e m e n t . Il faut recourir à u n e déclaration p o u r q u e A ait c o n n a i s s a n c e de B a v a n t q u e B ne soit définie : class B; // déclaration anticipée class A { protected: B objet_B; public : I I . . . }; class B { protected: A objet_A; public : // ... };
  • 16.
    Les constructeurs et lesdestructeurs La vie et la mort d'un objet C++ sont régies et les destructeurs. Les fonctions constructeurs création d'un objet, les fonctions destructeurs qu'il sort de sa portée de visibilité, c'est-à-dire par les constructeurs sont appelées à la sont invoquées dès dès qu'il meurt. Les notions de base L'idée E n C o u e n C + + , q u a n d v o u s déclarez u n e variable s a n s l'initialiser, son c o n t e n u est i n d é t e r m i n é . E n C + + , q u a n d v o u s déclarez u n objet d ' u n e certaine classe, s o n c o n s t r u c teur sera a p p e l é automatiquement. Un c o n s t r u c t e u r est u n e f o n c t i o n - m e m b r e qui p o r t e toujours le n o m de la classe d a n s laquelle elle est définie, et qui ne renvoit rien (pas m ê m e un v o i d ) . S i v o u s n e définissez p a s e x p l i c i t e m e n t u n c o n s t r u c teur p o u r u n e classe, l e C + + e n utilise u n d'office, qui v a s e contenter de réserver de la m é m o i r e p o u r toutes les variables de v o t r e classe. Attention : q u a n d v o u s définissez un p o i n t e u r sur un objet, a u c u n constructeur n ' e s t appelé. Si v o u s désirez e n s u i t e allouer de la m é m o i r e et appeler un constructeur, utilisez n e w (voir chapitre 6, p a g e 8 3 ) .
  • 17.
    28 Pont entre Cet C++ P a r a l l è l e m e n t a u x constructeurs, l e C + + n o u s p r o p o s e les destructeurs. Q u a n d l'objet sort de la p o r t é e du bloc d a n s lequel il a été déclaré, n o t a m m e n t q u a n d l ' e x é c u t i o n du prog r a m m e atteint la fin d ' u n bloc où l'objet est défini, son destructeur est appelé automatiquement. Mise en œuvre C++ constructeurs C o m m e n t définir u n e fonction c o n s t r u c t e u r ? N o u s savons q u ' e l l e doit porter l e m ê m e n o m q u e s a c l a s s e . P r e n o n s l ' e x e m p l e d ' u n e classe Equipage : class Equipage { private : int personnel; public : Equipage(int personnel_initial); } ; Equipage::Equipage(int personnel_initial) { personnel = personnel_initial; } N o t r e constructeur de la classe Equipage ne r e t o u r n e auc u n e valeur, p a s m ê m e u n v o i d , p u i s q u e c'est u n constructeur. Il accepte un p a r a m è t r e , le n o m b r e de p e r s o n n e s dans l ' é q u i p a g e . C e l a signifie q u e l o r s q u e v o u s v o u d r e z déclarer un objet de classe Equipage, il faudra faire suivre l'identifiant de l'objet p a r les p a r a m è t r e s effectifs du constructeur, entre p a r e n t h è s e s . À l ' e x é c u t i o n du p r o g r a m m e suivant, le c o n s t r u c t e u r Equipage : : Equipage est appelé a u t o m a t i q u e m e n t , avec c o m m e p a r a m è t r e l'entier 311 : Ici, le constructeur est appelé, ce qui initialise la d o n n é e - m e m b r e void main() { Equipage base_alpha(311) ; // déclaration de l'objet et appel du constructeur personnel à 311 ) Constructeur sans paramètre Si v o u s définissez un constructeur s a n s p a r a m è t r e , par e x e m p l e c o m m e ceci :
  • 18.
    Les constructeurs etles destructeurs 29 Equipage : : Equipage() { nombre = 0 ; ) il faut faire attention à la m a n i è r e de l'appeler. L ' e x e m p l e suivant v o u s m o n t r e ce qu'il faut et ne faut pas faire : void main() { Equipage Equipage mon_equipage(); mon_autre_equipage; // (1) incorrect // (2) correct } Si v o u s ajoutez des p a r e n t h è s e s , c o m m e d a n s le c a s 1, le c o m p i l a t e u r c o m p r e n d q u e v o u s déclarez u n e fonction ! Donc, pour appeler correctement un constructeur sans par a m è t r e , il faut o m e t t r e les p a r e n t h è s e s , c o m m e d a n s le cas 2. C ' e s t s e u l e m e n t dans ce cas que le c o n s t r u c t e u r sans p a r a m è tre est appelé. C o n s t r u c t e u r s surchargés Il est tout à fait p o s s i b l e de définir p l u s i e u r s c o n s t r u c t e u r s h o m o n y m e s , qui s e d i s t i n g u e r o n t alors p a r l e t y p e e t / o u l a quantité d e p a r a m è t r e s . R e p r e n o n s l a classe E q u i p a g e e t définissons trois c o n s t r u c t e u r s : class Equipage { private : int personnel; public : Equipage(int personnel_initial); Equipage(); Equipage(int nbr_femmes, int nbr_hommes); } ; // constructeur 1 Equipage:: Equipage(int personnel_initial) { personnel = personnel_initial; } // constructeur 2 Equipage : : Equipage() { personnel = 0; // 1 // 2 // 3
  • 19.
    30 Pont entre Cet C++ } // constructeur 3 Equipage::Equipage(int nbr_femmes, int nbr_hommes) { personnel = nbr_femmes + nbr_hommes; } V o u s d i s p o s e z m a i n t e n a n t de trois m a n i è r e s d'initialiser un objet d e classe E q u i p a g e . L ' e x e m p l e qui suit illustre c e concept : void main() { Equipage base_alpha(311); Equipage mixte(20, 1 ) ; Equipage inconnu; // appel au constructeur 1 // appel au constructeur 3 // appel au constructeur 2 } Mise en œuvre C++ destructeurs R a p p e l o n s q u e le destructeur est a p p e l é automatiquement q u a n d un objet sort de la p o r t é e du b l o c d a n s lequel il est déclaré — c'est-à-dire q u a n d il n ' e s t p l u s accessible au prog r a m m e u r . U n e f o n c t i o n - m e m b r e destructeur n e retourne p a s de t y p e (pas m ê m e un v o i d ) , et n ' a c c e p t e aucun paramètre. C o m m e p o u r les c o n s t r u c t e u r s , le c o m p i l a t e u r g é n è r e un destructeur p a r défaut p o u r toutes les classes qui n ' e n sont p a s p o u r v u e s . Ce destructeur p a r défaut se b o r n e à libérer la m é m o i r e des d o n n é e s de la classe. M a i s si v o u s désirez faire u n e o p é r a t i o n particulière p o u r détruire un objet, et notamm e n t si v o u s a v e z d e s p o i n t e u r s d o n t il faut libérer l'espace m é m o i r e , v o u s p o u v e z définir v o t r e p r o p r e destructeur. Attention ! C o n t r a i r e m e n t a u x c o n s t r u c t e u r s , il ne p e u t exister qu'un seul destructeur par classe ! Le n o m de la fonction destructeur est de la forme « -NomDeClasse ». Le p r e m i e r s i g n e est un « tilde ». Exemple I m a g i n o n s u n e classe qui c o n t i e n n e u n e d o n n é e - m e m b r e p o i n t a n t v e r s u n e c h a î n e de caractères : #include <stdio.h> #include <string.h> class Capitaine
  • 20.
    Les constructeurs etles destructeurs 31 { private : char *nom; public : // constructeur Capitaine) char *nom_initial ); // destructeur -Capitaine(); } ; // constructeur Capitaine::Capitaine( char *nom_initial ) { nom = (char*) malloc(strlen(nom_initial) + 1 ) ; strcpy(nom, nom_initial); } // destructeur Capitaine::-Capitaine() { free( nom ); } Compléments Constructeur copie Lorsqu'il faut initialiser un objet avec un autre objet de m ê m e classe, le constructeur copie est appelé a u t o m a t i q u e m e n t . Voici u n petit e x e m p l e d e c o d e a p p e l a n t l e c o n s t r u c t e u r c o pie de la classe B e t a : class Beta // le contenu ne nous intéresse pas ici void fonction_interessante(Beta beta) // traitement sur beta void main()
  • 21.
    32 Pont entre Cet C++ Beta premier, deuxième (premier), troisième = premier; fonction_interessante(premier); // (1) // (2) // (3) } * voir le paragraphe sur les listes d'initialisations, page 47, * Dans ce cas, l'opérateur = n'est pas appelé puisque ce n'est pas une affectation. La ligne (1) signifie q u e l'objet d e u x i è m e doit être initialisé a v e c l'objet p r e m i e r * . La ligne (2) initialise elle aussi l'objet t r o i s i è m e avec l'objet p r e m i e r . C e n ' e s t p a s u n e affectation, c'est une initialisation !* Enfin, la ligne (3) fait appel à u n e fonction qui va recopier le p a r a m è t r e effectif, p r e m i e r , d a n s s o n p a r a m è t r e formel, b e t a . Bref, ces trois lignes font implicitement a p p e l au constructeur c o p i e . C o m m e p o u r les c o n s t r u c t e u r s o r d i n a i r e s ou les destructeurs, l e C + + e n g é n è r e u n p a r défaut s i v o u s n e l'avez p a s fait v o u s - m ê m e . Ce c o n s t r u c t e u r c o p i e p a r défaut effectue u n e c o p i e « b ê t e », m e m b r e à m e m b r e , d e s d o n n é e s de la classe. D a n s les cas où v o s classes c o n t i e n n e n t d e s pointeurs, cela p o s e un p r o b l è m e : en ne c o p i a n t q u e le pointeur, l'objet c o p i é et l'objet c o p i a n t pointeraient sur la m ê m e zone mém o i r e . Et ce serait fâcheux ! C e l a signifierait q u ' e n modifiant l ' u n d e s d e u x objets, l'autre serait é g a l e m e n t modifié de m a n i è r e « invisible » ! L e s c h é m a suivant illustre c e p r o b l è m e . Schéma illustrant le problème d'une copie membre à membre
  • 22.
    Les constructeurs etles destructeurs 33 P o u r r é s o u d r e c e p r o b l è m e , v o u s d e v e z d o n c écrire votre propre constructeur copie, qui effectuera u n e c o p i e p r o p r e — avec, par e x e m p l e , u n e nouvelle allocation p o u r c h a q u e pointeur d e l'objet copié. N o u s o b t e n o n s l e s c h é m a suivant, après u n e c o p i e « p r o p r e ». Schéma illustrant le résultat d'une copie propre. C o m m e n t définir son p r o p r e c o n s t r u c t e u r copie ? La f o r m e d ' u n c o n s t r u c t e u r copie est toujours la m ê m e . Si votre classe s'appelle Gogol, s o n c o n s t r u c t e u r c o p i e s'appellera Gogol: : Gogol (const Gogol&). Le p a r a m è * les références sont traitées au chapitre 9, page I 13. *cout est un mot clé C++ utilisé pour l'affichage de caractères. Voir tre est u n e référence* à un objet de classe Gogol. Ici const n ' e s t p a s n é c e s s a i r e m a i s fortement r e c o m m a n d é , p u i s q u e v o u s ne modifierez pas l'objet à copier. É c r i v o n s la classe Gogol, p o s s é d a n t un p o i n t e u r de char, et efforçons-nous d'écrire son c o n s t r u c t e u r de c o p i e : #include <string.h> #include <iostream.h> // pour cout* class Gogol { private: char chaptre 7, page 89. *pt; public : // constructeur normal Gogol(char * c ) ; // constructeur de copie Gogol(const Gogol &a_copier);
  • 23.
    34 Pont entre Cet C++ // accès aux données membre void set_pt(char * c ) ; char *get_pt(); // autres fonctions void Afficher();: >; // constructeur normal Gogol::Gogol(char *c) { pt = new char [strlen(c) + 1 ] ; strcpy(pt, c ) ; } // constructeur de copie Gogol:: Gogol(const Gogol &a_copier) { if (this != &a_copier) // voir dans la marge Il faut vérifier que { l'objet source et // effaçons l'ancien pt delete pt; l'objet destination sont différents, car // allouons de la place pour le nouveau pt pt = new char [strlen(a_copier.pt) + 1 ] ; s'ils étaient identiques, delete pt effacerait à la fois les pointeurs // donnons une valeur au nouveau pt strcpy(pt, a_copier.pt); source et destination ! } } void Gogol::set_pt(char *c) { delete pt; pt = new char [strlen(c) + 1 ] ; strcpy(pt, c ) ; } char *Gogol: :get_pt() { return pt; } void Gogol::Afficher() { cout << pt << endl; } void { maint) Gogol gog("Zut"), bis = gog; // appel du constructeur copie gog.Afficher(); bis.Afficher(); // Zut // Zut
  • 24.
    Les constructeurs etles destructeurs 35 // on modifie la chaine de gog gog.set_pt("Mince alors"); gog.Afficher(); bis.Afficher(); // Mince alors // Zut } C o n c l u s i o n : les objets gog et bis p o s s è d e n t b i e n d e u x p o i n teurs d é s i g n a n t d e u x z o n e s m é m o i r e s différentes. C ' e s t parfait. M a i s si n o u s n ' a v i o n s p a s écrit notre p r o p r e constructeur copie, celui g é n é r é p a r défaut aurait c o p i é les d o n n é e s m e m b r e à m e m b r e . A i n s i , d a n s gog et bis, pt aurait été le m ê m e . D o n c , le fait de détruire le p o i n t e u r de gog aurait é g a l e m e n t détruit celui de bis p a r effet de b o r d . Inutile de préciser les c o n s é q u e n c e s d é s a s t r e u s e s d ' u n tel é v é nement. Constructeurs de conversion Si vous avez besoin de l'opération réciproque (convertir un objet de votre classe vers une variable de type T), allez voir p g 61 où l'on vous ae dira que c'est possible. Un c o n s t r u c t e u r n ' a y a n t q u ' u n seul a r g u m e n t de t y p e T spécifie u n e c o n v e r s i o n d ' u n e variable de t y p e T v e r s un o b jet de la classe du constructeur. Ce c o n s t r u c t e u r sera a p p e l é a u t o m a t i q u e m e n t c h a q u e fois q u ' u n objet de t y p e T sera rencontré là où il faut un objet de la classe de constructeur. P a r e x e m p l e , i m a g i n o n s q u ' o n veuille u n e c o n v e r s i o n autom a t i q u e d ' u n entier vers un objet de classe Prisonnier, où l'entier r e p r é s e n t e s o n n u m é r o matricule. A i n s i , q u a n d l e c o m p i l a t e u r attend un objet Prisonnier et qu'il n ' a q u ' u n entier à la p l a c e , il effectue la c o n v e r s i o n a u t o m a t i q u e m e n t en créant l'objet Prisonnier à l'aide du c o n s t r u c t e u r de conversion. Exemple : #include <iostream.h> class Prisonnier { protected: int numéro; char nom [50] ; public : // ... Prisonnier(int n) : numéro(n) { cout << "Conversion de " << n << endl; nom[0] = 0 ; } } ; void { fonction(Prisonnier p) // . . .
  • 25.
    36 Pont entre Cet C++ } void { main() Prisonnier p = 2; // donne p = Prisonnier(2) fonction(6); // donne fonction(Prisonnier(6)) } // affiche : // Conversion de 2 // Conversion de 6
  • 26.
    L'héritage simple L'héritage vouspermet de modéliser le monde réel avec souplesse, d'organiser vos classes et de réutiliser celles qui existent déjà. C'est un concept fondamental en programmation orientée-objets. Nous parlons ici d'héritage simple, par opposition à l'héritage multiple qui sera développé dans la deuxième partie du livre. Notions de base L'idée L e s informaticiens se feraient b e a u c o u p m o i n s de soucis s'ils p o u v a i e n t r é g u l i è r e m e n t réutiliser des p o r t i o n s d e c o d e qu'ils ont déjà écrit p o u r des projets antérieurs. G r â c e à l'héritage, u n e part du r ê v e devient réalité : v o u s p o u v e z profiter des classes déjà existantes p o u r en créer d'autres. M a i s l à o ù l'héritage est intéressant, c'est q u e v o u s p o u v e z ne garder q u ' u n e partie de la classe initiale, modifier certaines de ses fonctions ou en ajouter d'autres. L'héritage s'avère très a d a p t é a u x cas o ù v o u s d e v e z m a n i puler des objets qui ont des p o i n t s c o m m u n s , m a i s qui diffèrent l é g è r e m e n t .
  • 27.
    38 Pont entre Cet C++ * N o u s aurions pu parler de la classifica- Le principe Le p r i n c i p e initial de l'héritage est p r o c h e de celui de la classification en catégories : on part du c o n c e p t le p l u s général p o u r se spécialiser d a n s les cas particulier. A d m e t t o n s que n o u s v o u l i o n s parler de véhicules*, et e s s a y o n s de tisser des liens d'héritage entre q u e l q u e s v é h i c u l e s b i e n c o n n u s : t i o n des cornichons, des jeunes filles ou des flippers, mais un exemple classique est sans doute mieux adapté à un large public. Le sens des flèches d'héritage pourrait être : « p o s s è d e les caractéristiques de » ou « est un », au s e n s large du terme. Ainsi, voiture est un véhicule. De m ê m e p o u r le bateau. Le camion, lui, p o s s è d e les caractéritiques de voiture et, indirectem e n t , de véhicule. O n dit q u e v o i t u r e hérite de v é h i c u l e . D e m ê m e , bateau hérite de v é h i c u l e , et c a m i o n de voiture. D ' u n cas général, r e p r é s e n t é par véhicule, n o u s a v o n s tiré d e u x cas particuliers : voiture et bateau. Camion est lui-même un « cas particulier » de voiture, car il en p o s s è d e grosso modo toutes les caractéristiques, et en ajoute d'autres q u i lui sont propres. Mise en œuvre C++ D a n s l ' e x e m p l e ci-dessus, c h a q u e é l é m e n t du s c h é m a est rep r é s e n t é p a r u n e classe. U n e r e m a r q u e de vocabulaire : q u a n d u n e classe A hérite d ' u n e classe B, on dit q u e A est la classe de base et B la classe dérivée. P o u r y voir p l u s clair, écriv o n s les classes v é h i c u l e et voiture, et e x a m i n o n s ensuite ce q u e cela signifie a u n i v e a u d u C + + : Le m o t clé protected est expliqué dans les pages qui suivent. class Véhicule { protected : int // ... nombre_de_places;
  • 28.
    L'héritage simple 39 public : //constructeurs Véhicule(); // ... // fonctions de traitement void Afficher)); // . . . } ; Remarque : nous n'avons pas détaillé class Voiture : public Véhicule { protected : int chevaux_fiscaux; // . . . public : // constructeurs Voiture(); // fonctions de traitement void CalculVignette(); ces classes afin de nous concentrer sur le mécanisme d'héritage. Vous retrouverez les classes complètes en fin de chapitre. } ; R i e n ne d i s t i n g u e ces classes d'autres classes ordinaires, si ce n ' e s t la c h a î n e « : public Véhicule » à la suite du n o m de la classe Voiture. C ' e s t bel et b i e n cette c h a î n e qui indiq u e au compilateur q u e la classe Voiture hérite de la classe Véhicule. Le mot-clé public qualifie la spécification d'accès d e l'héritage. N o u s v e r r o n s ci-dessous q u e l e t y p e d ' h é r i t a g e d é t e r m i n e quelles d o n n é e s de la classe de b a s e sont accessib l e s d a n s la classe dérivée. V o i l à , n o u s a v o n s déclaré q u e la classe Voiture héritait de la classe Véhicule. Q u e cela signifie-t-il c o n c r è t e m e n t ? Q u e d a n s d e s objets de classe dérivée (Voiture), v o u s p o u v e z a c c é d e r à des d o n n é e s ou fonctions m e m b r e s d'objets de la classe de b a s e (Véhicule). * Rappelons que la spécification d'accès est déterminée par le mot-clé qui suit les deux-points («:») après la ligne class NomClasseDérivée. Accès a u x m e m b r e s de la classe de b a s e P o u r savoir p r é c i s é m e n t quelles d o n n é e s s o n t accessibles d a n s la classe dérivée, il faut considérer la spécification d'accès* ainsi q u e le t y p e des d o n n é e s (public, protected ou private) de la classe de b a s e . Le tableau s u i v a n t d o n n e les c o m b i n a i s o n s possibles :
  • 29.
    40 Pont entre Cet C++ A u t r e m e n t dit, le mot-clé protected est identique à private si vous n'utilisez pas l'héritage. L e c o n t e n u d u tableau i n d i q u e l e type d u m e m b r e ( p u b l i c , p r o t e c t e d , p r i v a t e o u inaccessible) d a n s l a classe dérivée. V o u s d é c o u v r e z d a n s c e tableau u n n o u v e a u spécificateur d ' a c c è s : p r o t e c t e d . C e spécificateur est i d e n t i q u e à p r i v a t e à l'intérieur de la classe. Il diffère u n i q u e m e n t en cas d'héritage, p o u r d é t e r m i n e r s i les é l é m e n t s p r o t e c t e d sont accessibles ou n o n d a n s les classes dérivées. P o u r m i e u x c o m p r e n d r e ce tableau, voici trois petits exemples de dérivation : class Base { private : int protected : int public : int detective_prive; acces_protege; domaine_publicp±e; } ; class Deriveel : public Base { // detective_prive est inaccessible ici // acces_protege est considéré comme « protected » // domaine_public est considéré comme « public » } ; class Derivee2 : protected Base { // detective_prive est inaccessible ici // acces_protege est considéré comme « protected » // domaine_public est considéré comme « protected » >; class { // // // }; Derivee2 : private Base detective_prive est inaccessible ici acces_protege est considéré comme « private » domaine_public est considéré comme « private »
  • 30.
    L'héritage simple 41 Ces exemplesamènent plusieurs remarques : U n e d o n n é e o u fonction m e m b r e p r i v a t e est toujours inaccessible d a n s ses classes dérivées L a spécification d ' a c c è s d ' u n e d o n n é e ( p u b l i c , p r o t e c t e d o u p r i v a t e ) d a n s u n e classe dérivée est e n fait l a spécification d ' a c c è s la p l u s forte. Par e x e m p l e , si u n e f o n c t i o n - m e m b r e est p r o t e c t e d d a n s l a classe d e b a s e , e t q u e l'héritage est p u b l i c , elle devient p r o t e c t e d d a n s la classe dérivée. E n règle générale, o n utilise l'héritage p u b l i c p o u r m o déliser des relations du m o n d e « réel ». V o i r le chapitre 15 sur les conseils. Redéfinition d'une fonctionmembre V o u s p o u v e z définir des fonctions a u x entêtes i d e n t i q u e s d a n s différentes classes a p p a r e n t é e s p a r u n e relation d'héritage. La fonction a p p e l é e d é p e n d r a de la classe de l'objet qui l'appelle, ce qui est d é t e r m i n é s t a t i q u e m e n t à la compilation. R e p r e n o n s : u n e f o n c t i o n - m e m b r e d ' u n e classe dérivée p e u t d o n c avoir le m ê m e n o m (et les mêmes p a r a m è tres) q u e celle d ' u n e classe de b a s e . P r e n o n s l ' e x e m p l e d ' u n e gestion de textes qui d i s t i n g u e les textes b r u t s des textes m i s en forme, et définissons d e u x fonctions a u x entêtes identiques (mais au c o r p s différent) : #include <stdio.h> class TexteBrut public : void void Imprimer(int nb) ; TexteBrut::Imprimer(int nb) printf("Imprimer de la classe TexteBrutXn"); class TexteMisEnForme : public TexteBrut public : void void // héritage Imprimer(int n b ) ; TexteMisEnForme::Imprimer(int nb) printf("Imprimer de la classe TexteMisEnFormeXn");
  • 31.
    42 Pont entre Cet C++ void main() { TexteBrut TexteMisEnForme txt; joli_txt; txt.Imprimer( 1 ) ; joli_txt.Imprimer(1); } // affichage : Imprimer de la classe TexteBrut Imprimer de la classe TexteMisEnForme Conversion de Dérivée* vers Base* D a n s un cas où u n e classe Dérivée hérite p u b l i q u e m e n t d ' u n e classe Base, les c o n v e r s i o n s de p o i n t e u r s de Dérivée v e r s des p o i n t e u r s de Base est faite a u t o m a t i q u e m e n t aux endroits où le c o m p i l a t e u r attend un p o i n t e u r sur Base : class Base { >; class Dérivée : public Base { }; void main() { Base *base; Dérivée *derivee; base = dérivée; // OK } Attention : cette c o n v e r s i o n n ' e s t p a s autorisée si l'héritage est protected ou private ! Si v o u s r e m p l a c e z public p a r protected d a n s le listing ci-dessus, la d e r n i è r e ligne du main p r o v o q u e r a u n e erreur de c o m p i l a t i o n . P o u r q u o i ? P a r c e q u e p o u r q u ' u n p o i n t e u r sur u n e classe dérivée puisse être converti sur u n e classe de b a s e , il faut q u e cette dernière soit accessible, c'est-à-dire q u ' e l l e p o s s è d e des m e m b r e s public accessibles. Or ce n ' e s t p a s le cas d ' u n h é r i t a g e pro- tected ou private. Résumé E n C + + , u n e classe p e u t hériter d ' u n e autre. O n dit alors que la classe qui hérite est u n e classe dérivée, l'autre classe étant a p p e l é e classe de base. Q u a n d u n e classe A hérite d'une classe B, elle p e u t a c c é d e r à certaines d o n n é e s ou fonctions
  • 32.
    L'héritage simple 43 m em b r e s d e l a classe B . C ' e s t u n petit p e u c o m m e s i u n e partie de la classe B avait été recopiée d a n s le c o r p s de la classe A. Pour savoir ce qui est accessible d a n s A, il faut c o n s i d é r e r la spécification d ' a c c è s ( p u b l i c , p r o t e c t e d o u p r i v a t e ) , l e type d ' a c c è s des d o n n é e s d e B ( p u b l i c , p r o t e c t e d o u p r i v a t e ) e t consulter l e tableau d e l a p a g e 4 0 . L'intérêt par rapport au C Là, le C est c o m p l è t e m e n t battu. G r â c e à l'héritage, v o u s allez enfin p o u v o i r réutiliser facilement le c o d e q u e v o u s a v e z déjà écrit. V o u s p o u r r e z enfin « factoriser » les p o i n t s c o m m u n s d ' u n e structure d'objets. Il n ' e x i s t e rien, d a n s le l a n g a g e C , qui p e r m e t t e d e mettre e n œ u v r e facilement u n m é c a n i s m e similaire. P r e n o n s un e x e m p l e : v o u s d e v e z gérer les e m p l o y é s d ' u n e centrale nucléaire. À partir d ' u n e classe de b a s e E m p l o y é , v o u s faites hériter d'autres classes c o r r e s p o n d a n t a u x catégories de p e r s o n n e l : Ouvrier, C a d r e , A g e n t S é c u r i t é , etc. Si à l'avenir d'autres p o s t e s sont créés (par e x e m p l e P o r t e P a r o l e ou A v o c a t ) , v o u s p o u r r e z créer la classe c o r r e s p o n d a n t e et la faire hériter de E m p l o y é ou de classes spécifiques. D a n s n o tre e x e m p l e , un P o r t e P a r o l e pourrait hériter de la classe C a dre. L ' a v a n t a g e : v o u s n ' a v e z p a s à ré-écrire tout ce q u e les portes-paroles et les c a d r e s ont en c o m m u n s . Compléments Héritage et constructeurs L e s c o n s t r u c t e u r s n e sont j a m a i s hérités. Q u a n d v o u s déclarez un objet d ' u n e classe dérivée, les c o n s t r u c t e u r s p a r d é faut d e s classes de b a s e s v o n t être a p p e l é s a u t o m a t i q u e m e n t avant q u e le c o n s t r u c t e u r spécifique de la classe dérivée ne le soit. Voici un e x e m p l e : class Base { public : Base();
  • 33.
    44 Pont entre Cet C++ } ; class Deriveel : public Base { public : Deriveel(); } ; class Derivee2 : public Deriveel { public : Derivee2(); }; void main() { Base b; // appel à Base() Deriveel dl; // appels à Base() puis à Deriveel() Derivee2 d2 ; // appels à Base() , Deriveel().. . // puis Derivee2() // corps de la fonction } P o u r ne p a s a p p e l e r les c o n s t r u c t e u r s p a r défauts m a i s des c o n s t r u c t e u r s avec des p a r a m è t r e s , v o u s d e v e z recourir aux listes d'initialisations, e x p l i q u é e s p l u s loin d a n s ce chapitre. Héritage et destructeurs A l o r s q u e les constructeurs sont a p p e l é s d a n s l'ordre desc e n d a n t (de la classe de b a s e à la classe d é r i v é e ) , c'est l'inverse p o u r les destructeurs : celui de la classe d é r i v é e est a p p e l é en p r e m i e r , p u i s celui de la classe de b a s e immédiat e m e n t supérieure, et ainsi de suite j u s q u ' à la classe de base la plus haute. C o m p l é t o n s l ' e x e m p l e p r é c é d e n t : void main() { Base b; // appel à Base() Deriveel dl ; // appels à Base{) puis à DeriveelO Derivee2 d2 ; // appels à Base(), Deriveel().. . // ...puis Derivee2() // corps de la fonction } // mort de b : appel à -Base() // trépas de dl : appels à -Deriveel() puis -Base() // éradication de d2 : appels à ~Derivee2(), // -Deriveel() puis -Base()
  • 34.
    L'héritage simple Exemple complet 45 R ep r e n o n s notre p r o b l è m e d e véhicules. Voici l e c o d e c o m plet des classes Véhicule et Voiture, et d e s e x e m p l e s d'accès a u x d o n n é e s de la classe de b a s e , à savoir Véhiculé. class Véhicule { private : char buffer_interne[128]; // uniquement utilisé par les fonctions de // la classe Véhicule. Inaccessible aux // classes dérivées. protected : char nom_vehicule[20]; int nombre_de_places; // données protégées, transmises aux classes // dérivées mais toujours inaccessibles / / à l'extérieur de l'objet public : // fonctions qui resteront publiques // dans les classes dérivées (à part les // constructeurs) // constructeur Véhicule(char *nom, int places); // fonctions d'accès aux données membre char *get_nom_vehicule(); int get_nombre_de_places(); void set_nom_vehicule(char *nom); int set_nombre_de_place(int p ) ; // fonctions de traitement void Afficher();; }; // définition des fonctions Véhicule::Véhicule(char *nom, int places) { sprintf(nom_vehicule, "%20s", n o m ) ; // on écrit dans la chaine nom_vehicule les // premiers caractères de la chaine nom. nombre_de_places = places; } char *Vehicule::get_nom_vehicule() { return nom_vehicule; } int Véhicule::qet nombre de places!) { return nombre_de_places; 20
  • 35.
    46 Pont entre Cet C++ } void Véhicule::set nom„vehicule(char *nom) { sprintf(nom_vehicule, "%20s", nom); // on écrit dans la chaine nom_vehicule les 20 // premiers caractères de la chaine nom. } int { Véhicule::set_nombre_de_place(int p) nombre_de_places = p; } // Voici maintenant la classe dérivée class Voiture : public Véhicule { private : float SavantCalcul (); protected : int chevaux_fiscaux; public : // constructeurs Voiture(char *nom, int places, int chevaux); // fonctions d'accès int get_chevaux_fiscaux(); void set_chevaux_fiscaux(int c h ) ; // fonctions de traitement float CalculVignette(); } ; Voiture::Voiture(char *nom, int places, int chevaux) { sprintf(nom_vehicule, "%20s", n o m ) ; nombre_de_places = places; chevaux_fiscaux = chevaux; } int Voiture : :get_chevaux_fiscaux() { return chevaux_fiscaux; } void Voiture::set_chevaux_fiscaux(int ch) { chevaux_fiscaux = ch; } float Voiture::SavantCalcul(int code) { float savant_calcul; // on fait un savant calcul avec "code" return savant_calcul;
  • 36.
    L'héritage simple 47 } La fonction CalculVignettefait float Voiture::CalculVignette() { int code; n'importe quoi, soyez certain que if (chevaux_fiscaux < 2) code = 0 ; else code = chevaux_fiscaux - 1; return (SavantCalcul(code)); l'auteur en a parfaitement conscience. } Listes d'initialisations L e s lecteurs les p l u s alertes n e m a n q u e r o n t p a s d e c o n s t a t e r q u e l q u e s l o u r d e u r s d a n s l ' e x e m p l e p r é c é d e n t . E x a m i n e z les c o n s t r u c t e u r s de la classe Voiture. E x a m i n e z m a i n t e n a n t les c o n s t r u c t e u r s de la classe Véhicule. Je récapitule p o u r v o u s les d e u x c o n s t r u c t e u r s ci-dessous. N e r e m a r q u e z - v o u s pas une certaine ressemblance ? Véhicule::Vehicule(char *nom, int places) { sprintf(nom„vehicule, "%20s", nom); nombre_de_places = places; } Voiture::Voiture(char *nom, int places, int chevaux) { sprintf(nom_vehicule, %20s", nom); nombre de_places = places; chevaux_fiscaux = chevaux; } n O h , m a i s c'est b i e n sûr ! L e s d e u x p r e m i è r e s lignes s o n t i d e n t i q u e s ! N ' e s t - c e p a s là le s i g n e p r o v o c a t e u r d ' u n e red o n d a n c e inutile et d a n g e r e u s e p r o p r e à susciter u n e colère aussi v i v e q u e justifiée ? Le p r o b l è m e vient du fait q u e les c o n s t r u c t e u r s (ainsi q u e les d e s t r u c t e u r s , m a i s c e n ' e s t p a s l e p r o p o s ici) n e s o n t p a s hérités. C e l a signifie q u e , q u e l q u e part d a n s le c o n s t r u c t e u r de Voiture, v o u s d e v e z initialiser les v a r i a b l e s - m e m b r e s défin i e s d a n s Véhicule, et d o n t Voiture a hérité. Ici, il s'agit d e s v a r i a b l e s nom et places. Le p r o b l è m e est en fait s i m p l e : c o m m e n t faire p o u r appeler un c o n s t r u c t e u r de la c l a s s e de B a s e , d e p u i s un c o n s t r u c t e u r d ' u n e classe d é r i v é e ? D e u x solutions :
  • 37.
    48 Pont entre Cet C++ Vous êtes en train de paniquer ? Vous avez l'impression d'être noyé sous les n o u veautés syntaxiques du C + + ? Ne trem- Faire c o m m e d a n s l ' e x e m p l e , c'est-à-dire recopier les lig n e s de c o d e du constructeur de la c l a s s e de b a s e qui v o u s intéresse. C ' e s t très laid : d u p l i q u e r b ê t e m e n t du c o d e , c'est alourdir le p r o g r a m m e , et si j a m a i s ce c o d e doit être modifié, v o u s devrez retrouver tous les e n d r o i t s où v o u s l ' a v e z recopié. I m a g i n e z u n p e u c e q u e cela peut d o n n e r d a n s u n e hiérarchie de p l u s i e u r s c e n t a i n e s de classes ! La m e i l l e u r e solution : utiliser u n e liste d'initialisation. Q u ' e s t - c e d o n c ? C ' e s t un m o y e n p r a t i q u e d ' a p p e l e r direct e m e n t des constructeurs p o u r des d o n n é e s - m e m b r e qu'il faut initialiser. Voici un e x e m p l e d'utilisation de la liste d'initialisation : blez plus, car l'aidem é m o i r e C + + est arrivé ! Il est joli et il vous attend sur le // constructeur de la classe Dérivée, amélioré Voiture ::Voiture(char *nom, int places, int chevaux) : Véhicule(nom, places) { rabat de la couverture ! chevaux_fiscaux = chevaux; ) J u s t e après les p a r a m è t r e s du constructeur, tapez le caractère : (deux points) et a p p e l e z e n s u i t e les constructeurs d e s classes de b a s e s q u e v o u s a v e z c h o i s i s , a v e c leurs par a m è t r e s b i e n e n t e n d u . Si v o u s a p p e l e z p l u s i e u r s constructeurs, séparez-les p a r u n e virgule. D a n s l ' e x e m p l e ci-dessus, les p a r a m è t r e s nom e t p l a c e s utilisés p o u r appeler l e c o n s t r u c t e u r d e V é h i c u l e sont ceux que vous avez passés au constructeur de V o i t u r e . V o u s p o u v e z é g a l e m e n t utiliser u n e liste d'initialisation p o u r v o s variables s i m p l e s (entier, caractère, etc.) Exemple : // constructeur de la classe Dérivée, encore amélioré Voiture::Voiture(char *nom, int places, int chevaux) : Véhicule(nom, places), chevaux_fiscaux(chevaux) { } L e s différents é l é m e n t s de la liste d'initialisation sont sép a r é s p a r u n e virgule. V o u s r e m a r q u e z q u e m a i n t e n a n t , il n ' y a plus d'instruction d a n s le corps de la fonction ! La liste d'initialisation est le seul m o y e n d'initialiser une d o n n é e - m e m b r e spécifiée constante. R a p p e l o n s qu'une
  • 38.
    L'héritage simple 49 c on s t a n t e ne p e u t être qu'' initialisée (et j a m a i s affectée), voir p a g e 67. Un autre a v a n t a g e d e s listes d'initialisation : elles n e créent p a s d'objets t e m p o r a i r e s c o m m e u n e fonction ordinaire. L e s objets sont initialisés d i r e c t e m e n t , d ' o ù gain de t e m p s certain. Résumé U n e liste d'initialisation p e u t appeler d e s c o n s t r u c t e u r s d e s classes d e b a s e o u initialiser des variables s i m p l e s . R é s u m é de la s y n t a x e : Classe::Classe(paramètres) : liste_ini { // corps du constructeur de Classe } O ù l i s t e _ i n i contient u n o u p l u s i e u r s é l é m e n t s s u i v a n t s , séparés p a r d e s virgules : ConstructeurClasseDeBase(paramètres) ou Variable(valeur_initiale)
  • 39.
    La surcharge Le C++offre aux programmeurs la possibilité de définir des fonctions homonymes. Ce principe est appelé « surcharge de fonction ». Notions de base L'idée Écrire d e u x fonctions qui portent l e m ê m e n o m est u n e c h o s e impensable en langage C. Pas en C + + . Imaginez que vous v o u l i e z écrire u n e fonction qui trie des tableaux d'entiers. L ' e n t ê t e de cette fonction ressemblerait à ceci : void tri(int tab[], int taille_tableau); Si p a r la suite v o u s a v e z b e s o i n d ' u n e fonction q u i trie des tableaux de réels, v o u s d e v e z réécrire la fonction, en lui donn a n t un autre n o m : void tri_reels(float tab[], int taille_tableau); S i v o u s êtes logiques avec v o u s - m ê m e , v o u s d e v r e z égalem e n t c h a n g e r le n o m de la fonction initiale : void tri_entiers(int tab[], int taille_tableau);
  • 40.
    52 Pont entre Cet C++ Ce qui v o u s oblige à modifier tous les appels à cette fonction. G r â c e au m é c a n i s m e de surcharge de fonction, v o u s pouvez d o n n e r le n o m tri a u x d e u x fonctions, le compilateur pouv a n t les différencier grâce a u x types d ' a r g u m e n t s . N o t e z q u e le l a n g a g e C p r o p o s e déjà un type de surcharge p o u r les o p é r a t e u r s arithmétiques : le m é c a n i s m e mis en œ u v r e d é p e n d du type des objets sur l e s q u e l s on invoque l'opérateur. Mise en œuvre C++ R e p r e n o n s notre e x e m p l e de tri, et appliquons-lui le principe de s u r c h a r g e . L e s d e u x entêtes d e v i e n n e n t : void void tri(int tab[], int taille_tableau); triffloat tab[], int taille_tableau); La seule différence étant le type du p r e m i e r paramètre. E x a m i n o n s m a i n t e n a n t les appels de fonctions suivants : void main() { float int tab_f[] = { 1.5, 1.2, 7.2, 0.07 } ; tab_i[] = { 3, 2, 1, 0 }; tri(tab_f, 4 ) ; tri(tab_i, 4 ) ; } Le p r e m i e r a p p e l à tri fait a u t o m a t i q u e m e n t référence à la d e u x i è m e fonction tri, qui accepte un tableau de flottants en p r e m i e r p a r a m è t r e . Le d e u x i è m e a p p e l à tri se fait natur e l l e m e n t avec la fonction tri qui s ' o c c u p e d e s entiers. C o m m e n t le c o m p i l a t e u r fait-il la différence e n t r e plusieurs fonctions h o m o n y m e s ? T o u t d ' a b o r d , seules les fonctions situées d a n s la portée de l'appel —c'est-à-dire « visibles »— sont p r i s e s en compte. E n s u i t e , les critères suivants sont e x a m i n é s : 1. Le type ou le n o m b r e de p a r a m è t r e s . D è s q u e d e u x fonctions h o m o n y m e s diffèrent p a r le t y p e ou le n o m b r e de l'un ou de p l u s i e u r s de leurs p a r a m è t r e s , le compilateur reconnaît la b o n n e fonction s e l o n les types réels passés à l'appel. C ' e s t l ' e x e m p l e q u e n o u s v e n o n s d e voir.
  • 41.
    La surcharge 53 Attention !Le type de retour de la fonction n ' e s t j a m a i s pris en c o m p t e ! Par e x e m p l e , il est illégal de déclarer les d e u x fonctions suivantes : int float truc(int); truc(int); Le c o m p i l a t e u r ne pourrait faire la différence. En revanc h e , il est toujours possible d'écrire : int float truc(int); truc(float); P u i s q u e , cette fois, le type des p a r a m è t r e s est différent. * Les classes de base 2. Si l'étape 1 ne p e r m e t pas d'identifier la fonction à a p p e ler, le c o m p i l a t e u r essaie de convertir les p a r a m è t r e s réels vers les p a r a m è t r e s des fonctions h o m o n y m e s . A i n s i , u n c h a r o u u n s h o r t seront convertis v e r s u n int, les float seront convertis v e r s des double, les p o i n t e u r s de classes dérivées seront convertis en p o i n t e u r s de classes de base*, etc. et les classes dérivées seront expliquées au chaprtre 3, sur l'héritage. L'intérêt par rapport au C En cas d ' a m b i g u ï t é insoluble, le c o m p i l a t e u r signale u n e erreur. D e toutes m a n i è r e s , m i e u x v a u t éviter les cas a m b i g u s qui sont s o u r c e d'affres éternelles. O n pourrait croire q u e les fonctions h o m o n y m e s r e n d e n t les p r o g r a m m e s m o i n s lisibles et plus confus. C e r t e s , si v o u s définissez plusieurs fonctions « traite_donnees », qui font des c h o s e s t o t a l e m e n t différentes, le gain en clarté sera plutôt discutable. E n r e v a n c h e , d a n s les cas o ù v o u s d i s p o s e z d ' u n j e u d e fonctions qui fait p r a t i q u e m e n t la m ê m e c h o s e , m a i s d o n t certains p a r a m è t r e s diffèrent, v o u s a v e z intérêt à utiliser la s u r c h a r g e . Bref, c h a q u e fois q u e le sens des fonctions est s e n s i b l e m e n t identique, d o n n e z - l e u r l e m ê m e n o m . D a n s tous les c a s , le l a n g a g e C v o u s oblige à d o n n e r d e s n o m s différents, s i m p l e m e n t p o u r q u e l e c o m p i l a t e u r reconnaisse ses petits. R e m a r q u o n s q u e d a n s l ' e x e m p l e du tri,
  • 42.
    54 Pont entre Cet C + + n o u s p o u r r o n s aussi n o u s servir du m é c a n i s m e de la généricité (mis en œ u v r e par les templates), e x p o s é au c h a p i t r e 10. Compléments Surcharge d'opérateurs E n C + + , les o p é r a t e u r s c o u r a n t s ( + , - , = = , etc.) p e u v e n t être s u r c h a r g é s . C o m m e n t est-ce p o s s i b l e ? En fait, q u a n d vous utilisez un opérateur op sur un objet, le c o m p i l a t e u r génère un appel à la fonction operator op. L e s trois écritures suiv a n t e s sont é q u i v a l e n t e s : objet_l = objet_2 + objet_3; objet_l = operatort (objet2, objet3); objet_l = objet_2.(operator+(objet_3)); // (1) // (2) « o p e r a t o r op », où op est un o p é r a t e u r s t a n d a r d (ici +) est en réalité u n n o m d e fonction c o m m e u n autre, qui est défini p a r défaut p o u r les types d e b a s e d u C + + . D o n c , p o u r personnaliser le c o m p o r t e m e n t des o p é r a t e u r s , il v o u s suffit de s u r c h a r g e r la fonction « o p e r a t o r op » correspondante, c o m m e v o u s s a v e z déjà le faire p o u r v o s p r o p r e s fonctions. Attention ! Q u a n d v o u s s u r c h a r g e z un o p é r a t e u r , il faut veiller à choisir l'une ou l'autre des d e u x m é t h o d e s de l ' e x e m p l e ci-dessus : D a n s le cas (1), il s'agit de la fonction operator+ globale, définie en d e h o r s de toute classe. Elle p r e n d d e u x paramètres, c o r r e s p o n d a n t aux d e u x m e m b r e s d e l'addition. D a n s le cas ( 2 ) , il s'agit de la f o n c t i o n - m e m b r e operator+, définie d a n s la classe d'objet_2, q u i s u p p o s e que l'objet sur lequel elle est a p p e l é e est le p r e m i e r paramètre de l'opération d'addition (ici ob j et_2). Si v o u s utilisiez à la fois ces d e u x m é t h o d e s de surcharge p o u r la m ê m e fonction, le c o m p i l a t e u r ne pourrait p a s faire la différence. V o u s p o u v e z s u r c h a r g e r les o p é r a t e u r s s u i v a n t s :
  • 43.
    La surcharge + - * / + =- = *= / = ! = < = >= && A % A %= = || ++ & &= -- ~ |= , i = 55 < << » > > = < < = ->* -> () [] > == V o y o n s l ' e x e m p l e d ' u n p r o g r a m m e c o m p l e t redéfinissant l'opérateur + p o u r des objets de classe C o l o r : #include <stdio.h> class Color { private : int rouge; int vert; int bleu; public : // constructeurs (voir chapitre 2) Color(int r, int v, int b ) ; Color(); void Afficher)); Color operator+ (Color Sdroite); }; Color;:Color() { rouge = vert = bleu = 0 ; } Color::Color(int r, int v, int b) { rouge = r; vert = v; bleu = b; ) void { Color::Afficher() printf("rouge=%d vert=%d bleu=%dn", rouge, vert, bleu); } * Le passage par référence est une exclusivité C++ ! Vous en saurez plus en lisant le chapitre 9, page 113. Color Color::operator+ (Color Sdroite) // droite est passé par « référence »* { // fonction redéfinissant l'opérateur + : // RESULTAT = A + B / / A est l'objet Color courant // B est le paramètre "droite" // RESULTAT est la valeur retournée Color résultat) (rouge + droite.rouge) / 2, (vert + droite.vert) / 2, (bleu + droite.bleu) / 2 ) ;
  • 44.
    56 Pont entre Cet C++ return résultat; } void { main() Color // // rouge_pur(10, 0, 0 ) , bleu_pur(0, 0, 10), mixage ; mixage = rouge_pur + bleu_pur; cette ligne éqruivaut à : mixage = rouge_pur.operator+ (bleu_pur) mixage.Afficher(); // affiche "rouge=5 vert=0 bleu=5" } Plusieurs r e m a r q u e s : Le p r e m i e r p a r a m è t r e d ' u n e fonction o p é r a t e u r (Color &droite d a n s l ' e x e m p l e ) est p a s s é p a r référence (voir chapitre 9, p a g e 1 1 3 ) . En d e u x m o t s : q u a n d v o u s p a s s e z un p a r a m è t r e p a r référence, cela é q u i v a u t à p a s s e r l'objet original. Ainsi, modifier un p a r a m è t r e p a s s é p a r référence revient à modifier le p a r a m è t r e réel, utilisé à l'appel de la fonction. U n o p é r a t e u r r e t o u r n e u n objet d e l a m ê m e classe q u e ses p a r a m è t r e s (ici, un objet de classe Color). V o u s devrez d o n c créer un objet de cette classe d a n s la fonction opérateur, l'initialiser en fonction des d e u x p a r a m è t r e s de l'opérateur, et le retourner. P o u r q u o i passer droite par référence dans o p e r a t o r + ? E x p l i q u o n s plutôt p o u r q u o i un p a s s a g e p a r valeur habituel serait p r o b l é m a t i q u e . A d m e t t o n s q u e n o u s a y o n s défini operator+ c o m m e suit : Color Color::operator+ (Color droite) // droite est passé par valeur { // . . . } L e p a r a m è t r e d r o i t e étant p a s s é p a r v a l e u r , u n e copie s ' o p è r e entre le p a r a m è t r e effectif (l'objet b l e u _ p u r ) et le p a r a m è t r e formel utilisé d a n s l a fonction (l'objet d r o i t e ) .
  • 45.
    La surcharge 57 D an s c e cas, l a c o p i e n e p o s e p a s d e p r o b l è m e , m a i s si, p a r e x e m p l e , votre classe c o m p o r t a i t un p o i n t e u r alloué, la c o p i e d'objet p a r défaut serait m a u v a i s e . A v e c un p a s s a g e p a r référence, il n ' y a p a s de c o p i e , p u i s q u e l a fonction o p e r a t o r + agit d i r e c t e m e n t sur l e p a r a m è t r e effectif, b l e u _ p u r . E n d'autres t e r m e s , m a n i p u l e r l'objet d r o i t e é q u i v a u t à m a n i p u l e r l'objet b l e u _ p u r . Récapitulons : Soit v o u s utilisez u n p a s s a g e p a r référence e t v o u s n ' a v e z p a s b e s o i n de v o u s soucier de la c o p i e de v o t r e objet, Soit v o u s utilisez u n p a s s a g e par v a l e u r e t v o u s d e v e z vérifier q u e la copie s'effectue bien. Si n é c e s s a i r e , définissez un c o n s t r u c t e u r de c o p i e (voir p a g e 3 1 ) . Inutile de préciser — m a i s je le fais q u a n d m ê m e — q u e la p r e m i è r e solution est plus intéressante q u e la s e c o n d e . Surcharge de l'opérateur = P o u r q u o i redéfinir l'opérateur = ? L'affectation d ' u n objet, via l'opérateur = , est u n e o p é r a t i o n sensible qui mérite q u ' o n s'y attarde. Si v o u s ne définissez p a s d'opérateur = p o u r u n e classe, le c o m p i l a t e u r en g é n è r e un p a r défaut qui effectue u n e affectation « b ê t e », d o n n é e s membre à données membre. Bien que ce genre de comport e m e n t soit satisfaisant d a n s le cas de classes s i m p l e s , il devient parfois d a n g e r e u x si v o u s utilisez des p o i n t e u r s . Le p r o b l è m e est similaire à celui du constructeur-copie (voir page 31). C o n s i d é r o n s l ' e x e m p l e suivant : Sinclude <stdio.h> tinclude <string.h> Le constructeur Exclamation ainsi que la fonction Affiche sont class Exclamation { private: char *cri ; public : Exclamation(char *c) { cri = new char[strlen(c)+1]; strcpy(cri, c ) ; } définis « inline » void set_cri(char * n c ) ; (voir page 21 ). new void Affiche() { printf("%sn", cri); ) et delete remplacent malloc et free (voir chapitre 6 page 83). };
  • 46.
    58 Pont entre Cet C++ void Exclamation::set_cri(char *nc) { // vérifions si nous n'avons pas affaire aux // même pointeurs if (ne == cri) return; // libérons la mémoire de l'ancienne valeur de cri delete cri; // allouons de la mémoire pour la nouvelle valeur cri = new char[strlen(nc)+1]; // copions le paramètre dans la donnée cri. strcpy(cri, n e ) ; } void main() { Exclamation beurk("Beurk"), bof("Bof"); beurk.Affiche(); bof.Affiche(); // affiche "Beurk" // affiche "Bof" bof = beurk; // operateur= défini par défaut beurk.Affiche(); bof.Affiche(); // affiche "Beurk" // affiche "Beurk" bof.set_cri("Ecoeurant"); beurk.Affiche(); bof.Affiche(); / / o n modifie bof et beurk // affiche n'importe quoi // affiche "Ecoeurant" } La c o p i e p a r défaut a ici des effets d é s a s t r e u x : en copiant u n i q u e m e n t le p o i n t e u r cri et n o n ce qui est p o i n t é , n o u s n o u s r e t r o u v o n s avec d e u x objets d o n t la d o n n é e cri pointe sur la m ê m e z o n e m é m o i r e :
  • 47.
    La surcharge 59 Le résultatde cette d é c a d e n c e ne se fait p a s attendre : il suffit d e modifier l'un des d e u x objets p o u r q u e s o n j u m e a u soit affecté. C ' e s t ce q u e n o u s faisons dans la ligne bof . s e t _ c r i ( " E c o e u r a n t ") ; Cette instruction libère l ' a n c i e n n e v a l e u r d e c r i e n a p p e l a n t d e l e t e . O r , cette anc i e n n e valeur est é g a l e m e n t p a r t a g é e p a r l'objet b e u r k . S a n s le savoir, n o u s a v o n s d o n c effacé d e u x cris au lieu d'un. C o m m e n t redéfinir l'opérateur = ? C e s constatations affreuses n o u s p o u s s e n t à écrire notre propre opérateur d'affectation, qui va se c h a r g e r de copier le c o n t e n u d e s p o i n t e u r s cris. R a p p e l o n s q u e l ' u s a g e de = appelle l a fonction o p é r â t o r = . Voici l'exemple complet : tinclude <stdio.h> #include <string.h> class Exclamation { private: char *cri ; public: Exclamation(char *c) { cri = new char[strlen(c)+1]; strcpy(cri, c ) ; } void set_cri(char * n c ) ; void Affiche() { printf("%sn", cri); ) Exclamation }; &operator=(Exclamation &source);
  • 48.
    60 Pont entre Cet C++ void Exclamation::set_cri(char *nc) { if (ne == cri) return; delete cri; cri = new char(strlen(ne)+1]; strcpy(cri, n e ) ; } Cette fonction, qui retourne une référence, est expliquée à la suite Exclamation &Exclamation::operator=(Exclamation Ssource) { // évitons d'affecter deux objets identiques if (this == &source) return *this; // retournons l'objet courant du listing. delete cri; cri = new char[ strlen(source.cri)+1 ]; strcpy(cri, source.cri); return *this; // retournons l'objet courant } void main() { Exclamation beurk("Beurk"), bof("Bof"); beurk.Affiche(); bof.Affiche(); // affiche "Beurk" // affiche "Bof" bof = beurk; // operateur= défini par nos soins beurk.Affiche(); bof.Affiche(); // affiche "Beurk" // affiche "Beurk" bof.set_cri("Ecoeurant"); beurk.Affiche(); bof.Affiche(); // on ne modifie que bof // affiche "Beurk" : parfait ! // affiche "Ecoeurant" } P o u r q u o i ' r e t u r n *this'? L ' o p é r a t e u r = doit retourner un objet de type Exclamation. P l u s e x a c t e m e n t , i l doit retourner l'objet sur lequel o p e r a tor+ a été a p p e l é , a p r è s q u e l'affectation a été faite. En ret o u r n a n t *this, n o u s r e t o u r n o n s b i e n l'objet c o u r a n t q u e n o u s v e n o n s d e modifier. M a i s p o u r q u o i retourner u n e référence plutôt q u ' u n objet Exclamation tout s i m p l e ? P o u r r é p o n d r e c o r r e c t e m e n t au cas de figure suivant : (bof = beurk).Affiche();
  • 49.
    La surcharge 61 Si no u s r e t o u r n i o n s u n e s i m p l e copie du résultat, l ' o p é r a t i o n A f f i c h e n e s'effectuerait p a s sur b o f m a i s sur s a c o p i e . P o u r q u o i faire le test 'if (this == & s o u r c e ) ' ? Ce test a p o u r b u t de vérifier si n o u s ne v o u l o n s p a s affecter l'objet l u i - m ê m e (ce serait le cas de l'instruction a = a ; ). Si c e test n'était p a s réalisé, n o u s n o u s r e t r o u v e r i o n s d a n s u n e situation très désagréable. R e g a r d e z un instant la p r e m i è r e ligne après l e test. O u i , c'est b i e n d e l e t e c r i . I m a g i n e z m a i n t e n a n t q u ' u n c o q u i n ait écrit b e u r k = b e u r k . E n faisant d e l e t e c r i , n o u s effacerions l e c r i d e l'objet s o u r c e mais aussi, sans le vouloir, celui de l'objet destination. D è s lors, rien d ' é t o n n a n t à ce q u e n o u s n o u s e x p o s i o n s à l ' i m p r é v u e n a c c é d a n t a u c r i d e l'objet source. Opérateurs de conversion de types L'opération réciproque, à savoir la conversion d'un type quelconque en un objet de la classe, est tout à fait possible ! Il P o u r autoriser et contrôler la c o n v e r s i o n d ' u n e classe A v e r s un type T q u e l c o n q u e , v o u s p o u v e z définir un o p é r a t e u r de c o n v e r s i o n de n o m operator T ( ). C h a q u e fois q u ' u n objet de classe A doit être c o n s i d é r é c o m m e u n e v a r i a b l e de t y p e T, l'appel à cet o p é r a t e u r se fera a u t o m a t i q u e m e n t . Attention ! Ce g e n r e d ' o p é r a t e u r ne doit pas avoir de type de retour, m a i s doit tout de m ê m e r e t o u r n e r u n e variable de t y p e T (on évite ainsi l a r e d o n d a n c e d u n o m d u t y p e e t d e s r i s q u e s d ' i n c o h é r e n c e liés à cette r e d o n d a n c e superflue). A d m e t t o n s q u e v o u s v o u l i e z m o d é l i s e r des articles d a n s u n m a g a s i n . La classe Article, qui contient diverses informations, doit p o u v o i r être utilisée d a n s d e s e x p r e s s i o n s de calcul de prix. Il faut d o n c définir operator float ( ) ; suffit d'utiliser un constructeur spécial (voir page 35), On ne devrait pas spécifier la taille du tableau directement dans la classe, mais plutôt utiliser une constante. Toutes mes excuses p o u r cette hérésie éhontée. #include <string.h> #include <stdio.h> class Article { protected: char nom[50]; float prix; int nb_disponible; public: Article(char *n, float p, int nb) : prix(p), nb_disponible(nb) { strcpy(nom, n ) ; } operator float(); };
  • 50.
    62 Pont entre Cet C++ Article : :operator float() { return prix; } void main() { float total; Article b("Bonne bière", 12., 3 0 ) ; total = 5 * b; // conversion de b en float printf("5 bonnes bières valent %.2f F", total); } // affichage : 5 bonnes bières valent 60.00 F
  • 51.
    Les petits +du C++ Pour conclure cette première partie, attardons-nous sur les petits changements qui rendent la vie du programmeur plus agréable. Contrairement aux autres chapitres, celui-ci ne se décompose pas en deux parties (notions de base et compléments). Chaque point est expliqué en une seule fois, sans distinction de « niveau de difficulté ». Les commentaires V o u s êtes u n p r o g r a m m e u r c o n s c i e n c i e u x , v o u s c o m m e n t e z donc abondamment vos programmes. Le C + + a pensé à v o u s e t p r o p o s e u n e n o u v e l l e m a n i è r e d'introduire d e s c o m m e n t a i r e s d a n s les c o d e s - s o u r c e s : void main() { // commentaire pertinent jusqu'à la fin de la ligne } L e s d e u x caractères « / / » i n d i q u e n t au p r é - p r o c e s s e u r le d é b u t d ' u n c o m m e n t a i r e , qui s'arrête a u t o m a t i q u e m e n t à la fin de la ligne. V o u s n ' a v e z d o n c p a s b e s o i n de r é p é t e r / / p o u r m e t t r e fin au c o m m e n t a i r e .
  • 52.
    64 Pont entre Cet C++ B i e n e n t e n d u , v o u s p o u v e z toujours utiliser l ' a n c i e n style de c o m m e n t a i r e (entre / * e t * / ) , très p r a t i q u e p o u r les prog r a m m e u r s qui ont b e a u c o u p de c h o s e s à r a c o n t e r — sur p l u s i e u r s lignes. L e s d e u x styles d e c o m m e n t a i r e s peuvent être i m b r i q u é s : u n e section de c o d e entre / * et * / p e u t contenir d e s c o m m e n t a i r e s / / , et r é c i p r o q u e m e n t . Ruse de sioux Il existe un autre m o y e n de m e t t r e en c o m m e n t a i r e de nomb r e u s e s lignes, très utile si ces lignes c o n t i e n n e n t déjà des c o m m e n t a i r e s avec / * et * /. P e n s e z au p r é - p r o c e s s e u r et utilisez # i f d e f e t # e n d i f . Voici l e listing initial : void fonction_boguee() { char *pointeur; // pointeur sur un truc /* début de la fonction */ strcpy(pointeur, "Plantage"); } V o u s v o u l e z m e t t r e en c o m m e n t a i r e tout le c o n t e n u de la fonction. A m o i n s de s u p p r i m e r le c o m m e n t a i r e intérieur, v o u s ne p o u v e z p a s utiliser / * et * / , p u i s q u e le p r e m i e r / * serait fermé par le * / du c o m m e n t a i r e intérieur. La solution : void { #if 0 char fonction_boguee() *pointeur; // pointeur sur un truc /* début de la fonction */ strcpy(pointeur, "Plantage"); #endif } Rappelons que # i f expression est é v a l u é p a r l e prép r o c e s s e u r . Si expression est vrai, le c o d e qui suit (jusqu'à # e n d i f ) est c o m p i l é . Il suffit d o n c de d o n n e r u n e expression fausse p o u r m e t t r e en « c o m m e n t a i r e s » la z o n e de code v o u l u e . C ' e s t p o u r q u o i n o u s utilisons # i f 0 qui n ' e s t jam a i s vrai.
  • 53.
    L e spetits + du C++ 65 Paramètres par défaut Là aussi, u n e idée s i m p l e et efficace : v o u s p o u v e z d o n n e r u n e v a l e u r p a r défaut p o u r u n o u p l u s i e u r s p a r a m è t r e s d e fonction, d a n s la déclaration de cette fonction. A i n s i , à l'appel, v o u s p o u v e z o m e t t r e les p a r a m è t r e s qui p o s s è d e n t u n e v a leur p a r défaut. E x e m p l e : Ici, on donne une /* fichier fonction.h */ float calcul_tva(float montant, float tva = 18.6); valeur par défaut (18.6) au deuxième paramètre de la fonction calcul_tva. /* fichier fonction.cpp */ float calcul_tva(float montant, float tva) { if (tva > 0. && tva < 100.) return montant * (tva / 100.); else { /* traitement d'erreur... */ } } /* fichier main.cpp */ #include "fonction.h" void main() { float res; res = calcul_tva(100.); // 2ème argument vaut 18.6 res = calcul_tva(100., 5.5); res = calcul_tva(100., 18.6); } I l existe d e u x possibilités d ' a p p e l e r l a fonction c a l cul_tva : A v e c d e u x p a r a m è t e s : c'est la solution c l a s s i q u e . D a n s ce cas, le p a r a m è t r e p a r défaut est « é c r a s é » p a r la v a l e u r d'appel du deuxième paramètre. A v e c un seul p a r a m è t r e : d a n s ce c a s , le c o m p i l a t e u r s u p p o s e q u e l e d e u x i è m e p a r a m è t r e p r e n d l a v a l e u r p a r défaut (dans l ' e x e m p l e , 1 8 . 6 ) . Attention : t o u s les p a r a m è t r e s qui d i s p o s e n t d ' u n e v a l e u r p a r défaut d o i v e n t être déclarés après les p a r a m è t r e s norm a u x , c'est-à-dire à la fin de la liste des p a r a m è t r e s .
  • 54.
    66 Pont entre Cet C++ P l u s i e u r s p a r a m è t r e s par défaut Il est p o s s i b l e de définir u n e v a l e u r par défaut p o u r plusieurs p a r a m è t r e s d'une fonction. Veillez c e p e n d a n t à les déclarer après les p a r a m è t r e s n o r m a u x . L ' e x e m p l e suivant est i m p o s s i b l e car a m b i g u : void phonquession(int a = 1, int b, int c = 3); // erreur ! C e t t e déclaration est i m p o s s i b l e : le p r e m i e r p a r a m è t r e a une v a l e u r p a r défaut, m a i s p a s le d e u x i è m e . P o u r q u o i n'est-ce p a s autorisé ? C ' e s t a s s e z l o g i q u e . I m a g i n e z q u e v o u s appeliez la fonction c o m m e ceci : phonquession(12 , 13); M e t t e z - v o u s un instant d a n s la p e a u du p a u v r e compilateur : q u e v e u t dire cet appel ? Q u e le p r e m i e r p a r a m è t r e vaut 12, et le d e u x i è m e 13 ? Ou b i e n le d e u x i è m e 12 et le troisième 13 ? A l l o n s , s o y o n s r a i s o n n a b l e s , et c o n t e n t o n s - n o u s de déclarer les v a l e u r s par défaut à la file, en fin d ' e n t ê t e : void phonquession(int // mieux. b, int a = 1, int c = 3 ) ; A i n s i , n o u s l e v o n s toute a m b i g u ï t é . Voici c o m m e n t seraient interprêtés différents appels à p h o n q u e s s i o n : phonquession(10); // b:10 a:l phonquession(10, 11); // b:10 a:ll phonquession(10, 11, 1 2 ) ; // b:10 a:ll c:3 c:3 c:12
  • 55.
    Les petits +du C++ 67 Les constantes Habitué(e) du l a n g a g e C, v o u s d e v e z c e r t a i n e m e n t utiliser # d e f ine à tour de b r a s p o u r définir v o s c o n s t a n t e s . L e s p l u s a u d a c i e u x d'entre v o u s a u r o n t peut-être a d o p t é le mot-clé const ( d i s p o n i b l e d e p u i s la n o r m e ANSI du l a n g a g e C ) . Le C + + v a p l u s loin e n é t e n d a n t les possibilités d e const. N o u s allons voir tout cela au travers des différents m o y e n s d'utiliser des c o n s t a n t e s e n C + + . U n e c o n s t a n t e s i m p l e , visible d a n s un b l o c ou un seul fichier s o u r c e C ' e s t la m a n i è r e la plus s i m p l e de déclarer u n e c o n s t a n t e : const float TVA = 0.186; Il est i n d i s p e n s a b l e d'initialiser la c o n s t a n t e au m o m e n t de sa déclaration. On ne p e u t p a s s i m p l e m e n t déclarer const float TVA, puis, q u e l q u e s lignes en d e s s o u s , affecter u n e valeur à TVA. T o u t c o m p i l a t e u r C + + v o u s insultera si v o u s agissez de la sorte. D a n s le cas d ' u n tableau constant, ce sont tous ses é l é m e n t s qui sont c o n s t a n t s : const float tab[2] = 1.1; tab[3] = { 2.2, 3.3, 4.4 } ; // impossible, refusé par le compilateur C o m m e toutes les autres variables, la p o r t é e d ' u n e c o n s t a n t e d é p e n d de l'endroit où elle a été déclarée. C o n s t a n t e s et p o i n t e u r s Il faut b i e n distinguer ce qui est pointé du pointeur l u i - m ê m e . G r â c e à const, n o u s s o m m e s en m e s u r e de spécifier au c o m p i l a t e u r si n o u s v o u l o n s q u e le pointeur soit c o n s t a n t , ou b i e n q u e les données pointées soient c o n s t a n t e s , ou e n c o r e q u e les d e u x soient figés. Détaillons ces trois c a s :
  • 56.
    68 Pont entre Cet C++ char char char chainel[] = "baratin"; chaine2 [ ] = "billevei/sées " ; chaine3[] = "balivernes"; // ce qui est en gras est constant const char * p = chainel; char * const p = chaine2; const char * const p = chaine3; // (1) // (2) // (3) D a n s (1), la c h a î n e " b a r a t i n " est c o n s t a n t e , m a i s pas le p o i n t e u r p. V o u s p o u v e z d o n c écrire p = 0 (après v o u s être assuré q u ' u n autre p o i n t e u r d é s i g n e " b a r a t i n " , sans quoi v o u s ne p o u v e z p l u s y a c c é d e r ) . D a n s (2), c'est le p o i n t e u r p qui est constant. Si v o u s utilisez u n autre p o i n t e u r p o u r a c c é d e r à " b i l l e v e u s é e s " , v o u s p o u r r e z la modifier librement. D a n s (3), tout est verrouillé. Ni p ni la chaîne " b a l i v e r n e s " n e p e u v e n t être modifiés. Illustrons u n e dernière fois ce qui est p o s s i b l e et ce qui ne l'est p o i n t : void main() { char chainel[] = "baratin"; char chaine2[] = "billeveusées"; char chaine3[] = "balivernes"; const char * p = chainel; // (1) char * const p = chaine2; // (2) const char * const p = chaine3; // (3) pl[2] = 'X'; pl = new char[10]; // impossible // OK p2[2] = 'X•; p2 = new char[10]; / / OK // impossible p3[2] = 'X'; p3 = new char[10]; // impossible // impossible } Pourquoi ne pas avoir déclaré les chaînes comme ceci : const char * p = "baratin"; En c o d a n t la c h a î n e " b a r a t i n " en « d u r » c o m m e ci-dessus, n o u s n ' a u r i o n s p a s pu la modifier. En effet, u n e chaîne dé-
  • 57.
    Les petits +du C++ 69 clarée ainsi est s t o c k é e d a n s u n e z o n e d e m é m o i r e p r o t é g é e . T o u t accès à cette z o n e e n g e n d r e un p l a n t a g e p l u s ou m o i n s i m m é d i a t . N o u s a v o n s d o n c été contraints d'utiliser d e s v a riables intermédiaires, qui stockent les d o n n é e s d a n s u n e z o n e d e m é m o i r e modifiable. U n e c o n s t a n t e s i m p l e , v i s i b l e d a n s p l u s i e u r s f i c h i e r s sources Un petit rappel de C s t a n d a r d s ' i m p o s e . Si v o u s v o u l e z déclarer u n e variable globale visible d a n s p l u s i e u r s fichiers sources, v o u s p r o c é d e z ainsi : C o m m e v o u s l e v o y e z , l a variable g l o b a l e n ' e s t définie q u ' u n e seule fois, d a n s l e fichier m a i n . c p p . C h a q u e fois q u ' u n autre fichier v e u t p o u v o i r l'utiliser, il doit i n d i q u e r q u e cette variable existe et est définie « ailleurs », d a n s un autre fichier (c'est l e rôle d u m o t - c l é e x t e r n ) .
  • 58.
    Les petits +du C++ 71 D é c l a r e r q u ' u n e fonction r e t o u r n e u n e c o n s t a n t e N e lisez p a s c e p a r a g r a p h e avant d e c o n n a î t r e les références, e x p o s é e s a u chapitre 9 , p a g e 113. P o u r q u ' u n e fonction retourne u n e référence c o n s t a n t e , i l suffit d ' i n d i q u e r c o n s t a v a n t le type de retour de la fonction. L'intérêt de cette t e c h n i q u e est d'éviter la c o p i e d ' u n objet entre la fonction a p p e l é e et la fonction appelante, tout en p r é s e r v a n t l'intégrité de l'objet. E x e m p l e : const int &f() { static big_objet big; // manipulations sur big return big; } void main() { f() = 0; // déclenche une erreur de compilation // à cause du const. } D é c l a r e r u n e c o n s t a n t e interne à u n e classe C ' e s t un p r o b l è m e intéressant : c o m m e n t déclarer u n e c o n s tante qui ne sera visible et utilisable q u e d a n s sa classe ? V o u s seriez peut-êter tenté de p r o c é d e r ainsi : class MaClasse { private: const int MAX = 100; public : // incorrect, hélas // . . . } ; C ' e s t oublier q u e l'on ne p e u t p a s affecter de v a l e u r directem e n t d a n s l a déclaration d ' u n e classe. V o u s n e p o u v e z q u e déclarer d e s variables, p a s les initialiser. M a l i n , v o u s v o u s dites q u e v o u s aller initialiser la c o n s t a n t e en d e h o r s de la classe : class MaClasse { private: const int MAX; public : // ... // toujours incorrect
  • 59.
    72 Pont entre Cet C++ } ; MaClasse::MAX = 100; // toujours incorrect C ' e s t e n c o r e i m p o s s i b l e , car v o u s n e p o u v e z affecter a u c u n e v a l e u r à u n e c o n s t a n t e , en d e h o r s de s o n initialisation. Et ce n ' e s t p a s u n e initialisation ici, car MAX est u n e variable d ' i n s t a n c e (liée à un objet précis) et n o n de classe. Elle ne p e u t d o n c p a s être initialisée sans être s t a t i c . Q u e faire, m e direz-vous ? I l existe d e u x solutions. U n e avec s t a t i c c o n s t , l'autre avec enum. La static const solution. U n e c o n s t a n t e p r o p r e à u n e classe doit être l o g i q u e m e n t déclarée s t a t i c c o n s t . Elle doit être s t a t i c car c'est u n e variable de classe, d o n t la valeur est c o m m u n e à tous les objets de cette classe ; et elle doit être c o n s t car on v e u t interdire les modifications de sa valeur. L a seule m a n i è r e d'initialiser u n e variable s t a t i c c o n s t déclarée d a n s u n e classe, c'est de l'initialiser en d e h o r s de la classe. En voici la d é m o n s t r a t i o n : class MaClasse { private: static const int MAX; public : MaClasse(); // constante de classe } ; const int MaClasse::MAX = 100; L ' e n u m solution Si v o s c o n s t a n t e s sont de t y p e entier, v o u s p o u v e z utiliser e n u m p o u r les déclarer et les initialiser à l'intérieur d ' u n e classe. B i e n q u e cela ne m a r c h e q u e sur les entiers, cette solution p r é s e n t e l ' a v a n t a g e d'initialiser la c o n s t a n t e immédiatement. C ' e s t la seule m é t h o d e q u e je c o n n a i s s e pour initaliser u n e c o n s t a n t e de classe d a n s le corps de la c l a s s e , et d o n c qui p e r m e t t e de l'utiliser p o u r d i m e n s i o n n e r un tableau d a n s la classe. V o i c i un e x e m p l e :
  • 60.
    Les petits +du C++ class MaClasse { private: enum { MAX = 100 }; int tab[MAX]; public : 73 // MAX est de type int // OK // ... >; Déclarations E n C + + v o u s p o u v e z déclarer v o s variables o u objets n ' i m p o r t e où d a n s le c o d e , y c o m p r i s a p r è s u n e série d'instructions. La p o r t é e de telles variables va de l'endroit de leur déclaration à la fin du bloc c o u r a n t ( l ' a c c o l a d e f e r m a n t e en général). void main() { int i; for (i=0; i<100; i++) { int j ; // traitement } // ici j est inconnu } V o u s p o u v e z déclarer d a n s u n sous-bloc u n e variable portant l e m ê m e n o m q u ' u n e autre variable d é c l a r é e d a n s u n bloc supérieur. D a n s c e c a s , l a variable l a p l u s p r o c h e d e l'endroit de l'instruction est utilisée. E x e m p l e : void main() { int i; for (i=0; i<100; i++) { int i ; i = 1;
  • 61.
    74 Pont entre Cet C++ } // ici i vaut 100 et pas 1 } Variable de boucle Voici u n e construction intéressante : v o u s p o u v e z déclarer u n e variable de b o u c l e d i r e c t e m e n t dans l'instruction f or ( , , ), ce qui v o u s garantit q u e p e r s o n n e n'utilisera cette variable en d e h o r s de la b o u c l e : void main() { for (int i = 0; i < 100; i++) { // traitement } )
  • 62.
    Résumé La découverte duC++ est un peu déroutante, tant les termes et concepts inédits sont nombreux. C'est pourquoi nous vous proposons ici un résumé rapide de la première partie du livre. Il vous permettra d'avoir les idées claires avant d'aborder la suite, riche en rebondissements...
  • 63.
    Résumé première partie Encapsulation 77 Lep r i n c i p e fondateur de la p r o g r a m m a t i o n orientée-objet est l'encapsulation. Au lieu de r é s o u d r e un p r o b l è m e p a r d e s structures e t des fonctions s a n s lien explicite ( c o m m e e n C ) , l ' e n c a p s u l a t i o n p r o p o s e d'utiliser des objets, entités inform a t i q u e s r e g r o u p a n t d o n n é e s e t fonctions i n t i m e m e n t liées. L e s d o n n é e s d ' u n objet n e p e u v e n t être m a n i p u l é e s q u e p a r les fonctions créées s p é c i a l e m e n t p o u r cela. E n C + + , u n objet est u n e i n s t a n c e de classe, q u e l'utilisateur p e u t définir c o m m e il le veut. L e s fonctions qui m a n i p u l e n t les d o n n é e s d ' u n objet s o n t appelées fonctions-membres. De m u l t i p l e s objets p e u v e n t être créés à partir de la m ê m e classe, c h a c u n c o n t e n a n t des v a l e u r s différentes. L e s objets sont issus d ' u n e classe. class NomClasse { private: // variables et objets public : // fonctions de manipulation de ces variables / / e t objets } ; void main() { NomClasse NomClasse objetl; objet2; objetl.nom_de_fonction_public(); } Constructeurs & destructeurs * qu'il sort simplement déclaré stàtiquement ou alloué dynamiquement par l'opérateur new. C h a q u e classe doit p o s s é d e r au m o i n s u n e fonction constructeur et u n e fonction destructeur. Si le p r o g r a m m e u r n ' e n définit pas, le c o m p i l a t e u r en g é n è r e p a r défaut. Le c o n s t r u c t e u r est appelé à c h a q u e création d'objet*, et p e r m e t a u p r o g r a m m e u r d'initialiser c o r r e c t e m e n t l'objet. L e destructeur est a p p e l é dès q u ' u n objet sort de sa p r o p r e p o r tée o u est d é s a l l o u é e x p l i c i t e m e n t p a r u n a p p e l à d e l e t e .
  • 64.
    78 Pont entre Cet C++ Héritage L'héritage p e r m e t de mettre en relation d e s classes. Q u a n d u n e classe B hérite d ' u n e classe A, A est a p p e l é e classe de b a s e et B classe dérivée. Au n i v e a u s é m a n t i q u e , B « est u n e sorte » de A (voir e x e m ple p a g e suivante). Au n i v e a u du C + + , la classe B « recopie » (en q u e l q u e sorte) les d o n n é e s et fonctions de la classe A. Un objet de classe B intègre donc les d o n n é e s et fonctions accessibles de A. La classe dérivée B p e u t redéfinir des fonctions de la classe de b a s e A. On parle alors de redéfinition de fonction membre, ce qui signifie q u ' u n m ê m e entête de fonction est partagé p a r toute u n e hiérarchie de classes.
  • 65.
    Résumé première partie Surcharge 79 Lasurcharge de fonction p e r m e t au p r o g r a m m e u r de d o n n e r l e m ê m e n o m à d e s fonctions situées d a n s l a m ê m e c l a s s e , o u e n d e h o r s d e toute classe. L a seule c o n t r a i n t e est q u e l e c o m p i l a t e u r p u i s s e faire la distinction g r â c e au n o m b r e ou au t y p e des p a r a m è t r e s de ces fonctions « s u r c h a r g é e s ». int carre(int a) { return (a * a) ; } float carre(float a) { return (a * a ) ; }
  • 66.
    Encore plus de C++ Vous connaissez maintenantles grands principes du C++. Mais votre voyage au pays des objets n'est pas fini : il vous reste à découvrir d'autres concepts qui enrichissent encore plus le langage. Une bonne connaissance des éléments présentés dans la première partie facilitera votre lecture.
  • 67.
    La fin dumalloc new et delete Nous sommes au regret de vous annoncer le décès de malloc, survenu après de nombreuses années de bons et loyaux services... En réalité, bien que vous puissiez toujours utiliser malloc, calloc et free, le C++ met à votre disposition new et delete, leurs talentueux remplaçants. Notions de base L'idée L e C + + propose deux nouveaux mots-clés, new e t d e l e t e , p o u r r e m p l a c e r leurs aînés m a l l o c e t f r e e . P o u r q u o i u n tel c h a n g e m e n t ? P o u r s'adapter a u x n o u v e l l e s caractéristiques d u C + + q u e sont l'héritage, les c o n s t r u c t e u r s e t les destructeurs. E n effet, alors q u e m a l l o c e t f r e e s o n t d e u x fonctions d e l a b i b l i o t h è q u e standard d u C , n e w e t d e l e t e s o n t c o m p l è t e m e n t intégrés au l a n g a g e C + + . Ce sont à la fois d e s m o t s - c l é s (réservés) et d e s opérateurs. Le rôle de n e w est d o u b l e : il r é s e r v e l ' e s p a c e m é m o i r e q u ' o n lui d e m a n d e , p u i s l'initialise en a p p e l l a n t les c o n s t r u c t e u r s des objets créés.
  • 68.
    84 Pont entre Cet C++ L e rôle d e d e l e t e est s y m é t r i q u e : i l a p p e l l e les d e s t r u c t e u r s des objets créés p a r new, p u i s libère l ' e s p a c e m é m o i r e réservé. Mise en œuvre C++ Dans les cas cicontre, la variable pt désigne toujours un pointeur de T (déclaré avec T *pt). L'opérateur new C o m m e m a l l o c , l'opérateur n e w r e t o u r n e soit l ' a d r e s s e m é m o i r e n o u v e l l e m e n t allouée, soit 0 (zéro) si l'opération a échoué. On p e u t utiliser n e w de trois m a n i è r e s différentes : pt = new T; L'opérateur new recherche un espace mémoire pour un seul é l é m e n t de type T. Ici, T p e u t d é s i g n e r un t y p e standard ( c h a r , i n t , etc.) o u u n e classe. D a n s c e dernier c a s , n e w appellera le c o n s t r u c t e u r par défaut de la c l a s s e T. pt = new T {arguments d'un constructeur de classe T) -, Ici, au lieu d ' i n v o q u e r le c o n s t r u c t e u r p a r défaut de la classe T, n e w appelera le c o n s t r u c t e u r c o r r e s p o n d a n t a u x paramètres indiqués. pt = new T [taille du tableau] ; C e t t e s y n t a x e d e m a n d e à n e w de c h e r c h e r un e s p a c e m é m o i r e p o u v a n t contenir t a i 1 2 e _ t a b l e a u é l é m e n t s consécutifs de type T (T p o u v a n t être un t y p e s t a n d a r d ou u n e classe). Le c o n s t r u c t e u r p a r défaut de la c l a s s e T est invoqué pour chaque élément. A t t e n t i o n ! Détruire un objet individuel avec l'instruction delete [ ] p r o v o q u e quelque chose d'indéfini mais de généralement nuisible (loi de Murphy). De même, utiliser delete p o u r détruire un tableau entraine des conséquences t o u t aussi aléatoires ! L'opérateur delete I l est fortement r e c o m m a n d é d ' a p p e l e r d e l e t e p o u r libérer l ' e s p a c e - m é m o i r e alloué p a r new. L a s y n t a x e d e d e l e t e est simple : d e l e t e pt; A p p e l l e le destructeur de pt (si pt est un p o i n t e u r v e r s u n objet) p u i s libère l ' e s p a c e m é m o i r e p r é a l a b l e m e n t alloué p a r u n new. d e l e t e [ ] pt; M ê m e c h o s e , m a i s p o u r libérer u n tableau alloué a v e c new T [ taille tableau ].
  • 69.
    La fin dumalloc 85 L e s spécifications officielles d u C + + v o u s g a r a n t i s s e n t q u e rien d e m a u v a i s n'arrivera s i v o u s a p p e l e z d e l e t e s u r u n p o i n t e u r nul. Exemple R e g a r d o n s m a i n t e n a n t c o m m e n t utiliser n e w e t d e l e t e p o u r allouer et libérer des c h a î n e s de caractères. P r e n o n s c o m m e e x e m p l e la classe CDRom, définie ci-dessous : class CDRom { protected : char * titre; public : CDRom(); CDRom(char *tit); -CDRom(); } ; // définition des constructeurs CDRom: :CDRom() { titre = new char[8]; if (titre == 0) badaboum(); strcpy(titre, "<Aucun>"); } CDRom::CDRom(char *tit) { titre = new char[ strlen(tit) + 1 ]; if (titre == 0) badaboum(); strcpy(titre, tit); } // définition du destructeur CDRom::-CDRom() { delete [] titre; } Vous constarerez que nous avons bien respecté la symétrie n e w / d e l e t e . L a fonction b a d a b o u m s'occupe d e traiter les erreurs, l o r s q u e n e w n ' e s t p a s p a r v e n u à allouer la m é m o i r e demandée. E x a m i n o n s m a i n t e n a n t c o m m e n t utiliser n e w e t d e l e t e a v e c d e s objets. P u i s q u e n o u s l ' a v o n s s o u s l a m a i n , g a r d o n s la classe CDRom :
  • 70.
    86 Pont entre Cet C++ void main() { CDRom CDRom *pt_inconnu, *pt_cd_X; rebel_assault("Rebel Assault"); pt_inconnu = new CDRom; pt_cd_X = new CDRom("Virtual Escort"); // blabla delete pt_inconnu; delete pt_cd_X; } // (1) // (2) // (3) // // // // // // (4) (5) (6) (7) (8) (9) L e s instructions (1) et (2) n ' a p p e l l e n t a u c u n c o n s t r u c t e u r , et ne r é s e r v e n t de la p l a c e q u e p o u r les p o i n t e u r s e u x - m ê m e s . La ligne (3) appelle le d e u x i è m e c o n s t r u c t e u r de la c l a s s e CDRom, et initialise l'objet rebel_assault. R i e n de n e u f ici. En r e v a n c h e , l'instruction (4) va trouver de la p l a c e en m é m o i r e p o u r u n objet de classe CDRom, i n i t i a l i s e p a r le p r e m i e r c o n s t r u c t e u r (le constructeur p a r défaut, p u i s q u ' i l ne p r e n d aucun paramètre). Vous pouvez maintenant utiliser pt_inconnu p o u r appeler d e s fonctions p u b l i q u e s de la classe CDRom. L'instruction (5) va créer un objet initialise p a r le d e u x i è m e constructeur, et r e t o u r n e r l ' a d r e s s e de cet objet. Enfin, les lignes (7) et (8) appellent le destructeur de la c l a s s e CDRom p o u r détruire les objets p o i n t é s p a r pt_inconnu et pt_cd_X. C e s destructeurs v o n t libérer les c h a î n e s de caractères allouées. L'intérêt par rapport au C * C'est une possibilité réservée aux experts... C o m p a r o n s les d e u x lignes s u i v a n t e s : tab = (char *) malloc (TAILLE * sizeof(char)); tab = new char [TAILLE] ; // C // C + + , L'intérêt d u n e w devrait v o u s frapper d e plein fouet. C ' e s t vrai : n o n s e u l e m e n t n e w est p l u s s i m p l e à utiliser, m a i s en plus, il fait partie i n t é g r a n t e du C + + , d o n c il est p o r t a b l e et n e n é c e s s i t e p a s l'adjonction d ' u n e librairie o u l'inclusion d ' u n fichier d'entête. Par ailleurs, l'utilisation du n e w p e r m e t la vérification de type (ce q u e ne p e r m e t p a s malloc), d o n c u n e p l u s g r a n d e sûreté d u c o d e . E n outre, n e w a p p e l l e l e c o n s t r u c t e u r v o u l u , c'est un opérateur, il p e u t être redéfini p a r v o s soins* et il peut être hérité. Q u e v o u l e z - v o u s de p l u s ?
  • 71.
    La fin dumalloc Que celui qui n'a pas pesté la première fois qu'il a découvert le profil de malloc se lève. 87 C e r t e s , v o u s p o u v e z toujours utiliser m a l l o c e n C + + . M a i s d a n s c e cas, v o u s d e v r e z appeler v o u s - m ê m e les c o n s t r u c teurs v o u l u s , et, ce qui est n e t t e m e n t p l u s f â c h e u x , v o u s risq u e r e z de p a s s e r au travers de n e w spécifiques à certaines c l a s s e s . Je m ' e x p l i q u e : si un gentil p r o g r a m m e u r réécrit un joli n e w p o u r s a classe e t q u e v o u s i g n o r e z s u p e r b e m e n t ses efforts e n utilisant m a l l o c , n o n s e u l e m e n t v o t r e objet risq u e r a d'être m a l initialise, m a i s , p l u s g r a v e , v o u s v o u s attirerez i n é v i t a b l e m e n t son inimitié. Un conseil : m e t t e z - v o u s au new, v o u s ne le r e g r e t t e r e z p a s .
  • 72.
    La fin duprintf cout, cin et cerr Nous venons famille printf. nération : la jets issus de réutilisabilité. d'enterrer la famille malloc, passons maintenant à Et donnons maintenant sa chance à la nouvelle famille cout. Notons que cout, cin et cerr sont des classes C++, et qu'ils illustrent très bien le concept la géobde Notions de base L e C + + intègre d a n s s a librairie s t a n d a r d différents m é c a n i s m e s d ' e n t r é e s e t d e sorties. I l s'agit d e s flux c o u t , c i n e t c e r r , c o u p l é s a u x o p é r a t e u r s > > e t < < . L ' o p é r a t e u r > > perm e t de lire un flux, alors q u e « v o u s a u t o r i s e à écrire dedans. V o i c i le rôle des trois flux s t a n d a r d s du C + + * :
  • 73.
    90 Pont entre Cet C++ t r e a m , e t c i n u n objet d e classe i s t r e a m . T o u t c e b e a u m o n d e est défini d a n s l e fichier i o s t r e a m . h . N o u s allons voir c o m m e n t utiliser les flux C + + e t p o u r q u o i ils s o n t p l u s intéressants q u e c e u x d e C . Mise en œuvre C++ P r e n o n s un e x e m p l e s i m p l e : n o u s allons écrire un petit p r o g r a m m e qui calcule l a m o y e n n e d e s n o t e s d ' u n étudiant. #include "iostream.h" void { char float float int maint) nom[50]; notes[3]; cumul = 0 ; i; cout << "Entrez le nom de l'étudiant : cin >> nom; for (i=0; i<3; i++) { cout << "Entrez la note n°" << i+1 << ' '; // (1) cin >> notes[ i ] ; cumul += notes[i]; N o t e z que, t o u t } c o m m e p o u r scanf, vous ne pouvez pas cout << nom << " a pour moyenne " << cumul/3. << endl; } entrer d'espace(s) pendant la saisie d'une chaîne. En fait, p o u r être exact, vous pouvez le faire, mais vous rendrez v o t r e programme complètement fou, et / / C e programme affiche ceci : // (les caractères gras sont tapés par l'utilisateur) Entrez le Entrez la Entrez la Entrez la GaryMad a nom de l'étudiant : GaryMad note n°l 0.5 note n°2 7 note n°3 19.5 pour moyenne 9 vous avec. Remarque V o u s p o u v e z afficher p l u s i e u r s variables o u c o n s t a n t e s d a n s l a m ê m e instruction, d u m o m e n t q u e v o u s les s é p a r e z p a r < < . C ' e s t l e cas e n ( 1 ) , d a n s l a p r e m i è r e instruction d e l a b o u c l e , o ù n o u s affichons u n e c h a î n e , p u i s l'entier i , p u i s l e caractère e s p a c e . C e t t e r e m a r q u e est é g a l e m e n t v a l a b l e p o u r les lectures a v e c c i n e t > > .
  • 74.
    La fin duprintf 91 M i s e en forme automatique V o u s n o t e r e z q u e v o u s n ' a v e z pas b e s o i n d e préciser l e form a t d'affichage, celui étant défini p a r défaut p o u r les types d e b a s e d e C . Si, n é a n m o i n s , v o u s s o u h a i t e z a p p l i q u e r d e s contraintes sur l'affichage ( n o m b r e de chiffres d é c i m a u x d ' u n réel par e x e m p l e ) , inutile de revenir à printf, cout sait le faire très bien. R e p o r t e z v o u s a u x c o m p l é m e n t s de ce chapitre p o u r savoir c o m m e n t . Sans adresse V o u s a v e z noté l'abscence de & d a n s la s y n t a x e du cin. Ce dernier n ' a p a s b e s o i n de connaître l'adresse de la variable à lire, c o m m e c'était l e cas p o u r s c a n f . L'intérêt par rapport au C L'intérêt principal de cout, cin, cerr, << et >> est la vérification de type. En effet, c h a q u e objet est affiché en fonction de sa classe ; il n ' y a p a s de r i s q u e de confusion ou de quip r o q u o . L e s fonctions printf et c o m p a g n i e s o n t i n c a p a b l e s de vérifier le type des d o n n é e s q u ' e l l e s traitent (ce qui accroît les r i s q u e s d'erreurs). P l u s j a m a i s les c h a î n e s de c a r a c t è r e s ne seront affichées c o m m e d e v u l g a i r e s entiers, j e v o u s l e p r o m e t s ... Par ailleurs, u n e s i m p l e c o m p a r a i s o n de lignes suffit p o u r q u e la vérité a p p a r a i s s e de m a n i è r e éclatante à l ' h o n n ê t e h o m m e : cout et ses frères sont r e m a r q u a b l e m e n t p l u s s i m p l e s à utiliser q u e leurs a i n e s d e la famille printf. // C standard (infâme, n'est-ce pas ?) printf("%s%d%c%fn", "Voici i:", entier, '+', réel); scanf("%f", &reel); // C++ (mieux, tout de même !) cout << "Voici i:" << entier << '+' << réel << endl; cin >> réel; M a i s l'intérêt ne se limite p a s là : v o u s p o u v e z surtout redéfinir les opérateur << et » p o u r c h a c u n e de v o s c l a s s e s ! A i n s i , les o p é r a t i o n s d ' e n t r é e s / s o r t i e s g a r d e r o n t toujours la m ê m e s y n t a x e , i n d é p e n d a m m e n t d e s classes utilisées ! Par e x e m p l e , u n e classe FilmHorreur p o u r r a afficher le n o m du film et s o n réalisateur, opération réalisée de la m ê m e m a n i è r e q u e p o u r un entier :
  • 75.
    92 Pont entre Cet C++ Utilisateurs de MSD O S , prenez garde ! Le n o m de fichier #include "FilmHorreur.h" void // voir dans la marge main() { utilisé dans l'include FilmHorreur fait plus de huit caractères ! film("Doume", "Aïdi software"); cout << film; } On s u p p o s e b i e n e n t e n d u q u e la classe est définie, et que « l'on a fait ce qu'il faut » p o u r q u e l ' o p é r a t e u r « prenne en c o m p t e l'affichage de la classe F i l m H o r r e u r . V o u s allez a p p r e n d r e c o m m e n t réaliser ce petit m i r a c l e d a n s les comp l é m e n t s d e c e chapitre. Compléments Surcharger « et » Avant de lire ce complément, vous devez connaître les fonctions amies (page 123), ainsi que les principes de la surcharge, développés page 5 1 . V o u s p o u v e z s u r c h a r g e r les o p é r a t e u r s < < e t > > p o u r affic h e r v o s p r o p r e s objets. En d'autres t e r m e s , il faut que les fonctions operator<< e t o p e r a t o r » s a c h e n t quoi faire l o r s q u ' e l l e s sont a p p l i q u é e s à des objets de v o t r e classe. A v a n t de p o u r s u i v r e les e x p l i c a t i o n s , un petit rappel s ' i m p o s e : q u a n d on utilise un o p é r a t e u r @, cela revient à app e l e r la fonction operator® c o r r e s p o n d a n t e . V o i l à ce que cela d o n n e p o u r + : int résultat, a = 1, b = 1; résultat = a + b; // équivaut à résultat = operatort (a, b ) ; T r a n s p o s o n s l ' e x e m p l e ci-dessus à « : NomClasse mon_objet; cout << mon_objet; // équivaut à operator<< (cout, mon__objet); Il s'agit ici de l'opérateur << global. R a p p e l o n s q u e cout et cin sont r e s p e c t i v e m e n t d e s objets de classe ostream et
  • 76.
    La fin duprintf 93 istream. P o u r s u r c h a r g e r « , i l suffit d o n c d'écrire l a fonction hors-classe suivante : ostream operator<< (ostream, NomClasse) La fonction à s u r c h a r g e r est identifiée. Il ne reste p l u s q u ' à l'écrire. Un p r o b l è m e se p o s e alors : c o m m e n t a c c é d e r a u x d o n n é e s de l'objet de classe NomClasse ? D e u x solutions : p a s s e r p a r les fonctions m e m b r e s public d ' a c c è s a u x d o n n é e s , ou b i e n déclarer d a n s la classe NomClasse q u e la fonction operator<< est u n e amie. D a n s ce dernier c a s , o p e r a t o r « p e u t a c c é d e r l i b r e m e n t a u x d o n n é e s private e t protected des objets de NomClasse. Ce qui d o n n e : Vous trouverez page 176 un exemple de surcharge de << qui ne déclare pas de fonction amie. #include <iostream.h> class NomClasse { private: int n; public : NomClasse(int i) : n(i) {} int get_n() const { return n; } // déclarons que l'opérateur global << est // un ami de notre classe (NomClasse) : friend ostreamS operator<< (ostream& out, const NomClasse& obj); }; Nous utilisons ici des passages par // surchargeons l'opérateur global << pour qu'il // sache traiter des objets de classe NomClasse : ostream& operator<< (ostreamk out, const NomClasse& obj) { référence (voir // out est l'objet comparable à cout // obj est l'objet à afficher return out << '[' << obj.n << ' ] ' << endl; chapitre 9, page I 13). } void { maint) NomClasse objet(1); cout << objet; } /* Pour redéfinir >>, utilisez : istream& operator>> (istreamk in, NomClasse& obj);
  • 77.
    94 Pont entre Cet C++ */ // affichage: // [1] L a fonction o p e r a t o r < < r e t o u r n e u n e référence sur u n objet d e classe o s t r e a m , p o u r q u e v o u s p u i s s i e z utiliser d e s instructions à la file : cout << objl << obj2; // équivaut à : // operator<< (operator<<( cout, objl), obj2); D a n s l a ligne ci-dessus, o p e r a t o r < < ( c o u t , o b j l ) doit être l u i - m ê m e l'objet d e classe o s t r e a m qui v i e n t d'être appelé ; c'est p o u r cela q u e n o u s r e t o u r n o n s u n e référence v e r s cet objet. Formater les sorties Ces formatages o n t été vérifiés sur le compilateur T u r b o C + + p o u r PC, mais devraient fonct i o n n e r sur tous les autres compilateurs. * sauf contre indicat i o n dans le texte. L e s indications de formatage (présentation) d e s sorties doiv e n t être p a s s é e s à c o u t d e l a m ê m e m a n i è r e q u e les d o n n é e s à afficher, à savoir avec l'opérateur < < . Q u a n d v o u s utilisez u n e instruction d e f o r m a t a g e , v o u s dev e z inclure au d é b u t de votre c o d e le fichier i o m a n i p . h en plus d u fichier i o s t r e a m . h . L e s p a r a g r a p h e s qui suivent d o n n e n t des e x e m p l e s p o u r c h a q u e type d e f o r m a t a g e différent. V o u s t r o u v e r e z e n s u i t e un tableau récapitulatif de toutes ces fonctions. Attention : toutes ces instructions de formatage* restent v a lables p o u r tous les c o u t qui les suivent d a n s l e p r o g r a m m e . A f f i c h e r au m o i n s n caractères L'identificateur s e t w ( n ) force c o u t à afficher a u m o i n s n caractères, et plus si nécessaire, s e t w est l'abbréviation de set zuidth (« spécifie la largeur »). Par défaut les affichages s o n t c a d r é s à droite. E x e m p l e : #include "iostream.h" #include "iomanip.h" void main() { cout << setw(3) << 1 << endl; cout << 1.2345 << endl; }
  • 78.
    La fin duprintf 95 // affiche : 1 1.2345 P o u r q u e c o u t affiche autant d e caractères q u e n é c e s s a i r e , spécifiez u n e l o n g u e u r m i n i m a l e d e 0 ( s e t w ( 0 ) ) . C ' e s t d'ailleurs la valeur par défaut. A l i g n e r les sorties à g a u c h e Utilisez ici l e barbare s e t i o s f l a g s ( i o s : : l e f t ) . E x e m ple : #include "iostream.h" finclude "iomanip.h" void main() { cout << setw(5) << "Wax" << endl; //(l) cout << setiosflags(ios::left) << "Wax" << endl; 11(2) II setw(5) est toujours valable ici ! } // affiche : Wax Wax La l i g n e (1) i n d i q u e qu'il faut afficher au m o i n s cinq caractères. Par défaut, l ' a l i g n e m e n t se fait à droite, d ' o ù la p r e m i è r e ligne affichée (deux e s p a c e s et les trois lettres). La ligne (2) p r é c i s e qu'il faut aligner le texte à g a u c h e . R a p p e l e z - v o u s q u e la c o n s i g n e d'afficher au m o i n s cinq caractères est toujours valable. Cette fois-ci, les trois lettres de « W a x » sont affichées, suivies de d e u x e s p a c e s . A l i g n e r les s o r t i e s à droite C ' e s t l e c o m p o r t e m e n t p a r défaut d e c o u t . S i v o u s v o u l e z l e rétablir, utilisez s e t i o s f l a g s ( i o s : : r i g h t ) — v o i r p a r a g r a p h e précédent. A f f i c h e r n chiffres après la v i r g u l e Utilisez s e t p r e c i s i o n ( n ) : #include "iostream.h" #include "iomanip.h" void main()
  • 79.
    96 Pont entre Cet C++ { cout << setprecision(2) << 1.23456 << endl; } // affiche : 1.23 Afficher les zéros après la virgule Utilisez s e t i o s f l a g s ( i o s : : s h o w p o i n t ) . #include "iostream.h" #include "iomanip.h" void main() { cout << setw(5) << setiosflags(ios::showpoint) << 1.2 << endl; } // affiche : 1.200 Ne pas afficher les zéros après la virgule C ' e s t l a fonction r é c i p r o q u e d e l a p r é c é d e n t e . Utilisez s e tiosf lags(ios::showbase) : #include "iostream.h" #include "iomanip.h" void main() { cout << setw(5) << setiosflags(ios::showpoint) << 1.2 << endl; cout << setiosflags(ios::showbase) << 1.2 << endl; } // affiche : 1.200 1.2 Afficher u n ' + ' p o u r les n o m b r e s positifs Utilisez s e t i o s f l a g s ( i o s : : s h o w p o s ) : #include "iostream.h" #include "iomanip.h" void { maint)
  • 80.
    La fin duprintf 97 cout << setiosflags(ios::showpos) << 1.2 << endl; } // affiche : + 1.2 R e m p l a c e r le caractère blanc par un autre caractère N o u s p a r l o n s ici d e s e s p a c e s affichés p o u r c o m b l e r les v i d e s . P a r e x e m p l e , si v o u s a v e z spécifié s e t w ( 5 ) et q u e v o u s affic h e z le chiffre 1, quatre e s p a c e s seront affichés en p l u s . Si v o u s n ' a i m e z p a s les e s p a c e s , utilisez s e t f i l l ( c a r ) p o u r les r e m p l a c e r p a r un caractère de votre c h o i x : #include "iostream.h" #include "iomanip.h" void main() { cout << setfill('-') << setw(5) << 1 << endl; } // affiche : 1 A f f i c h e r les n o m b r e s d a n s u n e autre b a s e S o n t définies trois b a s e s : l'octal (base 8 ) , le d é c i m a l (par défaut) et l ' h e x a d é c i m a l (base 16). il suffit de spécifier l'abréviation ( o c t , d e c , h e x ) p o u r q u e tous les affichages suivants se fassent d a n s cette b a s e : #include "iostream.h" #include "iomanip.h" void main() { cout << hex << 12 << endl; } // affiche : c A f f i c h e r les n o m b r e s e n n o t a t i o n s c i e n t i f i q u e Un rappel p o u r les n o n - m a t h e u x s ' i m p o s e : la n o t a t i o n scientifique est de la forme n Ee, où n est un n o m b r e réel entre 0 et 1 n o n inclus, et où e est é g a l e m e n t un n o m b r e réel.
  • 81.
    98 Pont entre Cet C++ U n e telle n o t a t i o n r e p r é s e n t e le n o m b r e n m u l t i p l i é p a r 10 p u i s s a n c e e. P o u r formater l'affichage en n o t a t i o n scientifiq u e , utilisez s e t i o s f l a g s ( i o s : : scientific). E x e m ple : #include "iostream.h" #include "iomanip.h" void main() { cout << setiosflags(ios::scientific) << 12.3 << endl; } // affiche : .123 e2 A f f i c h e r l e s n o m b r e s e n n o t a t i o n fixe C ' e s t l'affichage p a r défaut d e s n o m b r e s , il s'obtient en utilisant s e t i o s f l a g s ( i o s : : fixed). E x e m p l e : #include "iostream.h" #include "iomanip.h" void main() { cout << setiosflags(ios::scientific) << 12.3 << endl; cout << setiosflags(ios::fixed) << 12.3 << endl; } // affiche : .123 e2 12 .3
  • 82.
    La fin duprintf Tableau récapitulatif 99 Ce tableau r e p r e n d les différentes options d'affichage valables pour c o u t . Effet voulu cout « ... au moins n caractères setw(n) aligner à gauche setiosflags(ios::left) aligner à droite setiosflags(ios::right) n caractères après la virgule setprecision(n) afficher les 0 après la virgule setiosf lags(ios: :showpoint) ne pas afficher les 0 après la virgule setiosflags(ios::showbase) '+' devant les nombres positifs setiosf lags(ios: :showpos) changer le caractère de remplissage setfill(c); // c est un char afficher en décimal, octal ou hexadécimal dec, oct ou hex notation scientifique setiosf lags(ios::scientific) notation à virgule fixe setiosflags(ios::fixed)
  • 83.
    Le polymorphisme et lavirtualité Vous connaissez déjà les vertus de l'héritage. Découvrez maintenant celles du polymorphisme, mis en œuvre par la virtualité en C++. Si vos notions sur l'héritage sont encore un peu floues, vous gagnerez à relire le chapitre 3 avant d'aborder celui-ci. Notions de base L'idée Le p r i n c i p e de p o l y m o r p h i s m e p e u t s ' a p p a r e n t e r à u n e surc h a r g e e t u n e redéfinition d e f o n c t i o n - m e m b r e é t e n d u e . R a p p e l o n s q u e la surcharge consiste à p o u v o i r d o n n e r le m ê m e n o m à p l u s i e u r s fonctions ; le c o m p i l a t e u r faisant la différence grâce aux p a r a m è t r e s . La redéfinition de fonctionmembre a p p l i q u e ce m ê m e p r i n c i p e à travers u n e arboresc e n c e d'héritage, e t p e r m e t e n p l u s d e d o n n e r e x a c t e m e n t l e m ê m e entête d e fonction. Le p o l y m o r p h i s m e va p l u s loin : il p e r m e t de trouver dynamiquement à quel objet s ' a d r e s s e u n e fonction, p e n d a n t l ' e x é c u t i o n d u p r o g r a m m e . A l o r s q u e l a redéfinition d e f o n c t i o n - m e m b r e d é t e r m i n a i t cela à la c o m p i l a t i o n , c'est-àdire de m a n i è r e statique, le p o l y m o r p h i s m e m è n e s o n en-
  • 84.
    202 Pont entre Cet C++ quête et va jusqu'à retrouver la classe réelle d'un objet pointé. Limites de la redéfinition de fonction-membre Reprenons l'exemple d'une gestion de textes bruts et mis en forme. Si l'on utilise un pointeur de T e x t e B r u t qui pointe réellement vers un T e x t e M i s E n F o r m e , la redéfinition de fonction-membre seule montre ses limites : #include <iostream.h> class TexteBrut { | public: void j Imprimer(int n b ) ; } ; void TexteBrut::Imprimer(int nb) { cout << "Imprimer de la classe TexteBrut" << endl; ; } class TexteMisEnForme : public TexteBrut // héritage { ! I public: void ! Imprimer(int n b ) ; } ; void TexteMisEnForme::Imprimer(int nb) { cout << "Imprimer de la classe TexteMisEnForme" < < endl; i } | void main() { TexteBrut TexteMisEnForme i i } i *txt; joli_t;xt; txt = &joli_txt; txt -> Imprimer(1); // affichage : // Imprimer de la classe TexteBrut j i i Ce n'est pas le résultat espéré : il faudrait que la fonction Imp r i m e r définie dans la classe T e x t e M i s E n F o r m e soit appelée, car l'objet réellement pointé appartient à cette classe. Le polymorphisme permet justement de résoudre ce problème.
  • 85.
    Le polymorphisme etla virtualité Mise en œuvre C++ 103 L e p o l y m o r p h i s m e est m i s e n œ u v r e p a r l a virtualité e n C + + . R e p r e n o n s l ' e x e m p l e p r é c é d e n t . Il suffit d'ajouter le m o t - c l é virtual d e v a n t l'entête de la fonction Imprimer, d a n s la classe TexteBrut. Ce qui d o n n e : #include <iostream.h> class TexteBrut { public : virtual void Imprimer(int n b ) ; } ; // ... le reste est identique à l'exemple précédent ... void main() { TexteBrut TexteMisEnForme *txt; joli_txt; txt = &joli_txt; // txt pointe sur un TexteMisEnForme txt -> Imprimer(1); } // affichage : // Imprimer de la classe TexteMisEnForme E x a m i n o n s le rôle du mot-clé virtual d a n s n o t r e petite architecture de classes. Le rôle d'une fonction virtuelle Q u a n d l e c o m p i l a t e u r r e n c o n t r e d e s f o n c t i o n s - m e m b r e s virtuelles, il sait qu'il faut attendre l ' e x é c u t i o n du p r o g r a m m e p o u r savoir quelle fonction appeler. Il d é t e r m i n e r a en t e m p s v o u l u la bonne fonction, c'est-à-dire celle qui est définie d a n s la classe de l'objet réel a u q u e l la fonction est a p p l i q u é e . M a i s r e v e n o n s à n o t r e e x e m p l e p o u r illustrer c e s p r o p o s : le petit b o u t de m a i n ( ) a b e a u être petit, il n ' e n est p a s m o i n s très riche en e n s e i g n e m e n t s . L'utilisation d ' u n p o i n t e u r sur classe TexteBrut m é r i t e q u e l q u e s é c l a i r c i s s e m e n t s .
  • 86.
    204 Pont entre Cet C++ P o i n t e u r s sur u n e c l a s s e d e b a s e * O u i oui, en C c'est possible, car le C est un grand laxiste. Mais en C + + , le contrôle des types est plus serré, et vos incartades ne passeront plus : finies les convertions automatiques hasardeuses de truc_machin vers bidule_chose. De la discipline, que diable ! Résumé * C'est-à-dire m ê m e n o m b r e et mêmes types d'arguments. * Pour la clarté du listing, on pourra répéter le mot-clé virtual devant chaque fonction concernée, t o u t au long de la hiérarchie. L'intérêt par rapport au C En temps normal, quand vous déclarez un pointeur de type A, vous ne pouvez pas le f i e pointer sur une donnée de ar type B*. En tous cas, cela devrait être contraire à votre éthique. M a i s i i nous sommes dans une situation spéciale : c, nous déclarons un pointeur sur une classe de base. D a n s ce cas précis, et le C + + nous y autorise, vous pouvez faire point r ce pointeur vers une classe dérivée de cette classe de base. e C'est exactement ce que nous faisons dans l'avant-dernière ligne du l s i g itn. La ligne qui suit (txt -> Imprimer ( 1 ) ) devrait donc f i e ar appel à la fonction Imprimer définie dans la classe TexteBrut, puisque txt est un pointeur sur TexteBrut. M a i s NON, puisque le C + + aura vu que txt pointe maintenant sur joli_txt, objet de classe TexteMisEnForme. C e t t e ligne appelera donc la fonction Imprimer de la classe TexteMisEnForme. D a n s u n e hiérarchie de classe, p o u r déclarer des fonctions qui o n t l e m ê m e n o m e t les m ê m e s a r g u m e n t s * e t d o n t les appels sont résolus d y n a m i q u e m e n t , il faut qu'elles soient virtuelles. Il suffit de déclarer u n e fonction virtuelle d a n s la classe de b a s e p o u r q u e toutes les fonctions i d e n t i q u e s des classes dérivées soient elles aussi virtuelles*. P e n d a n t l'exécution, la fonction appelée d é p e n d de la classe de l'objet qui l'appelle (et n o n du type de variable qui pointe sur l'objet). D a n s le cas d'un pointeur sur u n e classe qui appelle u n e fonction virtuelle, c'est la classe de l'objet pointé réellement par ce pointeur qui d é t e r m i n e la fonction a p p e l é e . C e m é c a n i s m e c o m m u n à d e n o m b r e u x l a n g a g e s orientésobjets est appelé polymorphisme. La virtualité est sa m i s e en œuvre C + + . N o u s avions déjà v u q u ' a v e c l'héritage, l e C + + proposait q u e l q u e c h o s e de très intéressant, totalement a b s e n t du C. G r â c e a u p o l y m o r p h i s m e , v o u s d i s p o s e z d ' u n m o y e n nouv e a u , d ' u n e s o u p l e s s e et d ' u n e p u i s s a n c e é t o n n a n t e : utiliser des fonctions h o m o n y m e s , d o n t les a r g u m e n t s ont l e m ê m e profil, tout au l o n g d ' u n e hiérarchie de classe, en bénéficiant de la r é s o l u t i o n d y n a m i q u e des appels.
  • 87.
    Le polymorphisme etla virtualité Rions ensemble. Un utilisateur de Macintosh déclarait récemment : « Windows p o u r PC, c'est mettre du rouge 105 E x e m p l e : v o u s d é v e l o p p e z un e n v i r o n n e m e n t g r a p h i q u e . V o u s le baptisez Fenêtres pour Groupes de Travail 96*. G r â c e à la virtualité, c h a q u e objet g r a p h i q u e de v o t r e s y s t è m e (bouton, m e n u déroulant, liste, etc.) p o s s é d e r a u n e fonctionm e m b r e Afficher, a v e c les m ê m e s p a r a m è t r e s . Il v o u s sera p o s s i b l e d'écrire q u e l q u e c h o s e c o m m e : à lèvre à un poulet » ObjetGraphiqueGenerique *obj[10]; // tableau de 10 pointeurs // ... affectation d'objets divers aux pointeurs du tableau // (nous ne détaillons pas cette partie) for (i = 0; i < 10; i++) { obj[i] -> Afficher(paramètres); } V o u s n ' a v e z plus à v o u s soucier du t y p e e x a c t de l'objet grap h i q u e p o i n t é p a r obj [ i ] , p u i s q u e l a b o n n e fonction Aff i c h e r sera a p p e l é e a u t o m a t i q u e m e n t . En C, il aurait fallu p a s s e r p a r d e s p o i n t e u r s sur void*, définir p l u s i e u r s structures figées c o n t e n a n t d e s p o i n t e u r s sur d e s fonctions, e t autres bidouilles p e u c a t h o l i q u e s . U n e autre solution consisterait à utiliser des s w i t c h , m a i s cela rendrait l ' e x t e n s i o n du c o d e existant très é p i n e u s e . Le C n ' é t a n t p a s c o n ç u p o u r cela, v o u s v o u s e x p o s e r i e z à de m u l t i p l e s prob l è m e s de portabilité, de fiabilité, de m a i n t e n a n c e . C a r la v é ritable force d e frappe d u C + + , c'est l'héritage. S e u l l'héritage, et s o n c o m p l i c e la virtualité, p e r m e t t e n t de m e t t r e e n œ u v r e des s y s t è m e s c o m p l e x e s e t — r e l a t i v e m e n t — facile à modifier.
  • 88.
    Le polymorphisme etla virtualité Rions ensemble. Un utilisateur de Macintosh déclarait récemment : « Windows p o u r PC, c'est mettre du rouge 105 E x e m p l e : v o u s d é v e l o p p e z un e n v i r o n n e m e n t g r a p h i q u e . V o u s le baptisez Fenêtres pour Groupes de Travail 96*. G r â c e à la virtualité, c h a q u e objet g r a p h i q u e de v o t r e s y s t è m e (bouton, m e n u déroulant, liste, etc.) p o s s é d e r a u n e fonctionm e m b r e A f f i c h e r , a v e c les m ê m e s p a r a m è t r e s . I l v o u s sera p o s s i b l e d'écrire q u e l q u e c h o s e c o m m e : à lèvre à un poulet » ObjetGraphiqueGenerique *obj[10]; // tableau de 10 pointeurs // ... affectation d'objets divers aux pointeurs du tableau // (nous ne détaillons pas cette partie) for (i = 0; i < 10; i++) { obj[i] -> Afficher(paramètres) ; ) V o u s n ' a v e z plus à v o u s soucier du t y p e e x a c t de l'objet grap h i q u e p o i n t é p a r o b j [ i ] , p u i s q u e l a b o n n e fonction A f f i c h e r sera a p p e l é e a u t o m a t i q u e m e n t . E n C , i l aurait fallu p a s s e r p a r d e s p o i n t e u r s sur v o i d * , définir p l u s i e u r s structures figées c o n t e n a n t d e s p o i n t e u r s sur d e s fonctions, e t autres bidouilles p e u c a t h o l i q u e s . U n e autre solution consisterait à utiliser des s w i t c h , m a i s cela rendrait l ' e x t e n s i o n du c o d e existant très é p i n e u s e . Le C n ' é t a n t p a s c o n ç u p o u r cela, v o u s v o u s e x p o s e r i e z à de m u l t i p l e s p r o b l è m e s de portabilité, de fiabilité, de m a i n t e n a n c e . C a r la v é ritable force d e frappe d u C + + , c'est l'héritage. S e u l l'héritage, et s o n c o m p l i c e la virtualité, p e r m e t t e n t de m e t t r e e n œ u v r e des s y s t è m e s c o m p l e x e s e t — r e l a t i v e m e n t — facile à modifier.
  • 89.
    106 Pont entre Cet C++ Compléments Destructeurs virtuel Il est tout à fait p o s s i b l e de définir un d e s t r u c t e u r virtuel p o u r u n e classe. A quoi sert un d e s t r u c t e u r virtuel ? A détruire l'objet r é e l l e m e n t pointé, et n o n un objet i m a g i n a i r e qui aurait l e m ê m e type q u e celui d u p o i n t e u r . U n petit exemple : #include <iostream.h> class Véhicule { public : virtual -Véhicule!) { cout << "-Véhicule" << endl; } >; class Camion : public Véhicule { public : virtual -Camion() { cout << "-Camion" << endl; } }; void main() { Véhicule *pt_inconnu; Camion *mon_beau_camion = new Camion; pt_inconnu = mon_beau_camion; delete pt_inconnu; } // affichage : / / -Camion • // -Véhicule Le delete en dernière ligne appelle le d e s t r u c t e u r de Camion (puis le d e s t r u c t e u r de Véhicule, car les d e s t r u c t e u r s s o n t a p p e l é s en série de la classe d é r i v é e à la classe de b a s e ) . Si les d e s t r u c t e u r s n ' é t a i e n t p a s virtuels, seul le d e s t r u c t e u r de Véhicule aurait été a p p e l é . P o u r e x p l i q u e r cela a u t r e m e n t , pt_inconnu est d é c l a r é c o m m e un p o i n t e u r sur Véhicule, m a i s p o i n t e en réalité sur un objet de classe Camion. C o m m e les d e s t r u c t e u r s s o n t ici virtuels, le p r o g r a m m e a p p e l l e le destructeur qui à la classe de l'objet r é e l l e m e n t pointé, c'est-à-dire Camion.
  • 90.
    Le polymorphisme etla virtualité Classes abstraites et fonctions virtuelles pures 107 Le concept de classe abstraite (ou classe abstraite de base) est c o m m u n à de n o m b r e u x l a n g a g e s orientés-objets. De quoi s'agit-il ? U n e classe est dite abstraite si elle est c o n ç u e d a n s le b u t de servir de c a d r e g é n é r i q u e à ses classes dérivées. De p l u s , u n e classe abstraite ne p e u t e n g e n d r e r aucun objet. C ' e s t un p o i n t capital, qui p e r m e t de s o u l i g n e r q u e la raison d'être d ' u n e classe abstraite réside d a n s ses classes dérivées. Fonctions virtuelles pures L e C + + autorise l a m i s e e n œ u v r e des classes abstraites, p a r l e biais d e f o n c t i o n s - m e m b r e s virtuelles p u r e s . E n C + + , u n e classe est déclarée abstraite q u a n d elle contient au moins une fonction virtuelle pure. U n e fonction virtuelle p u r e est u n e f o n c t i o n - m e m b r e d o n t seul le profil (valeur retournée, n o m et p a r a m è t r e s ) est défini. On déclare u n e fonction virtuelle en ajoutant « = 0; » après l'entête d e l a fonction, e n plus d u m o t - c l é v i r t u a l . Conséquences Q u a n d v o u s déclarez u n e fonction virtuelle p u r e d a n s u n e classe, voilà ce qui se p a s s e : la classe est décrétée « classe abstraite » : elle ne p e u t d o n c pas être utilisée p o u r créer un objet. la fonction virtuelle p u r e ne peut être définie : elle n ' a q u ' u n profil, u n entête, m a i s pas d e c o r p s les classes futures qui hériteront de cette classe abstraite devront définir les f o n c t i o n s - m e m b r e s virtuelles p u r e s de la classe abstraite. I n t é r ê t du v i r t u e l p u r L'intérêt d e s fonctions virtuelles p u r e s et d e s c l a s s e s abstraites de b a s e , c'est de s'assurer q u e les classes d é r i v é e s r e s p e c teront l'interface d e votre classe. V o u s i m p o s e z u n e n t ê t e p o u r u n e fonction g é n é r i q u e , et v o u s forcez les classes dériv é e s à redéfinir cette fonction. C ' e s t un m o y e n s u p p l é m e n taire d e garantir u n e b o n n e h o m o g é n é i t é d e votre architecture de classes.
  • 91.
    108 Pont entre Cet C++ E x e m p l e court D a n s le listing qui suit, n o u s déclarons la f o n c t i o n - m e m b r e • a f f i c h e r virtuelle p u r e , e n ajoutant = 0 après s o n entête. N o u s n e p o u v o n s d o n c p a s créer d'objet d e cette classe. E n r e v a n c h e , n o u s p o u v o n s créer d e s objets de la classe Dériv é e qui définit l a fonction a f f i c h e r . class AbstraiteBase { protected : // blabla public : virtual void afficher(int x, int y) = 0; // c'est ce « = 0 » qui rend la fonction // virtuelle pure } ; class Dérivée : { protected : public : virtual // public AbstraiteBase // blabla void afficher(int x, int y ) ; (virtuelle simple) } ; void Dérivée::afficher(int x, int y) { // traitement } void main() { AbstraiteBase toto; Dérivée tata; // impossible !!! // possible. tata.afficher(15, 1 2 ) ; // possible. } Exemple complet Voici u n petit e x e m p l e c o m p l e t m e t t a n t e n é v i d e n c e l'intérêt de la virtualité p u r e . A v a n t d ' e n d é c o u v r i r le listing, quelques é c l a i r c i s s e m e n t s : D a n s l e c a d r e d u d é v e l o p p e m e n t d ' u n e interface g r a p h i q u e , n o u s a v o n s choisi de définir u n e classe Obj etGraphique, qui contient le m i n i m u m i n d i s p e n s a b l e à tous les objets grap h i q u e s de notre interface. Par objet g r a p h i q u e , n o u s entend o n s tout ce qui est utilisé d a n s l'interface (bouton, a s c e n s e u r s , m e n u s , m a i s aussi z o n e p o u v a n t contenir d'autres objets, z o n e de dessin, etc.) D a n s un souci de sim-
  • 92.
    Le polymorphisme etla virtualité 109 plification, n o u s retiendrons trois caractéristiques : l'objet g r a p h i q u e parent, les c o o r d o n n é e s en x et en y de l'objet d a n s l ' e s p a c e de c o o r d o n n é e s a s s o c i é e s à l'objet parent. A q u o i sert l'objet p a r e n t ? A savoir d a n s quel autre objet est situé l'objet courant. P a r e x e m p l e , u n b o u t o n aura c o m m e o b jet p a r e n t u n e fenêtre. D a n s l e c a d r e d e l ' e x e m p l e , n o u s décrirons é g a l e m e n t l a classe B o u t o n O n O f f , c o r r e s p o n d a n t à l'objet « c a s e à coc h e r ». P o u r offrir un m i n i m u m de r é a l i s m e s a n s c o m p l i q u e r , n o u s s u p p o s e r o n s q u e n o u s travaillons e n m o d e texte, e t utiliserons les primitives d e T u r b o C + + sur P C p o u r p o s i tionner le c u r s e u r à l'écran. R a s s u r e z - v o u s , il n ' y a rien de sorcier là dedans. Voici le listing : #include <conio.h> // propre a Turbo C++, // permet de positionner le curseur enum Boolean { False = (1==0), True = (1==1) } ; // type booléen classe ObjetGraphique // class ObjetGraphique { protected : ObjetGraphique *pere; int x, y; // Objet pere // coord. par rapport // au pere public : // constructeur ObjetGraphique(ObjetGraphique *pi = 0, int xi = 0, int yi = 0) : pere(pi), x(xi), y(yi) { } Nous utilisons ici plusieurs techniques propres au C + + et indépendantes des // fonctions d'accès ObjetGraphique *get_pere() { return pere; ) int get_x() { return x; } int get_y() { return y; } fonctions virtuelles : les paramètres par défaut (voir page 65), la liste d'initialisation (voir page 47), et la // fonctions de modification virtual Boolean set_x(int me) = 0; virtual Boolean set„y(int ny) = 0; définrtion inline des fonctions (voir page 21), // fonctions de haut niveau virtual void Redessine() = 0; } ;
  • 93.
    110 Pont entre Cet C++ classe BoutonOnOff // Les constantes sont traitées page 67. class BoutonOnOff : public ObjetGraphique { private : const char CAR_ON; //caractère affiché si ON const char CAR_OFF; //caractère affiché si OFF Boolean état; //bouton ON (True) ou OFF public : // constructeur BoutonOnOff(ObjetGraphique *pi = 0, int xi = 0, int yi = 0, Boolean ei = False) : ObjetGraphique(pi, xi, y i ) , etat(ei), CAR_ON('x'), CAR_OFF('0') { } // fonctions virtuelles pures a définir // fonctions de modification virtual Boolean set_x(int nx) ; virtual Boolean set_y(int n y ) ; // fonctions de haut niveau virtual void Redessine!); // fonctions nouvelles de BoutonOnOff Boolean get_etat() { return état; } void set_etat(Boolean ne) { état = ne; Redessine(); } } ; // définitions des fonctions de BoutonOnOff Boolean BoutonOnOff::set„x(int nx) { i f (nx != x) { x = nx ; Redessine(); } return True; } Boolean BoutonOnOff;:set v(int ny) { if (ny != y) { y = ny; Redessine( ) ; } return True; } void BoutonOnOff::Redessine() { // positionnons le curseur en x, y gotoxy(x, y ) ; // affichons un caractère à l'endroit du curseur
  • 94.
    Le polymorphisme etla virtualité 111 putch( état ? CAR_ON : CAR_OFF ); } // programme principal void main() { BoutonOnOff bouton(0, 10, 1 0 ) ; bouton.Redessine(); getch(); // attend qu'une touche soit frappée bouton.set_etat(True); ) Ce programme met en évidence l'intérêt de la virtualité pure. D a n s la classe de base O b j e t G r a p h i q u e , trois fonctions sont déclarées virtuelles pures : s e t _ x , s e t _ y , et R e d e s s i n e . Pourquoi ? D a n s l'idée, p o u r fournir un c a d r e g é n é r a l i d e n t i q u e à tous les objets g r a p h i q u e s . En forçant les classes d é r i v é e s à définir ces trois fonctions, on s'assure q u e leurs e n t ê t e s d e v r o n t se c o n f o r m e r à un certain profil. Un p r o g r a m m e u r sachant utiliser u n objet g r a p h i q u e s a u r a a u t o m a t i q u e m e n t utiliser tous les autres, y c o m p r i s c e u x qui ne sont pas e n c o r e écrits. s e t _ x e t s e t _ y d é p e n d e n t bel e t b i e n d e c h a q u e objet g r a p h i q u e , car il c o n v i e n t de s'assurer q u e les n o u v e l l e s v a l e u r s sont correctes, n o t a m m e n t , o n p e u t l ' i m a g i n e r , p a r r a p p o r t à l'objet p è r e qui contient n o t r e objet graphique. R e d e s s i n e d é p e n d aussi d e c h a q u e objet g r a p h i q u e . I l n e p r e n d a u c u n p a r a m è t r e car u n objet doit être c a p a b l e de se r e d e s s i n e r en fonction de ses d o n n é e s i n t e r n e s (ici ses c o o r d o n n é e s en x et y ) .
  • 95.
    Les références Seul lepassage par valeur est autorisé en C standard. Même lorsque vous passez un pointeur à une fonction, vous passez la valeur de ce pointeur. Le C++ introduit une nouvelle possibilité : le passage par référence (connu depuis longtemps en Pascal). Par ailleurs, ce même mécanisme de référence vous permet de déclarer des synonymes de variables : il s'agit des références libres. Notions de base L'idée Q u a n d v o u s p a s s e z à u n e fonction un p a r a m è t r e p a r référence, cette fonction reçoit un s y n o n y m e du p a r a m è t r e réel. En o p é r a n t sur le p a r a m è t r e p a s s é p a r référence, la fonction p e u t d o n c modifier d i r e c t e m e n t l e p a r a m è t r e réel. V o u s n ' a v e z p a s b e s o i n d'utiliser l'adresse d u p a r a m è t r e p o u r l e modifier, c o m m e v o u s d e v r i e z le faire en C s t a n d a r d . Mise en Le caractère & est utilisé d a n s l'entête de la fonction p o u r sig n a l e r q u ' u n p a s s a g e p a r référence est d é c l a r é : œuvre C++ void plus_l(int Snombre) // passage par référence attendu { nombre++;
  • 96.
    114 Pont entre Cet C++ ) void { main() int i = 3 ; plus_l(i); // i vaut maintenant 4 } C o m m e v o u s le constatez, l'appel se fait de m a n i è r e très s i m p l e . L a fonction p l u s _ l sait q u e l e p a r a m è t r e entier q u ' e l l e attend est u n e référence vers l e p a r a m è t r e réel i . E n c o n s é q u e n c e , toute modification d e n o m b r e d a n s l a fonction p l u s _ l s e répercute sur l a variable i d u p r o g r a m m e principal. Déclarer une référence libre V o u s p o u v e z utiliser les références en d e h o r s de toute fonction : en déclarant q u ' u n e variable est « s y n o n y m e » d'une autre, c'est-à-dire q u e l ' u n e est u n e référence v e r s l'autre. D è s lors, u n e modification sur l ' u n e q u e l c o n q u e de ces variables affectera le c o n t e n u de l'autre. P a r e x e m p l e : void main() { int nombre = 1 ; int &bis = nombre; // bis est une référence à nombre int *pointeur; bis = 1 0 ; nombre = 20; pointeur = &bis; (*pointeur) = 3 0 ; // bis et nombre valent 10 // bis et nombre valent 20 // bis et nombre valent 3 0 } L'intérêt par rapport au C * les constructeurs copie sont vus au chapitre 2, page 3 I. Le fait d'utiliser le caractère & p o u r autre c h o s e q u e ce qu'il signifie en l a n g a g e C doit v o u s paraître étrange. S o y e z rassuré : v o u s p o u v e z p r e s q u e t o t a l e m e n t v o u s p a s s e r d'utiliser les références, tout en bénéficiant des substanciels a v a n t a g e s du C + + . P o u r q u o i presque totalement ? Eh b i e n , p o u r être franc, il faut a v o u e r q u e les c o n s t r u c t e u r s de copie* ont b e soin de types p a s s é s p a r référence. V o u s r e m a r q u e r e z tout d e m ê m e q u e , p a s s é e l a p r e m i è r e a n g o i s s e , les références se révèlent très p r a t i q u e s !
  • 97.
    Les références 115 Compléments Références et objets provisoires *Petite précision de vocabulaire : dans la ligne int &ref = ini; ref P o u r q u ' u n e référence soit c o r r e c t e m e n t utilisée, il faut q u e l'objet référence et l'objet initial* soient de m ê m e type. S i n o n , plusieurs cas p e u v e n t se présenter : L'objet initial est u n e c o n s t a n t e int &ref = 1 ; const int &ref = 1 ; // (1) NON (voir ci-dessous) // (2) OUI désigne l'objet référence et ini l'objet initial. L'objet initial peut être converti v e r s l'objet r é f é r e n c e int const initial = 5; float &ref = initial; float &ref = initial; // (1) NON // (2) OUI L e s d e u x cas ci-dessus e n g e n d r e n t les m ê m e s r e m a r q u e s : (1) La référence n ' e s t p a s constante. V o t r e c o m p i l a t e u r doit n o r m a l e m e n t générer u n e erreur d e type. (2) La référence est constante. Un objet t e m p o r a i r e est créé ; la référence le désigne. Fonction retournant une référence D a n s s o n désir infini de r e p o u s s e r toujours plus loin les limites d u C , l e C + + p e r m e t , grâce a u x références, d'utiliser les fonctions c o m m e des variables ordinaires a u x q u e l l e s o n p e u t affecter u n e valeur. Ainsi, q u a n d u n e fonction r e t o u r n e u n e v a l e u r par référence, on p e u t d i r e c t e m e n t agir s u r cette v a l e u r d e retour. M a i s u n petit e x e m p l e v a u t m i e u x q u ' u n e explication o b s c u r e : // variables globales int t a b [ ] = { 0 , 1, 2, 4, 8 } ; const int nb_elem = 5 ; // nbr d'éléments de tab int &fonction(int j) // retourne une référence { if (j >= 0 && j < nb_elem) return tab[j]; else return tab[0]; } void main()
  • 98.
    116 Pont entre Cet C++ { fonction (0) = Ï0 ; // tab[0] vaut -ïb fonction(l) = fonction(4); // tab[l] vaut 8 } La p r e m i è r e ligne du m a i n est e x é c u t é e c o m m e ceci : a p p e l à f o n c t i o n , qui retourne u n e référence vers t a b [ 0 ] . D o n c , d a n s l ' e x p r e s s i o n f o n c t i o n (0) = 10;, tout se p a s s e c o m m e si f o n c t i o n ( 0 ) était r e m p l a c é p a r t a b [ 0 ]. On affecte d o n c b i e n la valeur 10 à t a b [ 0 ]. L a d e u x i è m e ligne fonctionne selon l e m ê m e principe.
  • 99.
    Les templates ou lamise en œuvre de la généricité Toujours plus, toujours : dans sa C + + nous offre les templates, qui les fonctions ou les classes par des sants, mais souvent délicats à mettre teurs relativement récents autorisent et fiable. débauche d'effets spéciaux, le vous permettent de paramétrer types. Les templates sont puisen œuvre : seuls les compilaleur usage de manière simple Notions de base L'idée Juste une remarque de vocabulaire : la généricité est le concept orientéobjet, alors que les templates sont la solution apportée par le C + + pour mettre en œuvre ce concept. J u s q u ' à présent, les fonctions p o u v a i e n t avoir c o m m e p a r a m è t r e s des variables o u des objets. M a i s j a m a i s v o u s n ' a v e z eu la possibilité de les p a r a m é t r e r p a r d e s types. C ' e s t j u s t e m e n t le rôle et l'intérêt de la généricité et des t e m p l a t e s . D e s classes p e u v e n t é g a l e m e n t être déclarées « t e m p l a t e s », si v o u s j u g e z q u ' e l l e s doivent d é p e n d r e d ' u n type. Par e x e m p l e , u n e classe qui gère u n e liste d'objets q u e l c o n q u e s n ' a p a s à se soucier du type réel de ces é l é m e n t s : elle ne doit s ' o c c u p e r q u e de les classer, d'en ajouter ou d ' e n s u p p r i m e r à sa liste, etc. U n e telle classe a d o n c intérêt à être p a r a m é t r é e
  • 100.
    118 Pont entre Cet C++ p a r le type des é l é m e n t s q u ' e l l e s t o c k e . Il serait j u d i c i e u x d'en faire u n e classe « t e m p l a t e ». Mise en œuvre C++ Il est i m p o r t a n t de distinguer d e u x types de t e m p l a t e s : celles qui c o n c e r n e n t les fonctions et celles qui c o n c e r n e n t les classes. E x a m i n o n s d ' a b o r d les t e m p l a t e s de fonctions. Templates de fonctions Exemple P r e n o n s l ' e x e m p l e classique de la fonction qui r e t o u r n e le m i n i m u m de d e u x chiffres. Il s e m b l e l é g i t i m e de v o u l o i r r e n d r e cette fonction i n d é p e n d a n t e du t y p e d e s objets. V o y o n s la m i s e en œ u v r e d ' u n e telle fonction, g r â c e a u x templates : tinclude <iostream.h> / / pour l'affichage avec cout template <class TYPE> TYPE min(TYPE a, TYPE b) { return ( a < b ) ? a : b; } main() { int char il = 10, i2 = 20; cl = 'x', c2 = ' a ; 1 cout << "min entier : " << min(il, i2) << endl; cout << "min char : " << min(cl, c2) << endl; } Commentaires Observez bien la déclaration de A v a n t le type de retour, v o u s d e v e z la fonction. préciser tem- piate <class NomType> où NomType est un identificateur de votre cru, q u e v o u s utiliserez e n s u i t e p o u r d é s i g n e r le type « p a r a m è t r e » de votre fonction. V o u s êtes obligés d'utiliser ce n o m de type d a n s au moins un p a r a m è t r e de votre fonction. Ici, les d e u x é l é m e n t s à c o m p a r e r sont de t y p e MonType, et la fonction r e t o u r n e é g a l e m e n t un objet de type MonType. À l'appel, le c o m p i l a t e u r reconnaît le type des p a r a m è t r e s et r e m p l a c e d a n s la fonction toutes les o c c u r e n c e s de
  • 101.
    Les templates 119 MonType parle t y p e v o u l u (ici, int d a n s le p r e m i e r appel e t c h a r d a n s l e d e u x i è m e ) . T e m p l a t e s de f o n c t i o n s : cas g é n é r a l Templates de classes * Il faudrait bien entendu gérer un Exemple L e s t e m p l a t e s v o u s seront é g a l e m e n t utiles d a n s d e s c l a s s e s . R e p r e n o n s l ' e x e m p l e de la classe qui gère u n e liste d'objets de type (ou de classe) q u e l c o n q u e . P o u r simplifier le p r o p o s , n o u s utiliserons u n tableau p o u r stocker les objets*. C o m m e n t la déclarer ? nombre variable d'objets, avec allocations et désallocations « à la demande ». Cela compliquerait la classe et nuirait à l'explication des tinclude <iostream.h> // pour affichage avec cout #include <stdlib.h> // pour la fonction exit template <class TYPE> class Liste { private: enum { MAX = 3 ,} ; // constante TYPE elem[MAX]; templates. Par ailleurs, public : la gestion d'erreurs est réduite ici à sa TYPE void void plus simple expression. N o u s verrons au chapitre I 3, page I 37, comment implanter une véritable gestion des erreurs. Liste() { } get_elem(int n ) ; set_elem(int n, TYPE e ) ; affiche(); } ; template <class TYPE> TYPE Liste<TYPE>::get_elem(int n) // retourne le nième élément classé { if (n >= 1 && n <= MAX) return elem[ n-1 ]; else { cerr <<"Liste::get_elem - Indice hors limite :" << n << endl;
  • 102.
    120 Pont entre Cet C++ exit(1); } } template <class T Y P E > void Liste<TYPE>: :set_elem(int n, TYPE e) // stocke e à la nième place dans la liste { if ( n >= 1 && n <= MAX) elem[n-l] = e; else { cerr <<"Liste : :set_elem - Indice hors limite :" << n << endl; exit(1); } } template <class TYPE> { int i; void Liste<TYPE>::affiche() for (i = 0; i < MAX ; i + +) cout << "Num " << i+1 << ':' << elem[i] << endl; } main() { Liste<int> liste; liste.set_elem(1, 1 0 ) ; liste.set_elem(3, 3 0 ) ; liste.set_elem(2, 2 0 ) ; liste.affiche(); // affichage : // Num 1 : 10 // Num 2 : 20 // Num 3 : 30 } Q u e l q u e s e x p l i c a t i o n s s u r cet e x e m p l e N o u s a v o n s choisi d e stocker les é l é m e n t s d a n s u n tableau de taille MAX, MAX étant u n e c o n s t a n t e spécifique à la classe Liste, définie à l'aide d ' u n enum (voir p a g e 7 1 ) . La déclaration de la classe i n d i q u e q u e c'est u n e t e m p l a t e par a m é t r é e p a r le type TYPE : template <class TYPE> class Liste
  • 103.
    Les templates 121 N ou s utilisons d o n c le type g é n é r i q u e TYPE d a n s la c l a s s e , p o u r i n d i q u e r ce q u e n o u s s t o c k o n s d a n s le t a b l e a u e l e m : TYPE elem[MAX]; N o t e z é g a l e m e n t la définition des f o n c t i o n s - m e m b r e s en deh o r s de la classe : template <class TYPE> TYPE Liste<TYPE>: :get_elem(int n) TYPE est le type de retour de la fonction, d o n t le n o m de classe est Liste<TYPE>. La seule c h o s e qui c h a n g e vraim e n t par r a p p o r t à u n e f o n c t i o n - m e m b r e n o r m a l e , c'est le préfixe « template <class TYPE>». Utilisation d'une classe t e m p l a t e L'utilisation d ' u n e classe t e m p l a t e se fait en d o n n a n t le n o m d u type entre c h e v r o n s . D a n s l ' e x e m p l e ci-dessus, cela donne : Liste<int> liste; où int aurait pu être r e m p l a c é p a r n ' i m p o r t e quel autre type o u classe. T e m p l a t e s de classes : cas g é n é r a l Voici c o m m e n t l'on déclare u n e classe t e m p l a t e :
  • 104.
    122 Pont entre Cet C++ Et voici c o m m e n t l'on définit u n e f o n c t i o n - m e m b r e d ' u n e classe t e m p l a t e : Compléments Attention à l'ambiguïté I l p e u t v o u s arriver d'avoir b e s o i n d ' i m b r i q u e r d e s t e m p l a tes. Par e x e m p l e , v o u s v o u l e z gérer u n e liste d'arbres, où la classe Liste et la classe A r b r e sont des t e m p l a t e s . Il n ' y a en théorie p a s de p r o b l è m e , m a i s je tiens à attirer v o t r e attention sur la m a n i è r e de déclarer un objet Liste : Liste<Arbre<int>> ma_liste_d_arbres; // NON D a n s cette déclaration, l e type d e v o t r e liste t e m p l a t e n ' e s t autre q u ' u n e classe A r b r e , e l l e - m ê m e t e m p l a t e , d o n t l e t y p e est i n t . C e t t e déclaration p r o v o q u e i n s t a n t a n é m e n t u n e erreur de c o m p i l a t i o n , b i e n q u e , à p r e m i è r e v u e , elle s e m b l e correcte. La seule c h o s e qui c l o c h e , c'est la p r é s e n c e d e » . E n C + + , les d e u x caractères s u p é r i e u r s r e p r é s e n t e n t un opérateur. Il y a d o n c m é p r i s e . P o u r y r e m é d i e r , t a p e z un e s p a c e entre les d e u x c h e v r o n s . L a b o n n e déclaration d e v i e n t donc : Liste< Arbre<int> > ma_liste_d_arbres; // OUI
  • 105.
    Les classes et fonctionsamies L'amitié n'est pas une vertu réservée aux humains. Les classes et les fonctions C++ peuvent, elles aussi, se lier d'amitié. Heureusement, vous seul pouvez les autoriser à s'enticher ainsi les unes des autres. Notions de base L'idée * Rappelons que l'encapsulation c o n siste à manipuler des objets composés de données et de fonctions de manipulation de ces données. Les données sont cachées au monde extérieur. L o r s q u ' u n e fonction est déclarée a m i e ( f r i e n d ) d ' u n e c l a s s e , elle p e u t a c c é d e r directement à toutes les d o n n é e s p r i v a t e o u p r o t e c t e d d e cette classe. L o r s q u ' u n e classe est déclarée a m i e d ' u n e autre c l a s s e , ce s o n t toutes les fonctions de la p r e m i è r e classe qui p e u v e n t a c c é d e r a u x d o n n é e s p r i v é e s d e l a d e u x i è m e classe. C'est bien beau, mais si vous avez bien compris le premier chapitre de ce livre, v o u s devriez b o n d i r de v o t r e siège, et v o u s écrier « M a i s c'est u n e entorse i n s u p p o r t a b l e à la sacrosainte règle de l'encapsulation* ! » Et v o u s auriez raison. A u s s i s o m m e s - n o u s en droit de n o u s d e m a n d e r pourquoi le créateur du C + + a p e r m i s q u e l'on viole — le m o t n ' e s t p a s
  • 106.
    224 Pont entre Cet C++ trop fort — l ' e n c a p s u l a t i o n ? E s s e n t i e l l e m e n t p o u r d e s rais o n s d'architecture, d u e s a u fait q u e l e C + + n ' e s t p a s u n lang a g e e n t i è r e m e n t orienté objet (il co-existe a v e c le C ) , m a i s aussi p o u r p e r m e t t r e à un p r o g r a m m e u r d ' a c c o r d e r des « droits » entre ses classes internes, et é v e n t u e l l e m e n t p o u r des raisons d ' o p t i m i s a t i o n (voir aussi les q u e s t i o n s - r é p o n s e s , a u chapitre 16). V o u s t r o u v e r e z l'utilité d e s classes a m i e s d a n s l ' e x e m p l e d e s u r c h a r g e d e l'opérateur « , p a g e 92. C e l a dit, méfiez-vous et n'utilisez les « a m i e s » q u e si v o u s y êtes contraint... Mise en œuvre C++ La déclaration de l'amitié d ' u n e classe A p o u r u n e autre entité se fait n ' i m p o r t e où d a n s la classe A, g r â c e au m o t - c l é friend: class A { friend void fonction_B(); friend class C; // // (1) pour une fonction 1/(2) pour une classe ... > ; La l i g n e (1) signifie q u e fonction_B p e u t a c c é d e r a u x d o n n é e s private ou protected de la classe A. R e m a r q u e z q u e si f onction_B était u n e fonction m e m b r e d ' u n e autre c l a s s e , il faudrait spécifier de quelle classe elle est m e m b r e , avec u n e ligne du style : friend void AutreClasse: :fonction_B(); La l i g n e (2) signifie q u e toutes les fonctions de la c l a s s e C ont le droit d ' a c c é d e r d i r e c t e m e n t a u x d o n n é e s private ou protected de la classe A. C o m m e v o u s l'avez constaté, la déclaration f riend... se fait d a n s la classe qui accorde le droit a u x autres de v e n i r tripatouiller ses p r o p r e s d o n n é e s . R é c a p i t u l o n s :
  • 107.
    Classes et fonctionsamies Attention ! 125 Utilisez avec la p l u s g r a n d e p r é c a u t i o n les fonctions f r i e n d : elles tordent le c o u à l ' e n c a p s u l a t i o n , et sont d o n c d a n g e r e u s e s p o u r la stabilité de votre application. Elles ne sont intéressantes q u e p o u r o p t i m i s e r des c l a s s e s , o u p o u r accorder d e s droits entre classes internes qui n ' i n t é r e s s e r o n t p a s d'autres p r o g r a m m e u r s . E n r é s u m é , bidouillez v o s c l a s ses à v o u s , m a i s faites du travail p r o p r e sur les classes qui seront u t i l i s é e s / r e p r i s e s p a r d'autres. G a r d e z b i e n p r é s e n t à l'esprit q u ' u n e modification d a n s les d o n n é e s p r i v é e s d ' u n e classe qui a déclaré d'autres c l a s s e s a m i e s doit se répercuter sur ces autres classes ! A u s s i , m é fiez-vous et i n d i q u e z toujours c l a i r e m e n t d a n s la d o c u m e n tation quelles classes sont a m i e s d e qui, p o u r q u e c e u x qui s e p e n c h e r o n t sur v o t r e travail d a n s q u e l q u e s m o i s ( v o u s y c o m p r i s ) ne s'arrachent p a s les c h e v e u x en se c o g n a n t la tête c o n t r e les m u r s .
  • 108.
    L'héritage multiple L'héritage déjà pour différence classes! tors... simple vous permet d'utiliser des classes qui existent en créer d'autres. L'héritage multiple est identique, à la près que vous pouvez hériter simultanément de plusieurs Ce qui n'est pas sans engendrer quelques problèmes re- Notions de base L'idée L ' h é r i t a g e m u l t i p l e v o u s p e r m e t d e créer des classes d é r i v é e s qui héritent e n m ê m e t e m p s d e p l u s i e u r s classes d e b a s e . I l n e s'agit pas d ' u n héritage s i m p l e e n c h a î n e m a i s bel e t b i e n d'un héritage m u l t i p l e s i m u l t a n é :
  • 109.
    128 Pont entre Cet C++ D a n s le cas p r é s e n t é à droite du s c h é m a ci-dessus, la classe C hérite du c o n t e n u des classes A et B (mais cela d é p e n d touj o u r s d e s spécificateurs p r o t e c t e d , p r i v a t e e t p u b l i c ) . Mise en œuvre C++ U n héritage multiple s e spécifie c o m m e u n héritage s i m p l e , en séparant p a r d e s virgules les classes de b a s e s désirées. P o u r déclarer l'héritage multiple du s c h é m a p r é c é d e n t , il faudrait écrire : class A { // blabla public : void fonction_a(); } ; class B { // blabla public : void fonction_b(); } ; class C : public A, public B { // blabla public : void fonction_c(); } ; E n admettant que f o n c t i o n _ a , f o n c t i o n _ b e t f o n c t i o n ^ ont été définies, v o u s pourriez écrire q u e l q u e chose comme : void main() { C objet_c; objet_c.fonction_a(); // appel à A::fonction_a() objet_c.fonction_b() ; // appel à B: :fonction_b() objet_c.fonction_c(); // appel à C: :fonction_c() } Il n ' y a là rien de b i e n surprenant. On pourrait m ê m e croire q u e c'est simple. M a i s c o m m e d e n o m b r e u x c o n c e p t s C + + , l e d e s s u s d e l'iceberg c a c h e u n certain n o m b r e d e complications difficiles à déceler au p r e m i e r a b o r d . . .
  • 110.
    L'héritage multiple 129 O rd r e d'appel des constructeurs D a n s le cas d ' u n héritage m u l t i p l e , la création d ' u n objet de la classe dérivée appelle les c o n s t r u c t e u r s des classes de b a s e d a n s l'ordre de déclaration de l'héritage. P a r e x e m p l e : class A { } ; class B { } ; class C : public B, public A // la ligne ci-dessus détermine l'ordre d'appel // des constructeurs { } ; void main() { C objet_c; // appel à B ( ) , puis A ( ) , puis C() } Les listes d'initialisations sont présentées page 47. Par ailleurs, si v o u s p r é c i s e z u n e liste d'initialisation*, l'ordre d ' a p p e l des c o n s t r u c t e u r s d é p e n d r a toujours de l'ordre de d é c l a r a t i o n des classes de b a s e s . P a r e x e m p l e : class C : public B, public A { public : C() ; } ; C::C() : A(param), B(param) // liste d'initialisation { } void main() { C objet_c; // appel à B(param), A(param) puis C() } L'intérêt par rapport au C Le l a n g a g e C ne p r o p o s e p a s d ' é q u i v a l e n t de la n o t i o n d'héritage s i m p l e , il est d o n c v a i n de c h e r c h e r un é q u i v a l e n t d'héritage m u l t i p l e . Le principal atout de ce dernier est q u ' i l p e r m e t d e m o d é l i s e r d e façon p l u s juste l e m o n d e réel.
  • 111.
    730 Pont entre Cet C++ Par e x e m p l e , si v o u s é l a b o r e z u n e classification des a n i m a u x , v o u s p o u v e z être a m e n é à considérer la b a l e i n e c o m m e un m a m m i f è r e m a i s aussi c o m m e u n a n i m a l v i v a n t s o u s l'eau. L'héritage multiple vous permettra de garder un schéma cohérent : exemple d'héritage multiple Il faut n é a n m o i n s t e m p é r e r cette excitation : l'héritage m u l tiple est c o m p l e x e à m a n i p u l e r ; n o u s a v o n s vu à q u e l point il faut faire attention a u x a m b i g u ï t é s difficiles à déceler. E s s a y e z autant q u e p o s s i b l e d'éviter l'héritage multiple. N ' o u b l i e z p a s q u e m ê m e s i v o u s l e m a î t r i s e z parfaitement, c e n ' e s t peut-être p a s le cas d e s futurs utilisateurs de v o s classes. Or la c o m p r é h e n s i o n d ' u n e hiérarchie de classes est vitale p o u r c o n c e v o i r du c o d e fiable et c o m p r é h e n s i b l e .
  • 112.
    L'héritage multiple 131 Compléments Duplication de données d'uneclasse de base A t t a r d o n s - n o u s sur c e qui s e p a s s e e n m é m o i r e d a n s certains cas d ' h é r i t a g e s multiples. A d m e t t o n s q u e les d e u x classes d e b a s e d ' u n héritage m u l t i p l e héritent e l l e s - m ê m e s d ' u n e m ê m e classe de b a s e : héritage multiple « double » Traduisons ce schéma en C + + : class Base { int variable_base; // blabla } ; class A : public Base { int variable_A; // blabla }; class B : public Base { int variable_B; // blabla } ; class C : public A, public B { int variable_C; // blabla }; C o n s é q u e n c e s : un objet de classe C c o n t i e n d r a d e u x fois les d o n n é e s héritées de la classe Base. P o u r q u o i ? P a r c e q u e à la fois l'objet A et l'objet B c o n t i e n n e n t les d o n n é e s héritées de la classe Base. Or la classe C hérite de la c l a s s e A et de la
  • 113.
    132 Pont entre Cet C++ classe B. C bénéficie donc de d e u x copies distinctes des donn é e s héritées de la classe Base. En m é m o i r e , cela r e s s e m b l e à ceci : C o m m e n t , dès lors, a c c é d e r à l ' u n e ou l'autre c o p i e d e s donn é e s de la classe Base ? T o u t s i m p l e m e n t en utilisant l'opérateur de résolution de p o r t é e : :. Il suffit de spécifier le n o m de la classe à laquelle appartient la d o n n é e v o u l u e . A i n s i , p o u r a c c é d e r à variable_base hérité d a n s la classe B, il faut utiliser B: :variable_base. De m ê m e , p o u r acc é d e r à la c o p i e de variable_base s t o c k é e d a n s la classe A, il faut utiliser A : : va r i ab 1 e_ba s e. Voici un e x e m p l e illustrant la duplicité d e s d o n n é e s et leur utilisation : class Base * Pour un exemple public : int variable_base; // désolé * simple et court, la variable est déclarée class A : public Base public. C'est un exemple à ne pas // blabla suivre (en temps normal). Il faudrait bien entendu la déclarer private ou class B : public Base // blabla protected et définir des fonctions public d'accès et de modification. class C : public B, public A // blabla void main()
  • 114.
    L'héritage multiple 133 { C objet_c; objet_c.variable_base =1; objet_c.B: :variable_base = 1; objet_c.A::variable_base = 2; // (1) erreur!!! // (2) mieux // (3) mieux } L a ligne (1) n e précise p a s à quelle v a r i a b l e _ b a s e elle recourt. L e c o m p i l a t e u r n e c o m p r e n d p a s e t v o u s d e m a n d e d e lever l ' a m b i g u ï t é . En r e v a n c h e , les lignes (2) et (3) sont s a n s é q u i v o q u e . R a p p e lons que les variables B : :variable_base et A : : v a r i a b l e _ b a s e s o n t différentes e t b i e n distinctes e n mémoire. Masquage du polymorphisme S o u s c e titre s e c a c h e u n p r o b l è m e é p i n e u x e n g e n d r é par l'héritage multiple. Le fait d'être obligé de p r é c i s e r e x a c t e m e n t à quelle fonction on fait référence va à l ' e n c o n t r e m ê m e du p r i n c i p e de virtualité. P r e n o n s un e x e m p l e où cette faiblesse est m i s e en é v i d e n c e : N o u s considérons qu'un périphérique est à la fois une ressource et un objet réel, physique. N o u s définissons une fonction virtuelle I n f o pour les classes R e s s o u r c e , O b j e t R e e l e t P é r i p h é r i q u e . Voici l e listing C + + qui correspond à cette architecture : class Ressource { public : virtual void Info(); }; void Ressource::Info() { } class ObjetReel
  • 115.
    134 Pont entre Cet C++ { public : virtual void Info(); } ; void ObjetReel: : Info() { } class Périphérique : public Ressource, public ObjetReel { } ; class Imprimante : public Périphérique { public : virtual void Info(); }; void Imprimante::Info() { } void main() { Périphérique Imprimante * inconnu; hp5 00; inconnu = &hp500; inconnu -> Info(); inconnu -> Ressource::Info(); // (1) // (2) inattendu // (3) OK ) Le problème * En effet inconnu est un pointeur de Périphérique, mais il pointe en réalité sur un objet de classe Imprimante. Il existe un moyen d'éviter ce problème : c'est l'héritage virtuel, détaillé ci-après. Voici le problème : en temps normal, grâce au mécanisme de virtualité, la ligne (2) du m a i n devrait appeler la fonction I n f o définie dans la classe I m p r i m a n t e * . M a i s hélas, à cause de l'héritage multiple, c'est la fonction-membre définie par la classe P é r i p h é r i q u e qui est appelée, à notre grand dam ! P o u r plus de clarté, je vais reprendre l'explication d'une autre manière. Quand une classe, ici P é r i p h é r i q u e , utilise l'héritage multiple, cela peut poser un problème d'ambiguité : il suffit que les classes de base héritées (ici R e s s o u r c e et O b j e t R e e l ) possèdent des fonctions homonymes (ici I n f o ) et toc, le compilateur C + + renonce au polymorphisme. Il appelle donc la fonction correspondant au type statique du pointeur (ici P é r i p h é r i q u e ) . O r , la souplesse de la virtualité réside justement dans cette espèce de flou permis au pro-
  • 116.
    L'héritage multiple 135 g ra m m e u r , qui p e u t utiliser des p o i n t e u r s d e classes p o u r a p p e l e r d e s fonctions virtuelles. L a b o n n e fonction étant app e l é e p e n d a n t l'exécution d u p r o g r a m m e , s e l o n l'objet réell e m e n t pointé. Héritage virtuel Il vaut mieux lire les paragraphes précédents avant de C o m m e n o u s l ' a v o n s v u p r é c é d e m m e n t , les d o n n é e s d ' u n e classe d e b a s e p e u v e n t être d u p l i q u é e s d a n s certains cas d ' h é r i t a g e multiple. M a l h e u r e u s e m e n t , tel n ' e s t p a s forcém e n t le désir du p r o g r a m m e u r . R e m é d i a n t à ce p r o b l è m e , l'héritage virtuel p e r m e t de n'avoir qu'une seule occurrence d e s d o n n é e s héritées d ' u n e classe d e b a s e . s'aventurer dans celui-ci. héritage multiple en diamant P o u r q u e la classe C ne p o s s è d e q u ' u n seul e x e m p l a i r e d e s d o n n é e s de la classe Base, il faut q u e les classes A et B héritent v i r t u e l l e m e n t de la classe Base. Voici l ' e x e m p l e C + + qui c o r r e s p o n d à cette explication (c'est le m ê m e e x e m p l e q u e le p a r a g r a p h e p r é c é d e n t , à l ' e x c e p t i o n des m o t s - c l é s virtual h a b i l e m e n t placés) : class Base { public : int variable_base; // blabla }; class A : virtual public Base { int variable_A; // blabla }; class B : virtual public Base { int variable_B; // blabla } ;
  • 117.
    136 Pont entre Cet C++ class C : public A, public B { int variable_C; // blabla } ; O b s e r v e z m a i n t e n a n t sur c e s c h é m a l'état d e l a m é m o i r e q u a n d un objet de classe C est créé : Voilà, il n ' y a p l u s q u ' u n e seule o c c u r r e n c e d e s d o n n é e s de B a s e . C o n t r a i r e m e n t a u p a r a g r a p h e p r é c é d e n t o ù v o u s étiez obligé de spécifier le « c h e m i n » de v o s d o n n é e s , v o u s n ' a v e z p l u s à v o u s en soucier ici. Illustrons cette j o v i a l e c o n c l u s i o n p a r un b o u t de p r o g r a m m e : void main() { C objet_c; ,objet_c.variable_base = 1 ; > // parfait !
  • 118.
    Les exceptions Le traitementdes erreurs est depuis toujours une vraie plaie pour les programmeurs. Le C++ nous propose le mécanisme des « exceptions » pour coordonner la lutte contre ces erreurs indésirables. Attention cependant : les exceptions sont encore peu répandues, tant au niveau des compilateurs que des programmeurs C++. C'est donc en quelque sorte un secteur « en chantier »... Notions de base L'idée U n p r o g r a m m e , m ê m e b i e n écrit, p e u t rencontrer d e s erreurs d ' e x é c u t i o n s : disque plein, saturation de la m é m o i r e , etc. C o m m e n t traiter ces p r o b l è m e s ? L e C + + r é p o n d s i m p l e m e n t par le c o n c e p t d'exception. Qu'est-ce qu'une « exception » ? U n e e x c e p t i o n est u n e variable, d e n ' i m p o r t e quel type, qui s y m b o l i s e u n e erreur. Q u a n d u n e partie d e p r o g r a m m e rencontre un p r o b l è m e , elle p e u t lancer u n e e x c e p t i o n , qui p o u r ra être interceptée p a r un autre b o u t de p r o g r a m m e . L'intérêt de ce s y s t è m e est de séparer la détection d'erreur d e s t r a i t e m e n t s associés. P a r e x e m p l e , q u a n d v o u s é c r i v e z u n e hiérarchie de classes, il est j u d i c i e u x de définir les e x -
  • 119.
    138 Pont entre Cet C++ ceptions q u e v o s classes p o u r r o n t lancer e n cas d e problème. De s o n côté, l'utilisateur de v o s classes aura c o n n a i s s a n c e de la liste de ces e x c e p t i o n s , et p o u r r a e n v i s a g e r différents trait e m e n t s p o u r é r a d i q u e r le mal. Mise en œuvre C++ A t t e n t i o n : les exceptions f o n t uniquement référence aux événements Utiliser des exceptions existantes P o u r ce faire, il faut b i e n distinguer d e u x b l o c s de c o d e : celui qui réalisera les opérations « d a n g e r e u s e s », c'est-à-dire susceptibles de générer des e x c e p t i o n s , et celui q u i traitera ces e x c e p t i o n s si elles surviennent. Le p r e m i e r bloc est précédé d u mot-clé t r y , l e s e c o n d d u mot-clé c a t c h . V o y o n s u n petit e x e m p l e : internes au programme, et n o n aux événements extérieurs c o m m e par exemple un clic souris ou l'appui sur une #include <iostream.h> #include <except.h> void main() { try t o u c h e du clavier. { int *p = new int[1E99]; // difficile ! cout << "Fin normale" << endl; } catch(xalloc) { cout << "Fin chaotique" << endl; L'exemple de xalloc fonctionne avec } ) T u r b o C + + sur PC. Consultez la documentation de // affichage : // Fin chaotique votre compilateur p o u r obtenir la liste des exceptions définies. E A l l o u e r 1 9 9 entiers est u n e opération délicate, en tout cas p o u r m o n ordinateur. Il faut savoir é g a l e m e n t q u ' e n cas d ' é c h e c d'allocation m é m o i r e , u n e e x c e p t i o n d e t y p e x a l l o c est g é n é r é e . A p r è s l e bloc t r y , n o u s détaillons u n bloc c a t c h qui intercepte j u s t e m e n t les e x c e p t i o n s x a l l o c . L ' e x é c u t i o n m o n t r e b i e n q u e l'exception a été g é n é r é e et traitée. F o n c t i o n n e m e n t de try et catch Q u a n d u n e e x c e p t i o n est détectée d a n s u n bloc t r y , l ' e x é c u t i o n de ce dernier est s t o p p é e , p u i s d é r o u t é e s u r le bloc c a t c h c o r r e s p o n d a n t . A l a fin d u bloc c a t c h , o n n e retourne pas d a n s le bloc t r y fautif. En r e v a n c h e , le pro-
  • 120.
    Les exceptions 139 g ra m m e suit son cours comme si le bloc t r y avait été exécuté. Q u a n d u n e e x c e p t i o n est r e n c o n t r é e , les d e s t r u c t e u r s d e s objets du bloc t r y sont appelés avant d'entrer d a n s le bloc catch. U n b l o c t r y doit o b l i g a t o i r e m e n t être suivi d ' a u m o i n s u n bloc c a t c h . Plusieurs b l o c s c a t c h p e u v e n t être décrits à la file, si chac u n i n t e r c e p t e u n type d ' e x c e p t i o n différent. S i a u c u n bloc c a t c h n'intercepte l'exception é m i s e , l a fonction t e r m i n a t e est appelée. Par défaut, elle fait appel à l a fonction a b o r t qui m e t fin a u p r o g r a m m e . C e p o i n t est a b o r d é p l u s e n détail d a n s les c o m p l é m e n t s d e ce chapitre. Précisions sur catch U n bloc c a t c h doit être p r é c é d é d ' u n bloc t r y . L a s y n t a x e d e c a t c h p e u t être l ' u n e des trois s u i v a n t e s : catch (Type) Intercepte les exceptions de type Type, ainsi que les exceptions de classes dérivées si Type est une classe. catch (Type obj) Idem que ci-dessus, mais lèxception est représentée catch (...) dans le bloc catch par un objet réel : obj. Intercepte toutes les exceptions non-traitées par les blocs catch précédents. Intercepter plusieurs exceptions I l suffit p o u r cela d'écrire plusieurs b l o c s c a t c h après l e bloc t r y . C o m m e n o u s l ' a v o n s v u d a n s l e tableau c i - d e s s u s , v o u s p o u v e z intercepter toutes les e x c e p t i o n s , s a n s distinction, e n utilisant c a t c h ( . . . ) . tinclude <iostreain.h> #include <except.h> void main() { try { // quelque chose qui va déclencher // Exceptionl, Exception2 ou une autre exception } catch(Exceptionl) {
  • 121.
    140 Pont entre Cet C++ cout << "Fin chaotique 1" << endl; } catch(Exception2) { cout << "Fin chaotique 2" << endl; } catch (...) { cout << "Fin très chaotique" << endl; } } Créer et lancer ses propres exceptions I n t e r c e p t e r les erreurs des a u t r e s , c'est b i e n , m a i s p r é v o i r les s i e n n e s , c'est m i e u x . N o u s a v o n s v u q u ' u n e e x c e p t i o n p o u v a i t être de n ' i m p o r t e q u e l type : u n e v a r i a b l e ou un objet. La p r e m i è r e é t a p e c o n s i s t e d o n c à créer les t y p e s c o r r e s p o n d a n t à v o s e x c e p t i o n s , c'est-à-dire a u x e r r e u r s q u e v o u s p o u r r e z d é c l e n c h e r . E n s u i t e , q u a n d v o u s é c r i v e z u n e fonction, v o u s p o u r r e z « lancer » u n e de v o s e x c e p t i o n s g r â c e au m o t - c l é throw: throw obj; throw; Lance l'exception obj. Relance la dernière exception lancée. throw s a n s p a r a m è t r e p e u t s'utiliser q u a n d v o u s n ' a r r i v e z p a s à r é s o u d r e le p r o b l è m e d a n s un b l o c catch. V o u s relancez donc la dernière exception, en espérant qu'il existe un autre b l o c catch d a n s la ou les fonctions a p p e l a n t e s . Exemple D a n s l ' e x e m p l e qui suit, la classe MaClasse va l a n c e r u n e e x c e p t i o n de classe MonErreur, dès q u e la fonction FonctionBoguee est a p p e l é e . #include <iostream.h> #include <except.h> // définissons une classe exception (en fait une classe // comme une autre) class MonErreur { public : MonErreur() {cout << "Constructeur de MonErreur" << endl;} };
  • 122.
    Les exceptions 141 // définissonsune classe qui va lancer des exceptions class MaClasse { public : void FonctionBoguee!); }; * Dans la ligne cicontre, MonErreurO crée un objet temporaire de classe MonErreur. C'est void MaClasse::FonctionBoguee() { // admettons que cette fonction soit boguée; // elle générera donc une exception : throw MonErreur(); // * voir dans la marge } void main() { try donc bel et bien un { MaClasse obj; objet qui est spécifié après t h r o w . obj.FonctionBoguee(); } catch (MonErreur) { cout << "Panique" << endl; } } // affichage : // Constructeur de MonErreur // Panique C e t exemple nous montre que l'exception générée dans F o n c t i o n B o g u e e , de la classe M a C l a s s e , est interceptée dans le m a i n par l'instruction c a t c h ( M o n E r r e u r ) . N o u s avons donc mis en évidence le fait que c'est le concepteur de la classe M a C l a s s e qui détecte les erreurs et lance une exception ; alors que c'est l'utilisateur de cette classe, ici la fonction main, qui envisage les solutions selon l'exception rencontrée. L'intérêt par rapport au C Le C ne p r o p o s e a u c u n s y s t è m e de g e s t i o n des erreurs. La p r a t i q u e la p l u s c o u r a n t e c o n s i s t e à r e n v o y e r u n e v a l e u r signifiant q u ' u n e erreur est i n t e r v e n u e . C e l a oblige à tester cette valeur à c h a q u e appel, ce qui alourdit et ralentit c o n s i d é r a b l e m e n t l e p r o g r a m m e . Par ailleurs, q u e faire q u a n d u n e telle erreur est détectée ? G r â c e a u x e x c e p t i o n s , les erreurs sont d e s objets à p a r t entière, d é c l e n c h a b l e s puis récupérables en c a s c a d e , du b l o c le
  • 123.
    142 Pont entre Cet C++ p l u s p r o c h e — en fait le p l u s c o m p é t e n t p o u r traiter l'erreur — a u x b l o c s les p l u s g é n é r a u x . M a i s p l u s e n c o r e , c'est u n e tentative de formaliser les t r a i t e m e n t s d'erreur, un c a d r e général qui p e r m e t a u x p r o g r a m m e u r s d e m i e u x s'y retrouver. Résumé L e C + + p r o p o s e d e gérer les erreurs p a r l e m é c a n i s m e d e s e x c e p t i o n s . U n e e x c e p t i o n n ' e s t rien d'autre q u ' u n e v a r i a b l e qui r e p r é s e n t e u n e erreur. T r o i s m o t s - c l é s p e r m e t t e n t d e les mettre en œuvre : t r y , c a t c h et t h r o w . Q u a n d une exception est l a n c é e p a r t h r o w q u e l q u e part d a n s u n b l o c t r y , l ' e x é c u t i o n de ce bloc est s t o p p é e et d é r o u t é e v e r s le b l o c c a t c h correspondant. S i aucun bloc c a t c h n'intercepte l'exception, l a fonction t e r m i n a t e est a p p e l é e (voir c o m p l é m e n t s ) . P a r défaut, elle m e t s o b r e m e n t fin a u p r o g r a m m e par un appel à a b o r t ( ). Exemple complet N o u s allons m a i n t e n a n t détailler u n c a s c o n c r e t d'utilisation d e s e x c e p t i o n s : u n e classe tableau qui n ' a c c e p t e p a s q u ' o n é c r i v e en d e h o r s de ses b o r n e s . #include <iostream.h> #include <except.h> class ErreurBorne { protected: int borne_fautive; public: ErreurBorne(int b) { borne_fautive = b; } int get_borne_fautive() { return borne_fautive; } } ; class Tableau { protected: enum { MAX = 10 }; // constante int tab[MAX]; public : int get_MAX() { return MAX; } int &operator[](int i ) ; } ;
  • 124.
    Les exceptions pour quel'utilisateur int&Tableau: :operator[] (int i) { if (i<0 || i>=MAX) throw ErreurBorne(i); else return tab[i]; puisse consulter mais 143 } * L'opérateur [ ] retourne ici une référence (un synonyme) du tableau aussi modifier sa valeur dans une expression du style t[n] = i. V o i r le // * voir dans la marge void main() { Tableau t; int i; chapitre sur les try { références, page I I 3. t[9] = 1; t[99] = 2; // instruction fautive i = t[9]; // ligne jamais exécutée } catch (ErreurBorne err) { cout << "Erreur de borne : " << err.get_borne_fautive() << endl; } } // affichage : // Erreur de borne : 99 Compléments Spécifier des exceptions V o u s p o u v e z i n d i q u e r a u c o m p i l a t e u r e t a u x futurs utilisateurs d e v o s classes quelles e x c e p t i o n s v o s fonctions s o n t susceptibles de lancer. P o u r ce faire, il faut n o m m e r c e s e x c e p t i o n s d a n s un throw, a p r è s l'entête n o r m a l . E x e m p l e : Attention : il faut que le throw soit spécifié dans l'entête de déclaration et dans void int void void fl(int a) throw (MonErreur); f 2 ( ) throw (MonErreur, MaCrainte); f3(char *s) throw(); // aucune exception f4(float f ) ; // toutes les exceptions l'entête de définition de la fonction. V o i l à la signification de ces trois lignes : f 1 ne p e u t lancer q u e d e s e x c e p t i o n s de classe MonErreur ou de c l a s s e s dériv é e s . f2 p e u t lancer des e x c e p t i o n s MonErreur ou Ma-
  • 125.
    144 Pont entre Cet C++ C r a i n t e ou de classes dérivées, f 3 ne p e u t l a n c e r a u c u n e exception, f 4 p e u t se p e r m e t t r e de lancer toutes les e x c e p tions, c a r a u c u n e limitation n ' e s t e x p r i m é e . Fonction unexpected S i , m a l g r é tout, u n e fonction lance u n e e x c e p t i o n qui ne fig u r e p a s d a n s l e t h r o w d e son entête, l a fonction u n e x p e c t e d (inattendue) est appelée. Par défaut, cette fonction fait appel à la fonction t e r m i n â t e qui fait e l l e - m ê m e appel à a b o r t . V o u s p o u v e z , s i v o u s l e désirez, r e m p l a c e r l a fonction u n e x p e c t e d p a r défaut p a r u n e fonction d e v o t r e cru. I l suffit d'utiliser s e t _ u n e x p e c t e d avec c o m m e seul param è t r e un n o m de fonction respectant l'entête s u i v a n t : PFV VotreNomDeFonction(void); où P F V est un type défini p a r : typedef void(*PFV) () ; Ce qui c o r r e s p o n d à un p o i n t e u r sur u n e fonction qui ne p r e n d a u c u n p a r a m è t r e e t qui r e t o u r n e v o i d . Exemple complet : #include <iostream.h> #include <except.h> // si PFV est inconnu, définissons-le #ifndef PFV typedef void( *PFV) (); #endif class MonErreur {}; class MonDilemne {}; class MaClasse { public : void FonctionBoguee)) throw(MonDilemne); }; void MaClasse::FonctionBoguee() throw(MonDilemne) { throw MonErreur(); //n'est pas autorisé par l'entête } PFV { Monlnconnue()
  • 126.
    Les exceptions 145 / /n e doit pas retourner à son appelant // doit sortir du programme cout << "Je suis dans l'inconnue..." << endl; exit(1); } void main() { try { MaClasse ob j ; set_unexpected((PFV)Monlnconnue); obj.FonctionBoguee(); } catch (MonErreur) { cout << "Panique" << endl; } } // affichage : / / J e suis dans l'inconnue Exception non interceptée Il p e u t arriver q u ' u n e e x c e p t i o n l a n c é e ne c o r r e s p o n d e à auc u n bloc c a t c h qui suit l e bloc t r y . D a n s c e c a s , l a fonction t e r m i n a t e est a p p e l é e . Par défaut, elle m e t fin a u p r o g r a m m e p a r u n appel à a b o r t ( ) . V o u s p o u v e z c e p e n d a n t définir v o t r e p r o p r e fonction terminate, à l'aide de s e t _ t e r m i n a t e . V o t r e fonction doit c o r r e s p o n d r e à l'entête suivant (où P F V est défini c o m m e i n d i q u é ci-dessus) : PFV VotreNomDeFonction(void); V o i c i u n e x e m p l e d e fonction t e r m i n a t e p e r s o n n e l l e : #include <iostream.h> #include <except.h> // si PFV est inconnu, définissons-le #ifndef PFV typedef void( *PFV ) (); #endif class MonErreur {}; class MonDilemne {}; class MaClasse { public : void FonctionBoguee() throw(MonErreur);
  • 127.
    146 Pont entre Cet C++ ) ; void MaClasse::FonctionBoguee() { throw MonErreur(); throw(MonErreur) } PFV MonTerminator() { // ne doit pas lancer d'exception // ne doit pas retourner à son appelant cout << "On dirait qu'il y a un problème" << endl; exit(1); } void main() { try { MaClasse obj; set„terminate((terminate_function)MonTerminator) ; obj.FonctionBoguee(); } catch (MonDilemne.) { cout << "Panique" << endl; } } // affichage : // On dirait qu'il y a un problème Exceptions en cascade Q u e s e passe-t-il q u a n d u n e e x c e p t i o n est l a n c é e d a n s u n b l o c catch ? R é p o n s e en d e u x t e m p s : Le b l o c A c o n t e n a n t le b l o c catch est s t o p p é . Si ce bloc A était l u i - m ê m e d a n s un bloc try, l ' e x c e p t i o n serait traitée p a r un d e s blocs catch c o r r e s p o n d a n t au bloc try qui contient A. L a b r u m e d e ces explications sera peut-être d i s s i p é e p a r l ' e x e m p l e s u i v a n t : u n e e x c e p t i o n de classe MonDilemne est l a n c é e d a n s un b l o c catch. #include <iostream.h> #include <except.h> class MonErreur { ) ; class MonDilemne {}; class MaClasse {
  • 128.
    Les exceptions public : voidFonctionBoguee() throw(MonErreur); } ; void MaClasse::FonctionBoguee() throw(MonErreur) { throw MonErreur(); // autorisé par l'entête } void fonction!) { try { MaClasse obj ; obj.FonctionBoguee(); } catch (MonErreur) { cout << "Erreur" << endl; throw MonDilemne(); } catch (MonDilemne) { // ce catch n'est pas appelé cout << "Dilemne dans fonction" << endl; } } void { try { main() fonction!); // déclenchera un MonDilemne } catch (MonDilemne) { // ce catch est appelé cout << "Dilemne dans le main" << endl; } } // affichage : // Erreur // Dilemne dans le main 147
  • 129.
    La compilation séparée Bienque la compilation séparée ne constitue pas une nouveauté du C++, un chapitre lui est consacré car beaucoup de programmeurs C en ignorent les principes. Par ailleurs, le C++ diffère légèrement du C dans ce domaine. Notions de base L'idée La c o m p i l a t i o n séparée p e r m e t de répartir le c o d e - s o u r c e d ' u n e application d a n s plusieurs fichiers. C e t t e t e c h n i q u e p r o c u r e u n e meilleure clarté d a n s l'organisation du projet. V o u s g a g n e z aussi du t e m p s : seuls les fichiers m o d i f i é s d e puis la dernière c o m p i l a t i o n sont r e c o m p i l é s . A v a n t d ' a b o r d e r la m i s e en œ u v r e de la c o m p i l a t i o n s é p a r é e , rappelons brièvement le fonctionnement d'un compilateur. P r i n c i p e d'un c o m p i l a t e u r D e u x g r a n d e s étapes sont n é c e s s a i r e s p o u r transformer votre c o d e - s o u r c e en e x é c u t a b l e : la c o m p i l a t i o n et l'édition de liens (parfois appelé « l i n k a g e » p a r certains fous qui refusent d ' e m p l o y e r les t e r m e s français officiels — mea culpa...). La compilation d ' u n fichier C ou C + + d o n n e un fichier objet,
  • 130.
    250 Pont entre Cet C++ U n e « librairie » est en quelque sorte un ensemble de fichiers .o que vous incluez à l'édition des liens. en général suffixe p a r . o. C e s fichiers . o sont e n s u i t e « liés » entre e u x , é v e n t u e l l e m e n t avec d e s librairies*, p o u r former l ' e x é c u t a b l e p r o p r e m e n t dit. D a n s l ' e x e m p l e ci-dessous, d e u x fichiers s o u r c e s suffixes p a r . c p p s o n t c o m p i l é s s é p a r é m e n t puis « linkés » a v e c u n e librairie i n d é p e n d a n t e p o u r d o n n e r l ' e x é c u t a b l e m a i n . e x e . S i , après u n e p r e m i è r e compilation, v o u s n e modifiez q u e l e fichier o u t i l s . c p p , v o u s n ' a u r e z b e s o i n d e r e c o m p i l e r q u e o u t i l s . c p p ; v o u s p o u r r e z réutiliser l'ancien m a i n . o p o u r l ' é d i t i o n des liens. Mise en œuvre P o u r réaliser effectivement u n e c o m p i l a t i o n s é p a r é e , il faut savoir d e u x c h o s e s : quoi m e t t r e , et d a n s quels fichiers c o m m e n t lancer la c o m p i l a t i o n Quoi mettre, et dans quels fichiers ? U n projet C + + s e d é c o m p o s e g é n é r a l e m e n t e n b e a u c o u p d ' é l é m e n t s de natures différentes : des c l a s s e s , d e s fonctions globales (hors-classes), des c o n s t a n t e s , d e s structures, etc. C o m m e n t savoir où placer c h a q u e é l é m e n t ?
  • 131.
    La compilation séparée Onparlera ici de fichiers .cpp pour désigner les fichiers contenant du codesource C + + . Votre compilateur utilise peut-être une autre extension (.cxx ou .C 151 Principe général D a n s la majorité d e s c a s , les fichiers fonctionnent p a r c o u ple : un fichier s u f f i x e p a r . h p o u r déclarer u n e classe ou d e s entêtes de fonctions, et un fichier s u f f i x e p a r . cpp p o u r la définition d e ces fonctions ( m e m b r e s o u n o n ) . A d m e t t o n s q u e v o u s écriviez d a n s un fichier A q u e l c o n q u e . Si v o u s a v e z b e s o i n d'utiliser u n e classe ou u n e fonction définie ailleurs, il faut inclure au d é b u t de A le fichier . h qui déclare ce d o n t v o u s a v e z b e s o i n . P o u r utiliser u n e autre i m a g e , le . h est l ' a m b a s s a d e u r de v o tre travail, il p r é s e n t e c o m m e n t utiliser ce q u e v o u s a v e z fait, alors q u e le . cpp décrit c o m m e n t c'est implanté. par exemple). Conseils La déclaration d ' u n e classe se fait g é n é r a l e m e n t d a n s un fichier . h qui p o r t e s o n n o m . La définition d e s f o n c t i o n s - m e m b r e s d ' u n e c l a s s e , ainsi q u e la définition des c o n s t a n t e s et variables de classe (static) se fait d a n s un fichier . cpp p o r t a n t le n o m de la classe. L e s objets, variables ou fonctions globales (c'est-à-dire accessibles d e p u i s n ' i m p o r t e quelle fonction d u p r o g r a m m e ) sont définis d a n s un fichier d o n t le n o m r e s s e m b l e à glo- bal . cpp La déclaration de ces objets g l o b a u x se fait d a n s un fichier d o n t le n o m r e s s e m b l e à extern.h, où c h a c u n de ces objets est p r é c é d é du m o t - c l é extern. L e s fonctions hors-classe, C standard, figurent d a n s d e s fichiers . c La déclaration de ces fonctions se fait d a n s des fichiers . h Si v o u s utilisez de n o m b r e u s e s classes, v o u s g a g n e r e z à reg r o u p e r plusieurs classes d a n s un seul fichier . h. Il est cep e n d a n t conseillé de g a r d e r un seul fichier . cpp p a r classe — les corps des fonctions p r e n n e n t en effet plus de p l a c e q u e les déclarations de classes. V o u s trouverez ci-dessous u n s c h é m a qui r é s u m e ces c o n seils à travers un e x e m p l e . L e s listings c o m p l e t s sont situés u n p e u plus loin d a n s c e chapitre.
  • 132.
    252 Pont entre Cet C++ Compilation séparée - exemple Remarques A v e c l a c o m p i l a t i o n séparée, l e p r o g r a m m e u r est contraint de respecter certaines règles : C h a q u e fichier . c p p utilisant d e s é l é m e n t s d ' u n autre fichier doit inclure le fichier . h déclarant les é l é m e n t s en question. Ici, m a i n . c p p inclut les fichiers e x t e r n . h , f o n c _ c . h e t C l a s s i . h , car i l v a utiliser d e s variables e t c o n s t a n t e s
  • 133.
    La compilation séparée 153 globalesdéclarées d a n s e x t e r n . h , des fonctions C déclarées d a n s f o n c _ c . h , e t des classes d é c l a r é e s d a n s Classl . h . Q u a n d v o u s modifiez l'entête d ' u n e fonction d a n s u n fichier . cpp, il faut veiller à mettre à j o u r le fichier . h correspondant. L ' o r d r e d'inclusion des fichiers . h est i m p o r t a n t . S i , p a r e x e m p l e , v o u s utilisez un objet global de classe Classl déclaré d a n s e x t e r n , h, il faut q u e p a r t o u t Classl. h soit inclus a v a n t e x t e r n . h (sinon l e t y p e Classl sera inconnu dans e x t e r n . h). Listing Voici le listing qui détaille l'architecture p r é s e n t é e d a n s le s c h é m a de la p a g e p r é c é d e n t e : // fichier main.cpp #include "extern.h" extern "C" { // voir compléments #include "fonc_c.h" } #include "Classl.h" void main() { Classl objetl; _statut = 0; } // fichier global.cpp // constantes globales const float PI = 3.1415; // variables globales int _statut; Cette manière d'opérer (avec #ifndef) est expliquée dans les compléments, page I 57. // fichier extern.h tifndef _EXTERN_H #define _EXTERN_H // constantes globales extern const float // variables globales extern int _statut; PI;
  • 134.
    754 Pont entre Cet C++ #endif // fichier Classl.h #ifndef _CLASS1_H #define _CLASS1_H class Classl { protected: // ... public : Classl ( ) ; void FoncMemb ( ) ; } ; #endif // fichier Classl.cpp #include "Classl.h" // constructeurs Classl::Classl() { } // autres fonctions-membre de Classl void Classl::FoncMemb() { } // fichier fonc_c.h #ifndef _FONC_C_H #define _FONC_C_H #define CONST_C void 100 fonction_c(int a ) ; #endif // fichier fonc c.c #include "fonc_c.h" int fonction_c(int a) { return a; }
  • 135.
    La compilation séparée Comment lancerla compilation ? 155 L e s m é t h o d e s d e c o m p i l a t i o n varient b e a u c o u p selon les s y s t è m e s . C e r t a i n s c o m p i l a t e u r s s ' o c c u p e n t de p r e s q u e tout : il v o u s suffit d'indiquer quels fichiers font partie de v o t r e p r o jet. D ' a u t r e s en r e v a n c h e , r e p o s e n t sur l'utilitaire m a k e . Make C e t utilitaire v o u s p e r m e t lancer u n e c o m p i l a t i o n s é p a r é e d ' u n e seule instruction. P o u r cela, il est n é c e s s a i r e d'écrire un script de compilation, a p p e l é makefile. Ce script idendifie les fichiers à c o m p i l e r , ainsi q u e les d é p e n d e n c e s entre ces fichiers. Si v o t r e makefile est écrit c o r r e c t e m e n t , seuls les fichiers n é c e s s a i r e s seront r e c o m p i l é s . Le format d ' u n e paire de lignes d ' u n fichier makefile est le suivant (ici, les crochets i n d i q u e n t des é l é m e n t s o p t i o n n e l s ) : fichier_cible : fichier_l [fichier_2 ...] instruction à exécuter Si une ligne de votre Makefile est t r o p longue, vous pouvez la couper en deux à condition que la première ligne se termine par un backslash (). C e qui signifie : p o u r obtenir f i c h i e r _ c i b l e , j ' a i b e s o i n d e f i c h i e r _ l , f i c h i e r _ 2 , etc. e t d ' e x é c u t e r l'instruction de la d e u x i è m e ligne. M a k e en déduit q u e si l'un d e s fichiers f i c h i e r _ l , f i c h i e r _ 2 , etc. est plus récent (au n i v e a u d e s dates d e dernière modification) q u e f i c h i e r _ c i b l e , i l faut e x é c u t e r l'instruction de la d e u x i è m e ligne. Ce format sert p r i n c i p a l e m e n t à d e u x g e n r e s d'instructions (on s u p p o s e r a q u e v o t r e c o m p i l a t e u r C + + s'appelle CC) : • c o m p i l e r un . c p o u r obtenir un . o fichier.o : fichier.c fichier.h autre_fichier.h CC -c fichier.c C e s lignes signifient q u e s i f i c h i e r . c , f i c h i e r . h o u a u t r e _ f i c h i e r . h ont été modifiés à u n e date p o s t é r i e u r e d e celle d e f i c h i e r . o , i l faut r e c o m p i l e r f i c h i e r . c p o u r o b tenir un n o u v e a u f i c h i e r , o. L ' o r d r e de c o m p i l a t i o n dép e n d b i e n e n t e n d u d e votre c o m p i l a t e u r . En fait, on i n d i q u e après les : le n o m du fichier s o u r c e c o n cerné, ainsi q u e la liste d e s fichiers . h qu'il utilise.
  • 136.
    156 Pont entre Cet C++ • lier les . o et les librairies p o u r former l ' e x é c u t a b l e exécutable : fichier.o autre_fichier.o lib.a CC -o exécutable fichier.o autre_fichier.o -llib.a C e qui v e u t d i r e : s i f i c h i e r , o , a u t r e _ f i c h i e r . o o u l i b . a s o n t p l u s r é c e n t s q u e e x é c u t a b l e , i l faut refaire u n e édition d e s liens. N o u s utilisons ici u n e librairie à titre d ' e x e m p l e ; il est tout à fait p o s s i b l e q u e v o u s n ' e n n ' a y e z p a s b e s o i n . Il faudrait d a n s ce cas écrire : exécutable : fichier.o autre_fichier.o CC -o exécutable fichier.o autre_fichier.o Exemple Voici l'allure du fichier makefile l ' e x e m p l e p r é s e n t é p a g e 152. permettant de compiler # fichier makefile appli : main.o global.o classl.o fonc_c.o CC -o appli main.o global.o classl.o fonc_c.o main.o : main.cpp extern.h fonc_c.h classl.h CC -c main.cpp global.o : global.cpp CC -c global.cpp classl.o : classl.cpp classl.h CC -c global.cpp fonc_c.o : fonc_c.c fonc_c.h CC -c fonc_c.c L a n c e r la c o m p i l a t i o n (enfin!) A s s u r e z - v o u s q u e le fichier makefile est d a n s le r é p e r t o i r e où sont situés les fichiers-sources de v o t r e projet, et t a p e z make -f nom_fichier_makefile Si v o u s n o m m e z le fichier makefile M a k e f i l e (avec un M m a j u s c u l e ) , il v o u s suffira de taper m a k e p o u r lancer la c o m p i l a t i o n s é p a r é e de votre projet.
  • 137.
    La compilation séparée 157 No t e z q u e certains s y s t è m e s a c c e p t e n t i n d i f f é r e m m e n t m a k e f i l e o u M a k e f i l e (avec o u s a n s m a j u s c u l e ) , d o n n a n t l a préférence au fichier qui c o m m e n c e p a r u n e m a j u s c u l e si les d e u x sont p r é s e n t s d a n s le répertoire courant. Compléments Comment éviter les redéclarations ? L e c o m p i l a t e u r n ' a i m e p a s les déclarations m u l t i p l e s d ' u n m ê m e é l é m e n t (objet, variable, c o n s t a n t e , fonction, etc.) Il faut d o n c veiller à ne p a s inclure d e u x fois le m ê m e fichier . h d a n s le m ê m e fichier. C e l a arrive facilement d a n s un c a s de t y p e « p o u p é e s r u s s e s » : E n traitant a p p l i . c p p , l e p r é - p r o c e s s e u r r e m p l a c e l a ligne #include " e x t e r n . h " p a r l e c o n t e n u d u fichier e x tern.h. On obtient d o n c deux lignes #include " toto . h", qui inclueront d e u x fois le fichier toto . h, ce qui va g é n é r e r des erreurs de redéclaration. Il existe un m o y e n s i m p l e d'éviter ces r e d é c l a r a t i o n s , tout en p e r m e t t a n t l'inclusion m u l t i p l e du m ê m e . h : utiliser les directives d e p r é c o m p i l a t i o n # i f n d e f , # d e f i n e e t # e n d i f . Le truc consiste à ne p r e n d r e en c o m p t e le c o n t e n u du . h q u e la p r e m i è r e fois q u e le p r é c o m p i l a t e u r le r e n c o n t r e . Voici un fichier . h qui utilise cette t e c h n i q u e : // début du fichier extern.h #ifndef _EXTERN_H #define EXTERN_H // corps complet du fichier #endif // fin du fichier extern.h
  • 138.
    158 Pont entre Cet C++ En fait, on associe à c h a q u e fichier . h un s y m b o l e p o r t a n t s o n n o m (ici _EXTERN_H). A U début, c e s y m b o l e n ' e x i s t e pas. La p r e m i è r e fois q u e le p r é c o m p i l a t e u r traite le fichier, il r e n c o n t r e la ligne #ifndef _EXTERN_H. C o m m e le s y m b o l e _EXTERN_H n ' e x i s t e p a s , le p r é c o m p i l a t e u r traite le fichier j u s q u ' à rencontrer u n # e n d i f (ou u n #elif d'ailleurs). Le p r é - p r o c e s s e u r traite d o n c tout le c o r p s du fichier j u s q u ' à n o t r e #endif final. D a n s cette partie d e c o d e prise e n c o m p t e , l a p r e m i è r e ligne est # de fi ne _EXTERN_H, qui définit le symbole _EXTERN_H, ce qui servira au p r o c h a i n p a s s a g e à r e n d r e la c o n d i t i o n # i f n d e f _EXTERN_H fausse, et à éviter d'inclure u n e n o u v e l l e fois l e fichier e x t e r n . h . Static, inline et portée * C'est-à-dire en dehors de t o u t e fonction Le g a i n de t e m p s et de clarté n ' e s t p a s le seul intérêt de la c o m p i l a t i o n s é p a r é e . L a d é c o m p o s i t i o n e n p l u s i e u r s fichiers a aussi d e s c o n s é q u e n c e s sur la p o r t é e de certaines v a r i a b l e s et fonctions : Static global T o u t e fonction déclarée s t a t i c ainsi q u e toute v a r i a b l e déclarée s t a t i c e n global*, n ' e s t accessible q u e d a n s l e fichier où elle est définie. E x e m p l e : PORTÉE DE VAR ET FONCI On ne p e u t a c c é d e r ni à var, ni à foncl d e p u i s f i c h 2 . cpp, car ils ont été déclarés static d a n s f i c h l . c p p . Ils ne sont a c c e s s i b l e s q u ' à l'intérieur de f ichl. cpp.
  • 139.
    La compilation séparée 159 Attention! Ici, s t a t i c n ' a p a s l e m ê m e sens q u e lorsqu'il qualifie les v a r i a b l e s - m e m b r e s ou f o n c t i o n s - m e m b r e s ! R a p p e l o n s q u e d a n s ces derniers c a s , s t a t i c signifie qu'il s'agit de variables ou fonctions de classe, c o m m u n e s à tous les o b jets issus de la classe. F o n c t i o n s inlines S i v o u s déclarez u n e fonction i n l i n e e n d e h o r s d ' u n e classe, la p o r t é e de celle-ci est a u t o m a t i q u e m e n t r é d u i t e au fichier d a n s lequel elle est déclarée. Constantes globales U n e c o n s t a n t e déclarée en global n ' e s t visible q u e d a n s le fichier où elle est définie, à m o i n s de spécifier e x p l i c i t e m e n t c o n s t d a n s l a décla'ration externe. E x e m p l e 1, où PI est déclaré l o c a l e m e n t à f i c h l . c p p : E x e m p l e 2, où PI est visible à la fois d a n s f i c h l . c p p et fich2.cpp :
  • 140.
    7 60 Pont entreC et C++ Travailler avec d'autres langages L e C + + p e r m e t l ' u s a g e d e fonctions p r o v e n a n t d ' a u t r e s lang a g e s : le C b i e n e n t e n d u , m a i s aussi le P a s c a l , le Fortran, etc. C o m m e c h a q u e l a n g a g e a d o p t e s a p r o p r e l o g i q u e d'édition d e s liens ( n o t a m m e n t e n c e qui c o n c e r n e l a m a n i è r e d e stocker les p a r a m è t r e s de fonctions sur la pile d ' e x é c u t i o n ) , il faut l'indiquer d ' u n e m a n i è r e ou d ' u n e autre d a n s le c o d e C + + . V o u s p o u v e z l e faire grâce à l a c l a u s e e x t e r n " n o m " , où nom représente un type d'édition de liens ( c o n s u l t e z la d o c u m e n t a t i o n d e votre c o m p i l a t e u r ) . Il existe trois m a n i è r e s d'utiliser e x t e r n "nom" : extern "nom" élément; déclare que l'édition des liens de élément (un entête de fonction ou une variable) se fait selon la convention nom. extern "nom"{ bloc de déclarations; Idem que ci-dessus, mais tous les éléments déclarés dans bloc sont concernés. } extern "nom" { #include "fichier.h" tions faites dans fichier.h sont concernées. Idem que ci-dessus, mais toutes les déclara- ) Prenons l'exemple d'un programme C + + ayant besoin de fonctions C. On utilise e x t e r n " C " : // fichier sériai.h (C standard) int int serial_output(int, unsigned char); serial_input(int, unsigned char*); // fichier main.cpp (C++) possibilité 1 extern "C" int serial_output(int, unsigned char); extern "C" int serial_input(int, unsigned char*); void main { /*...*/ } Utilisons la d e u x i è m e possibilité p o u r déclarer les d e u x fonctions sans répéter e x t e r n "C" : // fichier main.cpp (C++) possibilité 2 extern "C" { int int } serial_output(int, unsigned char); serial_input(int, unsigned char*);
  • 141.
    La compilation séparée 161 voidmain { /*...*/ } Enfin, a d o p t o n s la dernière solution, la p l u s é l é g a n t e , la p l u s é c o n o m i q u e , bref, la m e i l l e u r e d a n s n o t r e e x e m p l e : // fichier main.cpp (C++) possibilité 3 extern "C" { #include "sériai.h" } void main { /*...*/ } U t i l i s e r du c o d e C + + à partir du C P o u r utiliser d u c o d e C + + à partir d ' u n c o d e C , s u i v e z l'exemple suivant : // fichier C extern "C" double racine(double d) { // code C++ Math monObjetMath; return monObjetMath.racine(d); } void main() { double res; res = racine(22.); }
  • 142.
    Guide de survie Les conceptsprésentés dans les deux premières parties vous laissent peut-être perplexe : comment utiliser un tel arsenal pour venir à bout d'un problème réel ? Cette partie expose quelques conseils de base pour concevoir une application en C++, ainsi qu'une série de questions/réponses sur le langage.
  • 143.
    Conseils Ce chapitre abordequelques conseils de programmation orientéeobjets en C++. Il ne s'agit pas d'énoncer des règles d'or, mais plutôt de donner quelques pistes pour débuter. Par la suite, l'expérience aidant, vous pourrez concevoir et coder dans un style qui ne vous sera dicté par personne... Les étapes d'un projet U n projet i n f o r m a t i q u e p e u t s e d é c o m p o s e r e n q u a t r e p h a s e s grossières : 1. 2. 3. 4. D é t e r m i n a t i o n du cahier des c h a r g e s fonctionnel Conception Codage Maintenance M a l g r é tout le soin p o u r les séparer, ces p h a s e s ont t e n d a n c e à se m é l a n g e r de m a n i è r e subtile. C ' e s t p o u r q u o i les tentativ e s d e formalisation d e l a p r o g r a m m a t i o n ont s o u v e n t é c h o u é , car il s'agit là d ' u n e activité p r o f o n d é m e n t h u m a i n e , v o i r e artistique.
  • 144.
    166 Pont entre Cet C++ L a p r e m i è r e p h a s e consiste à d é t e r m i n e r c e q u e v o u s (ou v o s clients) a t t e n d e n t de l'application à d é v e l o p p e r . Il est en effet capital d e n e j a m a i s p e r d r e d e v u e les b e s o i n s a u x q u e l s l e p r o g r a m m e doit r é p o n d r e . M a l h e u r e u s e m e n t (ou h e u r e u s e m e n t ) , c e s b e s o i n s s o n t susceptibles d ' é v o l u e r tout a u l o n g d u projet, m e t t a n t e n c a u s e c e qui était c o n s i d é r é c o m m e acquis d è s l e début. N o u s n e n o u s p e n c h e r o n s p a s p l u s sur cette p h a s e qui n e d é p e n d p a s d u C + + . A t t a r d o n s - n o u s m a i n t e n a n t sur l a c o n c e p t i o n p r o p r e m e n t dite. Conception La c o n c e p t i o n orientée-objets consiste e s s e n t i e l l e m e n t à trouver les classes qui m o d é l i s e n t v o t r e p r o b l è m e et les relations qui se tissent entre elles. Trouver les classes C ' e s t u n e p h a s e essentielle. Pourtant, il est difficile de d o n n e r d e s c o n s e i l s g é n é r a u x tant les solutions d é p e n d e n t d u p r o b l è m e . O n isole p l u s facilement les classes q u a n d o n p r e s s e n t (du v e r b e pressentir) les relations qui les u n i r o n t ; aussi la lecture de la section suivante pourra-t-elle v o u s aider. Q u e l q u e s r e m a r q u e s : u n e classe p e u t être v u e c o m m e u n e entité, u n e idée a u n i v e a u d e votre projet. R a p p e l o n s q u ' u n e classe est un tout, formé de d o n n é e s et de fonctions qui traitent ces d o n n é e s . P o u r créer u n e c l a s s e , il faut d o n c q u e d e s liens étroits existent entre ses c o m p o s a n t s . Si cela n ' e s t p a s le cas, il se p e u t q u ' e l l e se d é c o m p o s e en s o u s - c l a s s e s . Un autre m o y e n consiste à formuler le p r o b l è m e en français. V o u s a u r e z alors d e b o n n e s c h a n c e s p o u r q u e les n o m s c o m m u n s r e p r é s e n t e n t des objets, e t q u e les v e r b e s s y m b o l i sent d e s f o n c t i o n s - m e m b r e s (c'est l a m é t h o d e d e B o o c h ) . À ce n i v e a u de c o n c e p t i o n , ne p r e n e z p a s en c o m p t e les classes liées à l ' i m p l é m e n t a t i o n , m a i s c o n c e n t r e z - v o u s sur celles qui r é p r é s e n t e n t d i r e c t e m e n t la réalité du p r o b l è m e . Inutile,
  • 145.
    Conseils 167 p a re x e m p l e , de p e n s e r à des listes ou des tableaux à ce niveau. Architecturer les classes C ' e s t p r a t i q u e m e n t la p h a s e la p l u s difficile. Il existe princip a l e m e n t quatre sortes de relations entre d e u x c l a s s e s A et B: 1. 2. 3. 4. A A A A est u n e sorte de B est c o m p o s é de B utilise B n ' a a u c u n lien avec B (eh oui!) La différence entre les points d e u x et trois n ' e s t p a s toujours é v i d e n t e , m a i s n o u s allons voir cela d a n s les p a r a g r a p h e s qui suivent. N o u s ne détaillerons p a s le dernier point, d o n t le rôle est s i m p l e m e n t d e v o u s rappeler q u ' i l n e faut p a s a b s o l u m e n t s ' a c h a r n e r à trouver un lien entre d e u x c l a s s e s . A est u n e s o r t e de B Il s'agit d ' u n e relation d'héritage. La f o r m u l a t i o n « est u n e sorte de » fonctionne très b i e n p o u r l'identifier. Si m a l g r é tout cela ne suffit p a s , il faut p e n s e r q u e la classe d é r i v é e (ici A) p e u t être u n e spécialisation de la classe de b a s e . D a n s certains cas, u n e relation n e p e u t p a s s e m o d é l i s e r s o u s forme « est u n e sorte de », b i e n q u ' i n t u i t i v e m e n t v o u s sentiez q u ' i l existe u n e relation d'héritage entre les d e u x . Il est alors p o s s i b l e q u e les d e u x entités soient « s œ u r s », c'est-àdire q u ' e l l e s aient des p o i n t s c o m m u n s et héritent toutes d e u x d ' u n e m ê m e classe d e b a s e . Exemple Q u e l l e s sont les relations entre camion et voiture ? U n e v o i t u r e « est-elle u n e sorte » de c a m i o n ou est-ce l'inverse ? D a n s ce c a s , s e l o n l e p r o b l è m e , o n p e u t p e n c h e r p l u t ô t p o u r l a solution « un c a m i o n est u n e sorte de v o i t u r e », car les v o i t u r e s s o n t p l u s fréquentes, et elles ont été i n v e n t é e s a v a n t les c a m i o n s . L e s c a m i o n s sont d o n c u n e spécialisation d e s v o i t u res. On p e u t aussi créer u n e classe véhicule, ou v é h i c u l e terrestre, qui servira de classe de b a s e à v o i t u r e et c a m i o n .
  • 146.
    168 Pont entre Cet C++ A est c o m p o s é de un ou p l u s i e u r s B C e t t e relation r e p r é s e n t e u n objet qui p e u t s e d é c o m p o s e r e n d'autres objets, qui p o s s è d e n t c h a c u n leur vie p r o p r e . D e tels objets B sont déclarés c o m m e d o n n é e s - m e m b r e s de l'objet principal A . Exemple U n e v o i t u r e est c o m p o s é e d ' u n m o t e u r e t d e q u a t r e p n e u s . C e m o t e u r e t ces p n e u s sont des objets qui p e u v e n t e x i s t e r indépendamment. C e n ' e s t v i s i b l e m e n t p a s u n e relation d ' h é r i t a g e , car u n p n e u (ou un m o t e u r ) n ' e s t p a s « u n e sorte de » v o i t u r e . A utilise B Il existe d e u x m a n i è r e s de représenter cette relation en t e r m e de l a n g a g e orienté-objet. Soit l'objet B est défini c o m m e donn é e - m e m b r e de l'objet A, soit A utilise d e s objets B g l o b a u x o u p a s s é s c o m m e p a r a m è t r e s d e fonctions. P a r r a p p o r t à la relation p r é c é d e n t e (A est c o m p o s é de B ) , la n u a n c e se situe au n i v e a u du s e n s : ici, les objets B ne font
  • 147.
    Conseils 169 p a spartie d e l'objet A . Ils sont c o m p l è t e m e n t i n d é p e n d a n t s , m a i s s o n t utilisés p a r A p o u r r é s o u d r e un p r o b l è m e . Exemple Dans un environnement graphique, le gestionnaire d ' é v é n e m e n t s utilise les informations en p r o v e n a n c e de la souris. Il n ' e s t ni c o m p o s é d ' u n e souris, p a s p l u s qu'il n ' e s t l u i - m ê m e u n e sorte d e souris. B i e n e n t e n d u , en t e r m e d ' i m p l é m e n t a t i o n , il se p e u t q u e la classe g e s t i o n n a i r e d ' é v é n e m e n t ait c o m m e d o n n é e - m e m b r e un objet de classe souris. M a i s il se p e u t é g a l e m e n t qu'il c o m m u n i q u e a u t r e m e n t avec l'objet souris, p a r e x e m p l e par l'intermédiaire d ' u n e autre classe qui centralise les entrées. Détailler les classes L e s classes sont c o n n u e s et leurs relations identifiées. Il faut m a i n t e n a n t détailler les f o n c t i o n s - m e m b r e s q u ' e l l e s contiennent. Ce qu'il est b o n de g a r d e r à l'esprit : M i n i m i s e r les é c h a n g e s e n t r e les c l a s s e s P l u s v o s classes sont i n d é p e n d a n t e s , m o i n s elles é c h a n g e n t d'informations a v e c les autres, et p l u s facile sera la m a i n t e n a n c e d e v o t r e projet. R é d u i s e z autant q u e p o s s i b l e l e n o m bre de p a r a m è t r e s d a n s les f o n c t i o n s - m e m b r e s . En dévoiler un m i n i m u m D a n s l e m ê m e ordre d'idées, u n e classe doit s'efforcer d e cac h e r un m a x i m u m de ses p r o p r e s d o n n é e s , et surtout la manière d o n t ces d o n n é e s sont s t o c k é e s . I m a g i n e z toujours ce qui se passerait si v o u s c h a n g i e z la m a n i è r e de r e p r é s e n t e r ces d o n n é e s . L'interface avec l'extérieur, c'est-à-dire les fonctions p u b l i c , n e doit p a s c h a n g e r — e n tout c a s aussi p e u que possible.
  • 148.
    2 70 Pont entreC et C++ Évaluer l'ensemble C o n f r o n t e z plusieurs scénarios à votre architecture de class e s , e t vérifiez q u e tout p e u t être m i s e n œ u v r e . V o u s g a g n e rez à p a s s e r en r e v u e les fonctionnalités a t t e n d u e s ( p h a s e 1). Si v o t r e c o n c e p t i o n s'avère trop c o m p l e x e à utiliser, r e m e t tez-là en c a u s e et e s s a y e r d ' e n i m a g i n e r u n e autre. Il faut savoir q u e s i v o u s a v e z des p r o b l è m e s d e c o m p r é h e n s i o n d e l'architecture à ce n i v e a u , ils ne feront q u e s'amplifier à l'étape suivante. Codage C++ * C'est une façon de parler. Quels outils ? V o t r e projet est détaillé sur le papier, il ne reste plus* q u ' à le p r o g r a m m e r . A v e c l e C + + , v o u s d i s p o s e z d e n o m b r e u x outils d o n t l'utilisation s i m u l t a n é e n ' e s t p a s toujours é v i d e n t e . Ce p a r a g r a p h e va tenter de v o u s faciliter la tâche. C e t t e é t a p e consiste à choisir les classes-outils ou les bibliot h è q u e s q u e v o u s utiliserez p o u r m e t t r e e n œ u v r e v o t r e c o n ception. C e n e s o n t p a s d e s classes c o n c e p t u e l l e s , m a i s d e s classes p r o p r e s a u x t e c h n i q u e s d e p r o g r a m m a t i o n . Par e x e m p l e , toutes les classes « c o n t e n e u r » (tableaux, listes, arb r e s , g r a p h e s ) en font partie. Elles servent à s t o k e r d'autres objets. Si v o u s d e v e z c o n c e v o i r d e s classes-outils p o u r votre projet, p e n s e z à la réutilisabilité, et faites en sorte q u ' e l l e s soient aussi universelles q u e possible. V o u s a u r e z peut-être p l u s d e m a l , m a i s v o u s g a g n e r e z d u t e m p s sur v o t r e p r o c h a i n projet. B i e n e n t e n d u , c e g e n r e d e classe-outils n é c e s s i t e u n e d o c u m e n t a t i o n : c o m m e n t e z - l e s ou décrivez leur f o n c t i o n n e m e n t à part, si p o s s i b l e a v e c des e x e m p l e s d'utilisation. Factoriser des classes R e p r e n e z votre g r a p h e d'héritage. V o u s p o u v e z e s s a y e r d e trouver d e s p o i n t s c o m m u n s entre c l a s s e s , s u f f i s a m m e n t n o m b r e u x p o u r former u n e classe d e b a s e . L ' a v a n t a g e d ' u n e
  • 149.
    Conseils 171 telle factorisation résided a n s la m i n i m i s a t i o n d e s r e d o n d a n c e s . D o n c , les modifications éventuelles n ' a u r o n t à se faire q u ' à un endroit si elles c o n c e r n e n t la classe de b a s e . C e t t e é t a p e ne figure p a s d a n s la c o n c e p t i o n , car il s'agit là d ' u n e m é t h o d e d e p r o g r a m m a t i o n qui n ' a p a s f o r c é m e n t d e s e n s conceptuel. * Rappelons que le C+ + fart la conversion de Dérivée* vers Base*. Mise en œuvre de la réutilisabilité Exemple Vous concevez un environnement graphique composé de divers objets : fenêtres, b o u t o n s , etc. Si c h a q u e objet c o m p o r t e u n identifiant, u n e référence vers l'objet p a r e n t , e t j e n e sais q u o i d'autre, il e s t j u d i c i e u x de c r é e r u n e classe ObjetG e n e r i q u e qui r e g r o u p e r a tous ces p o i n t s c o m m u n s . Ici n o u s bénéficions d ' u n autre atout : tous les objets fenêtres, b o u tons et autres p o u r r o n t êtres d é s i g n é s p a r un p o i n t e u r s u r ObjetGenerique*. L ' u n d e s c h e v a u x d e bataille d e s l a n g a g e s orientés-objets est l a réutilisabilité d u c o d e produit. L e C + + est p a r t i c u l i è r e m e n t b i e n a r m é p o u r m e n e r à b i e n cette croisade. O u t r e l'héritage q u e n o u s a v o n s détaillé d a n s la partie c o n c e p t i o n , p a r l o n s ici du p o l y m o r p h i s m e , de la généricité et des e x c e p t i o n s . L e p o l y m o r p h i s m e e t les f o n c t i o n s v i r t u e l l e s U n e fonction virtuelle doit garder le m ê m e s e n s d a n s toute la b r a n c h e d'héritage. U n e x e m p l e parfait serait u n e fonction d e rotation virtuelle définie p o u r un objet g r a p h i q u e de b a s e . U n b o n m o y e n d ' i m p o s e r u n m o u l e p o u r u n e série d e c l a s s e s consiste à créer u n e classe de b a s e abstraite, où des fonctions virtuelles p u r e s sont déclarées. C e g e n r e d e c l a s s e fixe u n c a dre général d'utilisation : les classes dérivées d o i v e n t définir les fonctions virtuelles p u r e s , en r e s p e c t a n t leur entête. N o t r e e x e m p l e s'appliquerait b i e n à cela : u n e classe abstraite ObjetVirtuel, qui ne créerait d o n c a u c u n objet, m a i s qui définirait les entêtes des fonctions affichage, rotation, etc. On i m a g i n e facilement q u e des classes R e c t a n g l e o u C e r c l e héritent d'ObjetVirtuel (voir s c h é m a p a g e suivante).
  • 150.
    172 Pont entre Cet C++ La généricité et les templates Les templates permettent de paramétrer des classes par des types de d o n n é e s , ces derniers p o u v a n t être d'autres classes. L e s c o n t e n e u r s (les classes de s t o c k a g e ) sont tout i n d i q u é s p o u r utiliser les templates. U n b o n exercice serait d e c o n c e voir u n e classe Liste template, qui accepterait c o m m e param è t r e le type des objets q u ' e l l e stocke. Les exceptions Q u a n d v o u s c o n c e v e z u n e classe, v o u s n e s a v e z p a s forcém e n t qui v a l'utiliser, n i q u a n d o n v a l'utiliser. E n a d o p t a n t l e m é c a n i s m e d ' e x c e p t i o n , v o u s p o u v e z signaler des erreurs ( t h r o w ) à l'utilisateur de la classe qui agira en c o n s é q u e n c e s ( t r y / c a t c h ) . D o c u m e n t e z e t signalez c l a i r e m e n t les c l a s s e s d ' e x c e p t i o n s q u e v o u s p o u v e z lancer. L ' u n d e s m o y e n s c o n siste à faire suivre l'entête d ' u n e fonction p a r les e x c e p t i o n s q u ' e l l e est susceptible de lancer (voir p a g e 1 4 3 ) . Implantation des classes E n c o n c e v a n t e t c o d a n t u n e classe C + + , i l est intéressant d e c o n s i d é r e r q u e l q u e s points : Respectez la cohérence des fonctions d'accès U n e classe c o m p t e très s o u v e n t d e s fonctions d ' a c c è s p o u r consulter ou modifier d e s d o n n é e s - m e m b r e s (à m o i n s q u e
  • 151.
    Conseils 173 v o us n ' a y e z déclaré des d o n n é e s - m e m b r e s public, m a i s v o u s devriez écarter d ' e m b l é e cette possibilité). P e n s e z à garder u n e c o h é r e n c e p o u r distinguer ces fonctions d e s a u tres. Faites p a r e x e m p l e p r é c é d e r les fonctions de c o n s u l t a tion p a r g e t _ e t les fonctions d e modification p a r s e t _ , e n les faisant suivre du libellé exact des d o n n é e s - m e m b r e s . Prat i q u e m e n t tous les e x e m p l e s de ce livre r e s p e c t e n t ce format. R e n d e z « const » les fonctions qui ne m o d i f i e n t rien S i u n e f o n c t i o n - m e m b r e d ' u n e classe n e modifie a u c u n e d o n n é e - m e m b r e , p e n s e z à l a r e n d r e c o n s t . V o u s garantissez ainsi a u x utilisateurs de votre classe q u e l'objet ne sera p a s modifié en faisant appel à cette fonction. Tous les exemples de ce livre ne respectent pas cette convention, par souci d'allégement et de clarté. class Awesome { protected: int a; public : int get_a() const { return a; }; } ; Préférez protected à private R a p p e l o n s q u e des d o n n é e s private ne s o n t p a s héritées. L e s d o n n é e s protected, en r e v a n c h e , s o n t accessibles a u x classes dérivées m a i s restent inaccessibles a u x autres c l a s s e s . A u s s i est-il g é n é r a l e m e n t plus intéressant d'utiliser pro- tected. Respectez la symétrie des constructeurs et destructeurs Si un c o n s t r u c t e u r alloue de la m é m o i r e ou d e s r e s s o u r c e s diverses, faites en sorte q u e le destructeur les libère. Attention : r a p p e l e z - v o u s q u e la s y n t a x e de delete est différente p o u r u n e s i m p l e variable (delete v a r ) q u e p o u r u n ta- b l e a u (delete [ ] t a b ) . I n i t i a l i s e z t o u t e s les d o n n é e s - m e m b r e s , d a n s c h a q u e c o n s tructeur Il n ' y a rien de p l u s d é s a g r é a b l e q u e de confier la tâche d'initialisation à un c o n s t r u c t e u r irresponsable. Le rôle du
  • 152.
    Ï74 Pont entre Cet C++ c o n s t r u c t e u r est de p r é p a r e r un n o u v e l objet, et ce rôle c o m p r e n d son initialisation. F a u t - i l r e d é f i n i r le c o n s t r u c t e u r c o p i e ? Il suffit q u ' u n e d o n n é e - m e m b r e soit un p o i n t e u r et v o u s aurez c e r t a i n e m e n t b e s o i n d u c o n s t r u c t e u r c o p i e . C e dernier s e c h a r g e d'initialiser un n o u v e l objet à partir d ' u n objet de m ê m e classe déjà existant. R a p p e l o n s l e format d ' u n c o n s tructeur c o p i e : NomClasse::NomClasse(const NomClass &a_copier) {/*...*/} L e c o n s t n ' e s t p a s obligatoire, m a i s fortement conseillé : v o u s viendrait-il à l'idée de modifier l'objet à c o p i e r ? Faut-il r e d é f i n i r l ' o p é r a t e u r d ' a f f e c t a t i o n = ? Si l ' u n e d e s d o n n é e s - m e m b r e s est un pointeur, redéfinissez l'opérateur = p o u r v o u s a s s u r e r q u e les é l é m e n t s d é s i g n é s p a r le p o i n t e u r s o n t b i e n r e c o p i é s . A t t e n t i o n : l ' o p é r a t e u r = n ' e s t p a s hérité p a r les sous-classes ! F a u t - i l r e d é f i n i r d e s o p é r a t e u r s de c o m p a r a i s o n ? D a n s d e n o m b r e u x c a s , i l v a u t m i e u x q u e v o u s redéfinissiez l'opérateur de c o m p a r a i s o n = = . Si v o u s êtes a m e n é s à trier d e s objets de votre classe, redéfinissez é g a l e m e n t < ou >, car o n n ' e s t j a m a i s m i e u x servi q u e p a r s o i - m ê m e . V o u s s a u r e z ce s u r quoi la c o m p a r a i s o n s'effectue : a d r e s s e d e s objets, ou b i e n v a l e u r d ' u n identifiant, o u e n c o r e é q u i v a l e n c e l o g i q u e , etc.
  • 153.
    Questions-Réponses Si vous n'avezpas trouvé les réponses à vos questions dans les chapitres précédents ou dans l'index, tentez votre chance ici. Même si vous ne vous posez pas de question, il peut être instructif de lire les réponses... Q u ' e s t - c e q u ' u n c o n s t r u c t e u r par défaut ? C ' e s t u n c o n s t r u c t e u r qui n e p r e n d a u c u n p a r a m è t r e , o u d o n t les p a r a m è t r e s p o s s è d e n t tous u n e v a l e u r p a r défaut. L e C + + g é n è r e u n c o n s t r u c t e u r p a r défaut s i v o u s n ' a v e z défini a u c u n constructeur. Un c o n s t r u c t e u r p a r défaut est appelé « en silence » p o u r les objets qui ne s o n t p a s e x p l i c i t e m e n t initialisés. Il est é g a l e m e n t a p p e l é q u a n d v o u s allouez un n o u v e l objet a v e c new obj ; ou un tableau a v e c new obj [TAILLE] ;. C o m m e n t appeler un constructeur d'une classe de base dans le constructeur d'une classe dérivée Utilisez u n e liste d'initialisation (plus de détails p a g e 4 7 ) . class Dérivée : public Base { public : Dérivée ( ) : B a s e O { /* ... */ } } ;
  • 154.
    î 76 Pont entreC et C++ J ' a i r e d é f i n i l ' o p é r a t e u r = m a i s il n ' e s t pas a p p e l é d a n s la d é c l a r a t i o n Classe obj2 = objl; Q u ' a i - j e fait de m a l ? R i e n . D a n s l ' e x e m p l e cité, o b j 2 est initialisé avec un autre objet ( o b j 1 ) . D a n s c e c a s , c e n ' e s t p a s l'opérateur = q u i est a p p e l é , m a i s l e c o n s t r u c t e u r copie. L e rôle d e c e dernier c o n siste p r é c i s é m e n t à initialiser un objet à partir d ' u n autre o b jet existant. Il s'agit ici d ' u n e initialisation de variable, et n o n d ' u n e affectation, qui serait réalisée p a r l'opérateur -. P o u r q u o i l ' o p é r a t e u r « doit-il être d é c l a r é f r i e n d ? Il ne doit p a s forcément être déclaré f r i e n d , m a i s cela facilite les c h o s e s . Q u e l q u e s explications : l'opérateur « s ' a d r e s s e à l'objet c o u t d e l a classe o s t r e a m . C o m m e n o u s n e p o u v o n s p a s ajouter notre o p é r a t e u r « d i r e c t e m e n t d a n s la classe o s t r e a m , il faut surcharger l'opérateur « global. O r , ce dernier n ' a p a s accès a u x d o n n é e s c a c h é e s d e notre classe. D e u x solutions s'offrent à n o u s : déclarer n o t r e o p é r a t e u r « f r i e n d d a n s notre classe, o u n e p a s l e faire [ha h a ] . D a n s c e dernier c a s , l e c o r p s d e l'opérateur < < d e v r a s e c o n t e n t e r d'utiliser les fonctions p u b l i q u e s de notre classe. Voici comment se passer du f r i e n d : #include <iostream.h> class NomClasse { private: int n; public : NomClasse(int i) : n(i) {) int get_n() const { return n; } // vous voyez, on se passe du friend : // friend ostream &operator<< // (ostream& out, const NomClasse& obj); } ; // nous devons utiliser get_n() au lieu de n : ostream &operator<< (ostreamk out, const NomClassek obj) { return out << '[' << obj.get_n() << ' ] ' << endl; } void main() { NomClasse obj1(1), obj2(2);
  • 155.
    Questions-Réponses 177 cout << objl<< obj 2; } // affichage: // [1] // [2] P o u r q u o i u t i l i s e r cout et c i n au l i e u de p r i n t f et s c a n f ? E n utilisant c o u t , c i n , c e r r e t les autres objets définis d a n s i o s t r e a m . h , v o u s bénéficiez d e plusieurs a v a n t a g e s : 1. V o u s n ' a v e z p a s b e s o i n de préciser le t y p e d e s objets q u e v o u s utilisez (plus de % d o n c m o i n s d'erreurs p o s s i b l e s ) , car c o u t , c i n e t c e r r intègrent les vérifications d e type ! 2. V o u s p o u v e z redéfinir « et » p o u r qu'ils traitent v o s classes. D e cette m a n i è r e , elles s'utiliseront c o m m e les typ e s prédéfinis, ce qui est un g a g e de c o h é r e n c e . 3. printf et s c a n f sont c o n n u s p o u r leur lenteur, d u e à l ' a n a l y s e s y n t a x i q u e qu'ils sont obligés d ' o p é r e r sur la c h a î n e d e format. R a s s u r e z - v o u s , c o u t e t ses frères s o n t plus rapides. 4 . Utiliser c o u t a u lieu d e p r i n t f est plus chic. Q u e l est l'intérêt d e d é c l a r e r d e s o b j e t s a u b e a u m i l i e u d'une fonction plutôt qu'au début ? P e r s o n n e l l e m e n t , je préfère déclarer tous les objets au d é b u t d ' u n e fonction. C e l a évite de parcourir tout le c o d e p o u r trouver un objet précis. M a i s les déclarations tardives ont leur c h a r m e : elles p e u v e n t accélérer le p r o g r a m m e . En effet, la création d ' u n objet est c o û t e u s e p u i s q u ' i l faut allouer la m é m o i r e e t appeler l e constructeur. I m a g i n e z u n e fonction c o m p o r t a n t un bloc A e x é c u t é u n e fois sur cent. Si A utilise dix objets, v o u s a u r e z intérêt à les déclarer d a n s A plutôt q u ' a u d é b u t de la fonction. C e l a dit, c'est u n e q u e s t i o n de g o û t p e r s o n n e l . C o m m e n t faire p o u r a l l o u e r n octects a v e c n e w ? char *pointeur; pointeur = new char[n]; // alloue n octets
  • 156.
    1 78 Pont entreC et C++ Dois-je b a n i r friend de ma religion ? O u i , autant q u e possible. N ' u t i l i s e z f r i e n d q u e s i v o u s n e p o u v e z p a s raisonnablement faire a u t r e m e n t . P a r e x e m p l e , i m a g i n o n s q u ' u n e classe p r é c i s e (et u n e seule) a b e s o i n d ' a c c é d e r a u x d o n n é e s c a c h é e s d ' u n e autre classe. D a n s c e cas d e figure, f r i e n d garantit q u e seule l a c l a s s e v o u l u e p e u t a c c é d e r a u x d o n n é e s c a c h é e s . C e l a évite d ' a v o i r à déclarer d e s fonctions d'accès « p u b l i q u e s » qui ne serviraient q u ' à u n e seule autre classe, et q u e les autres d e v r a i e n t i g n o rer. Q u e s i g n i f i e c o n s t d a n s u n e d é f i n i t i o n du s t y l e void f() const {I* ... */ i ? L e m o t clé c o n s t , inséré ici entre les p a r a m è t r e s d ' u n e fonction et s o n corps, signifie q u e cette fonction ne modifie p a s les d o n n é e s - m e m b r e s de la classe à laquelle elle appartient : class Awesome { protected: int a ; public : int get_a() const { return a; }; }; D a n s cet e x e m p l e , la fonction g e t _ a ( ) ne modifie p a s la d o n n é e - m e m b r e a . Elle peut d o n c être déclarée c o n s t . Q u e l l e est la capitale du K a z a k h s t a n ? Alma-Ata. P o u r q u o i d i a b l e n ' a r r i v é - j e pas à a c c é d e r a u x d o n n é e s de ma c l a s s e de b a s e d e p u i s ma c l a s s e d é r i v é e ? V o u s avez peut-être déclaré ces d o n n é e s p r i v a t e a u lieu d e p r o t e c t e d . R a p p e l o n s q u e les d o n n é e s p r i v a t e n e sont p a s accessibles d a n s les classes dérivées. Il se p e u t é g a l e m e n t q u e votre héritage soit p r i v a t e o u p r o t e c t e d a u lieu d e p u b l i c . V o y e z l e chapitre sur l'héritage, s p é c i a l e m e n t page 39.
  • 157.
    Index Symboles « surcharger 92 surcharger 57 » surcharger92 [] comment allouer n octets? 177 new et delete 83 amies classes et fonctions 123 B base classe de... 38 bases du C + + 11 C surcharger 142 A accès aux membres dans un héritage 39 affectation opérateur= 57 affichage cout, cin et cerr 89 formater 94 surcharger « 92 allocation mémoire catch 138 cerr 89 surcharger « 92 cin 89 surcharger » 92 classe 13 abstraite de base 179 amie 123 comment les architecturer? 167 comment les trouver? 166 conseils d'implantation 172
  • 158.
    182 Pont entre Cet C++ conversion de pointeurs 104 de base 38 déclaration 25 définir 15 dérivée 38 différences avec les objets 14 template 119 commentaires 63 ruse de sioux 64 compilateur principes de base 149 compilation séparée 149 conseils 151 make 155 conception conseils 166 conseils de codage 170 de conception 166 constantes 67 et compilation séparée 159 paramètres 70 propres à une classe 71 constructeurs 27 copie 31 et héritage 43 ordre d'appel dans un héritage multiple 129 par défaut 175 vue d'ensemble 27 conversion constructeur 35 de classe vers un type 61 de type vers une classe 35 dérivée* vers base* 42; 104 copie constructeur 31 par défaut 58 cout 89 surcharger « 92 tableau récapitulatif 99 D dec 97 déclaration de classe 25 n'importe où 73 définir classe 15 fonction-membre 16 delete 83 syntaxe 84 démarrer en C + + 20 dérivée classe... 38 destructeurs 27 et héritage 44 virtuels 106 vue d'ensemble 27 E encapsulation 14 erreurs gestion des... 137 exceptions 137 créer ses propres... 140 en boucle 146 intercepter plusieurs... 139 non interceptée 145 spécifier dans l'entête 143 extern "nom" 160 F fichiers séparation en plusieurs... 149 fonctions amies 123 définir fonction-membre 16 inline 21 redéfinition de fonction-membre 41 retournant une référence 115 spécifier les exceptions dans un entête 143 templates 118 virtuelles 101 virtuelles pures 107
  • 159.
    Index formater les sorties94 friend classes et fonctions 123 et o p é r a t e u r « 176 et religion 178 G généricité 117 guide de survie 163 H héritage accès aux membres 39 conseils 167 conversion de pointeurs 104 et constructeurs 43 et destructeurs 44 exemple complet 45 multiple 127 multiple, duplication de données 131 multiple, masquage de la virtualité 133 résumé 42; 78 simple 37 virtuel 135 hex 97 I implantation conseils 172 initialisation listes 47 inline 21 iomanip.h 94 ios::fixed 98 ios::left 95 ios::right 95 ios::scientific 98 ios::showbase 96 ios::showpoint 96 ios::showpos 96 iostream.h Voir cout et cin 183 L langages travailler avec d'autres... 160 listes d'initialisations 47 M make 155 malloc la fin du... 83 modèles Voir templates N new 83 syntaxe 84 notation scientifique 97 O objet accéder à la partie public 17 créer 17 différences avec les classes 14 pourquoi comparer deux objets identiques 61 provisoire et références 115 oct 97 opérateurs Voir Symboles, au début de l'index de conversion de classe vers un type 61 surcharge (vue d'ensemble) 54 P paramètres par défaut 65 passage par référence 113 patrons Voir templates pointeurs sur classe de base 104 polymorphisme 101 printf la fin du... 89 protected 40 conseils 173
  • 160.
    184 Pont entre Cet C++ d'opérateurs 54 de l'opérateur « 92 de l'opérateur = 57 de l'opérateur » 93 de l'opérateur [] 142 Q questions 175 R redéclarations comment éviter les... 157 redéfinition d'une fonction-membre 41 références 113 et objets provisoires 115 retournées par une fonction 115 résumé 1ère partie 75 réutilisabilité 171 S set_terminate 145 set_unexpected 144 setfill 97 setiosflags 95 setprecision 95 setw 94 sioux ruse de 64 static 24 portée 158 surcharge 51 T template ambiguïté 122 templates 117 terminate 145 this 23 pourquoi retourner *this? 60 throw 140 try 138 U unexpected 144 V virtualité 101 destructeurs 106 héritage virtuel 135 masquage dans un héritage multiple 133 pure 107