Académique Documents
Professionnel Documents
Culture Documents
Notes au lecteur LES BASES DU C++ 1) Les objets et les classes Notions de base L'ide Mise en uvre C + + ' L'intrt par rapport au C Rsum Pour bien dmarrer en C + + Complments Fonctions inline Le mot-cl this lments static dans une classe Dclaration de classe anticipe 2) Les c o n s t r u c t e u r s et les destructeurs Les notions de base L'ide Mise en uvre C + + constructeurs Mise en uvre C + + destructeurs Complments Constructeur copie Constructeurs de conversion 3) L'hritage simple Notions de base L'ide Mise en uvre C + + Redfinition d'une fonction-membre Conversion de Drive* vers Base* Rsum
13 13 13 14 19 20 20 21 21 23 24 25 27 27 27 28 30 32 32 36 37 37 37 38 41 42 43
Pont entre C et C + +
L'intrt par rapport au C Complments Hritage et constructeurs Hritage et destructeurs Exemple complet Listes d'initialisations 4) La surcharge Notions de base L'ide Mise en uvre C + + L'intrt par rapport au C Complments Surcharge d'oprateurs Surcharge de l'oprateur = Oprateurs de conversion de types 5) Les petits + du C++ Les commentaires Ruse de sioux Paramtres par dfaut Les constantes Dclarations RSUM Encapsulation Constructeurs & destructeurs Hritage Surcharge ENCORE PLUS DE C++ 6) La fin du malloc : new et delete Notions de base L'ide Mise en uvre C + + L'intrt par rapport au C 7) La f i n du printf : cout, cin et cerr Notions de base L'ide Mise en uvre C + + L'intrt par rapport au C Complments Surcharger et
43 44 44 44 45 47 51 51 51 52 53 54 54 57 61 63 63 64 65 67 73 75 77 77 78 79
83 83 83 84 86 89 90 90 90 91 93 93
Formater les sorties Tableau rcapitulatif 8) Le p o l y m o r p h i s m e et la virtualit Notions de base L'ide Mise en uvre C + + Rsum L'intrt par rapport au C Complments Destructeurs virtuels Classes abstraites et fonctions virtuelles pures 9) Les rfrences Notions de base L'ide Mise en uvre C + + L'intrt par rapport au C Complments Rfrences et objets provisoires Fonction retournant une rfrence 10) Les templates ou la mise en uvre de la gnricit Notions de base L'ide Mise en uvre C + + Templates de fonctions Templates de classes Complments Attention l'ambigut 11) Les classes et f o n c t i o n s amies Notions de base L'ide Mise en uvre C + + Attention ! 12) L'hritage m u l t i p l e Notions de base L'ide Mise en uvre C + + L'intrt par rapport au C Complments Duplication de donnes d'une classe de base Masquage du polymorphisme
95 100 102 102 102 104 105 105 107 107 108 114 114 114 115 115 116 116 117 118 119 119 119 119 121 124 124 125 126 126 126 127 130 130 130 131 133 134 134 136
Hritage virtuel 13) Les e x c e p t i o n s Notions de base L'ide Mise en uvre C + + L'intrt par rapport au C Rsum Exemple complet Complments Spcifier des exceptions Exception non intercepte Exceptions en cascade 14) La c o m p i l a t i o n spare Notions de base L'ide Mise en uvre Quoi mettre, et dans quels fichiers ? Comment lancer la compilation ? Complments Comment viter les redclarations ? Static, inline et porte Travailler avec d'autres langages GUIDE DE SURVIE 15) Conseils Les tapes d'un projet Conception Trouver les classes Architecturer les classes Dtailler les classes valuer l'ensemble Codage C + + Quels outils ? Mise en uvre de la rutilisabilit Implantation des classes 16) Questions-Rponses Index
138 140 140 140 141 145 145 145 147 147 148 150 153 153 153 154 155 159 161 161 162 164
169 169 170 170 171 173 174 174 174 175 177 179 185
16
d o n n e s d ' u n objet, donnes-membres. P a r opposition, les autres d o n n e s et fonctions sont qualifies 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 dfinir la classe L i v r e :
class Livre { private : char char char public : void void void
} ;
Le C + + n'aime pas forcer la main des programmeurs. Il est possible de dfinir des donnes public, c'est-dire accessibles directement de l'extrieur de l'objet C'est une pratique dconseiller, car elle viole l'ide m m e d'encapsulation.
C o m m e n t dfinir 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 dclarer 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 dfinir 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 prs : il faut indiquer au compilateur quelle classe est rattache u n e fonction. Le format de dfinition d'une fonction-membre (c'est--dire inclue dans u n e classe) est le suivant :
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);
}
17
void
{
Livre::Imprimer()
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 crer 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 dclarer une variable en C :
Nom_Classe nom_objet;
B i e n e n t e n d u , toutes les formes de dclaration du C s o n t acceptes : 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 dclarer un objet, il reste dcouvrir 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'accs 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 accdiez u n e d o n n e d ' u n e structure classique du l a n g a g e C :
18
main() bouquin[10]; i;
Livre int
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. Prcision i m p o r t a n t e sur l'accs 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 prcision s ' i m p o s e : u n e fonction m e m b r e d ' u n e classe A peut accder 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 accd 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();
} ;
19
void
{
A::fonction_a() autre_a; objet_b; // // // // OK : on reste dans cet objet OK : autre_a est de classe A NON : b est private dans une autre classe
A B
a = 1; autre_a.a = 1 ; objet_b.b = 1;
}
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'intrt de rais o n n e r e n objets plutt 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 scurit. 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 contrler l'intgrit 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 conue*, v o u s c o m p r e n e z r a p i d e m e n t le rle 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 rattache 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 dv 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 trs gros p r o g r a m m e s . 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, aprs 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 prilleuse, v o u s p e r d e z le bnfice 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 rgles 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 caractristiques 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
Rsum
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 orients-objets : l'encapsulation. L e C + + p e r m e t d e crer 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 donnes 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'intrieur de l'objet, partir de ses fonctions p r o p r e s . P o u r crer u n objet e n C + + , i l faut d ' a b o r d dfinir 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 crs c o m m e des variables classiques, partir de leur classe. Les d o n n e s et fonctions dfinies d a n s u n e classe s o n t a p p e les respectivement donnes 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.
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 droutera peut-tre a u dbut. 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 plutt 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 rflexe 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 agrable. 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 rel plaisir et b e a u c o u p d ' a v a n t a g e s .
21
Complments
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 dsirez e n connatre 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'excution 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 dfinie 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 trs 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
} ;
C o m m e v o u s p o u v e z le voir, cette classe dclare trois fonctions p u b l i q u e s , qui ne sont e n c o r e dfinies nulle part. En thorie, il faudrait dfinir 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 dfinir c o m p l t e m e n t u n e fonction l'intrieur de la dfinition de sa classe. Ainsi, au lieu de dfinir part d e s fonctions trs
22
nbr;
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 dfini complt 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 troisime, A f f i c h e r , n ' e s t q u e dclare n o t e z l e pointv i r g u l e aprs la dclaration. Il faudra la dfinir 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 dfinit u n e fonction inline, il est inutile d'accoler le n o m de la classe et les caractres : : a v a n t le n o m de la fonction, p u i s q u ' e l l e est dfinie d a n s le c o r p s de sa classe. Le mot-cl inline Il existe un autre m o y e n de bnficier du m c a n i s m e inline, sans dfinir 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 tte de la dfinition de la fonction. Q u a n d v o u s spcifiez 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-cls 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 dclaration de la classe UnNombre, il aurait fallu crire :
inline
{
Ce systme est efficace si vous utilisez la compilation spare. N o u s dtaillerons cela au chapitre 14.
void
UnNombre: : Afficher()
23
La dclaration 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 rle 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 dclare d i r e c t e m e n t d a n s la classe. Rsum L e C + + p e r m e t a u p r o g r a m m e u r d e dfinir 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 rsulte un g a i n de t e m p s l'excution. On rserve c e p e n d a n t cet a v a n t a g e a u x fonctions trs 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 .
Le mot-cl this
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 spciale : 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 ( ) ;
) ;
// 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 dduirez q u e ( * t h i s ) reprsente 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 oprateurs, o u p o u r tester l'galit de d e u x objets (voir p a g e 6 1 ) .
24
On ne peut pas utiliser le mot-cl this dans une fonction static, puisque celle-ci ne dpend pas d'un objet prcis.
V o u s p o u v e z dclarer 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 spcifiques 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 dernires doivent tre dclares 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 dclaration d e classe, sans rpter 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 dclares s t a t i c d o i v e n t tre a p p e les 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 rfrence un objet prcis. 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 donne; public : Flam() : donne(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; // dclarons 3 objets
// affichage // 0 // 3
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 (dclare static au s e n s C + + ) . En crant 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 diffrence 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 dclarer 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 .
T o u t c o m m e v o u s p o u v e z dclarer u n e structure un endroit et la dfinir 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 dclaration 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 dfinie :
class B; // dclaration anticipe
Les constructeurs
et les destructeurs
La vie et la mort d'un objet C++ sont rgies et les destructeurs. Les fonctions constructeurs cration d'un objet, les fonctions destructeurs qu'il sort de sa porte de visibilit, c'est--dire
28
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 dclar, 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 dfini, son destructeur est appel automatiquement.
C o m m e n t dfinir 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;
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 dclarer 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 personnel 311
)
void
{
main()
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
{
Equipage Equipage }
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 dclarez 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 surchargs Il est tout fait p o s s i b l e de dfinir 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 dfinissons trois c o n s t r u c t e u r s :
class Equipage { private : int
personnel;
// 1 // 2 // 3
30
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 dclar 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 paramtre. 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 dfaut 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 dfaut se b o r n e librer 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 dsirez faire u n e o p r a t i o n particulire p o u r dtruire 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 librer l'espace m m o i r e , v o u s p o u v e z dfinir 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 caractres :
#include <stdio.h> #include <string.h> class Capitaine
31
private : char *nom; public : // constructeur Capitaine) char *nom_initial ); // destructeur -Capitaine();
} ;
Complments
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
void
fonction_interessante(Beta beta)
void
main()
32
Beta
fonction_interessante(premier);
}
* Dans ce cas, l'oprateur = 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 dfaut 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 dfaut 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 mm o i r e . Et ce serait fcheux ! 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 .
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, aprs u n e c o p i e p r o p r e .
C o m m e n t dfinir 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 rfrences sont traites au chapitre 9, page I 13.
tre est u n e rfrence* 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 efforons-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> class Gogol { private: char public : // constructeur normal Gogol(char * c ) ; // constructeur de copie Gogol(const Gogol &a_copier); *pt;
*cout est un mot cl C++ utilis pour l'affichage de caractres. Voir chaptre 7, page 89.
// pour cout*
34
// accs aux donnes membre void set_pt(char * c ) ; char *get_pt(); // autres fonctions void Afficher();:
>;
Il faut vrifier que l'objet source et l'objet destination sont diffrents, car s'ils taient identiques, delete pt effacerait la fois les pointeurs source et destination !
// effaons l'ancien pt delete pt; // allouons de la place pour le nouveau pt pt = new char [strlen(a_copier.pt) + 1 ] ; // donnons une valeur au nouveau pt strcpy(pt, a_copier.pt);
} }
void Gogol::Afficher()
{
void {
maint) Gogol gog("Zut"), bis = gog; // appel du constructeur copie // Zut // Zut
gog.Afficher() ; bis.Afficher();
35
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 diffrentes. 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 dfaut 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 dtruire le p o i n t e u r de gog aurait g a l e m e n t dtruit celui de bis p a r effet de b o r d . Inutile de prciser 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'opration rciproque (convertir un objet de votre classe vers une variable de type T), allez voir p a g e 61 o l'on vous 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 spcifie 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 crant 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 numro; char nom [50] ; public :
// ...
Prisonnier(int n) : numro(n) { cout << "Conversion de " << n << endl; nom[0] = 0 ; }
} ;
void {
fonction(Prisonnier p)
// . . .
36
void {
main()
Prisonnier
p = 2;
// donne p = Prisonnier(2)
L'hritage simple
L'hritage vous permet de modliser le monde rel avec souplesse, d'organiser vos classes et de rutiliser celles qui existent dj. C'est un concept fondamental en programmation oriente-objets. Nous parlons ici d'hritage simple, par opposition l'hritage multiple qui sera dvelopp dans la deuxime partie du livre.
Notions de base
L'ide 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 rutiliser des p o r t i o n s d e c o d e qu'ils ont dj crit p o u r des projets antrieurs. G r c e l'hritage, u n e part du r v e devient ralit : v o u s p o u v e z profiter des classes dj existantes p o u r en crer d'autres. M a i s l o l'hritage est intressant, 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'hritage s'avre trs 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 diffrent l g r e m e n t .
38
* N o u s aurions pu parler de la classificat i o n des cornichons, des jeunes filles ou des flippers, mais un exemple classique est sans doute mieux adapt un large public.
Le principe Le p r i n c i p e initial de l'hritage est p r o c h e de celui de la classification en catgories : on part du c o n c e p t le p l u s gnral p o u r se spcialiser 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 vhicules*, et e s s a y o n s de tisser des liens d'hritage 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 :
Le sens des flches d'hritage pourrait tre : p o s s d e les caractristiques de ou est un , au s e n s large du terme. Ainsi, voiture est un vhicule. De m m e p o u r le bateau. Le camion, lui, p o s s d e les caractritiques de voiture et, indirectem e n t , de vhicule. O n dit q u e v o i t u r e hrite de v h i c u l e . D e m m e , bateau hrite de v h i c u l e , et c a m i o n de voiture. D ' u n cas gnral, r e p r s e n t par vhicule, n o u s a v o n s tir d e u x cas particuliers : voiture et bateau. Camion est lui-mme un cas particulier de voiture, car il en p o s s d e grosso modo toutes les caractristiques, et en ajoute d'autres q u i lui sont propres.
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 hrite d ' u n e classe B, on dit q u e A est la classe de base et B la classe drive. 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 + + :
class Vhicule { protected : int // ...
nombre_de_places;
L'hritage simple
39
Remarque : nous n'avons pas dtaill ces classes afin de nous concentrer sur le mcanisme d'hritage. Vous retrouverez les classes compltes en fin de chapitre.
protected : int chevaux_fiscaux; // . . . public : // constructeurs Voiture(); // fonctions de traitement void CalculVignette();
} ;
* Rappelons que la spcification d'accs est dtermine par le mot-cl qui suit les deux-points (:) aprs la ligne class
NomClasseDrive.
40
A u t r e m e n t dit, le mot-cl protected est identique private si vous n'utilisez pas l'hritage.
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 drive. 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 spcificateur d ' a c c s : p r o t e c t e d . C e spcificateur est i d e n t i q u e p r i v a t e l'intrieur de la classe. Il diffre u n i q u e m e n t en cas d'hritage, 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 drives. 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 drivation :
class Base { private : int protected : int public : int
} ;
class Deriveel : public Base { // detective_prive est inaccessible ici // acces_protege est considr comme protected // domaine_public est considr comme public
} ;
class Derivee2 : protected Base { // detective_prive est inaccessible ici // acces_protege est considr comme protected // domaine_public est considr comme protected
>;
class { // // //
};
Derivee2 : private Base detective_prive est inaccessible ici acces_protege est considr comme private domaine_public est considr comme private
L'hritage simple
41
Ces exemples amnent 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 drives L a spcification 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 drive est e n fait l a spcification 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'hritage est p u b l i c , elle devient p r o t e c t e d d a n s la classe drive. E n rgle gnrale, o n utilise l'hritage p u b l i c p o u r m o dliser des relations du m o n d e rel . V o i r le chapitre 15 sur les conseils.
V o u s p o u v e z dfinir des fonctions a u x enttes i d e n t i q u e s d a n s diffrentes classes a p p a r e n t e s p a r u n e relation d'hritage. 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 drive p e u t d o n c avoir le m m e n o m (et les mmes 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 dfinissons d e u x fonctions a u x enttes identiques (mais au c o r p s diffrent) :
#include <stdio.h> class TexteBrut public : void void
Imprimer(int nb) ;
TexteBrut::Imprimer(int nb)
// hritage
Imprimer(int n b ) ;
TexteMisEnForme::Imprimer(int nb)
42
txt; joli_txt;
txt.Imprimer( 1 ) ; joli_txt.Imprimer(1);
}
D a n s un cas o u n e classe Drive hrite 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 Drive 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 Drive : public Base { }; void main() { Base *base; Drive *derivee; base = drive;
}
// OK
L'hritage 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 recopie 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 spcification 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, le C est c o m p l t e m e n t battu. G r c e l'hritage, v o u s allez enfin p o u v o i r rutiliser facilement le c o d e q u e v o u s a v e z dj 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 grer les e m p l o y s d ' u n e centrale nuclaire. partir d ' u n e classe de b a s e E m p l o y , v o u s faites hriter d'autres classes c o r r e s p o n d a n t a u x catgories 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 crs (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 crer la classe c o r r e s p o n d a n t e et la faire hriter de E m p l o y ou de classes spcifiques. 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 hriter 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 .
Complments
Hritage 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 hrits. Q u a n d v o u s dclarez un objet d ' u n e classe drive, 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 spcifique de la classe drive ne le soit. Voici un e x e m p l e :
class Base { public : Base();
44
} ;
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 dfauts 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.
Hritage 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 immdiat e m e n t suprieure, 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() // trpas de dl : appels -Deriveel() puis -Base() // radication de d2 : appels ~Derivee2(), // -Deriveel() puis -Base()
L'hritage simple
45
Exemple complet
R e p r e n o n s notre p r o b l m e d e vhicules. Voici l e c o d e c o m plet des classes Vhicule et Voiture, et d e s e x e m p l e s d'accs a u x d o n n e s de la classe de b a s e , savoir Vhicul.
class Vhicule { private : char buffer_interne[128]; // uniquement utilis par les fonctions de // la classe Vhicule. Inaccessible aux // classes drives. protected : char nom_vehicule[20]; int nombre_de_places; // donnes protges, transmises aux classes // drives mais toujours inaccessibles / / l'extrieur de l'objet public : // fonctions qui resteront publiques // dans les classes drives ( part les // constructeurs) // constructeur Vhicule(char *nom, int places); // fonctions d'accs aux donnes 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();;
};
// dfinition des fonctions Vhicule ::Vhicule (char *nom, int places) { sprintf(nom_vehicule, "%20s", n o m ) ; // on crit dans la chaine nom_vehicule les // premiers caractres de la chaine nom. nombre_de_places = places;
}
20
char
{
*Vehicule::get_nom_vehicule()
return nom_vehicule;
}
int
{
46
void Vhicule::set nomvehicule (char *nom) { sprintf(nom_vehicule, "%20s", nom); // on crit dans la chaine nom_vehicule les 20 // premiers caractres de la chaine nom.
}
int {
}
//
Vhicule::set_nombre_de_place(int nombre_de_places = p;
p)
private : float SavantCalcul (); protected : int chevaux_fiscaux; public : // constructeurs Voiture(char *nom, int places, int chevaux); // fonctions d'accs int get_chevaux_fiscaux(); void set_chevaux_fiscaux(int c h ) ; // fonctions de traitement float CalculVignette();
} ;
return chevaux_fiscaux;
}
float Voiture:: SavantCalcul (int code) { float savant_calcul; // on fait un savant calcul avec "code" return savant_calcul;
L'hritage simple
47
La fonction CalculVignette fait n'importe quoi, soyez certain que l'auteur en a parfaitement conscience.
float
{
Voiture:: CalculVignette()
int code; if (chevaux_fiscaux < 2) code = 0 ; else code = chevaux_fiscaux - 1; return (SavantCalcul(code));
}
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 Vhicule. Je rcapitule 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 ?
Vhicule::Vehicule(char *nom, int places) { sprintf(nomvehicule, "%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 sr ! 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 colre aussi v i v e q u e justifie ? 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 hrits. 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 dfin i e s d a n s Vhicule, et d o n t Voiture a hrit. 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
Vous tes en train de paniquer ? Vous avez l'impression d'tre noy sous les n o u veauts syntaxiques du C + + ? Ne tremblez plus, car l'aidem m o i r e C + + est arriv ! Il est joli et il vous attend sur le rabat de la couverture !
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 intresse. C ' e s t trs 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 hirarchie 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 :
// constructeur de la classe Drive, amlior Voiture ::Voiture(char *nom, int places, int chevaux) : Vhicule(nom, places)
{
chevaux_fiscaux = chevaux;
)
J u s t e aprs les p a r a m t r e s du constructeur, tapez le caractre : (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, sparez-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 utiliss 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 passs 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, caractre, etc.) Exemple :
// constructeur de la classe Drive, encore amlior Voiture::Voiture(char *nom, int places, int chevaux) : Vhicule(nom, places), chevaux_fiscaux(chevaux) { }
L e s diffrents l m e n t s de la liste d'initialisation sont sp 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 spcifie constante. R a p p e l o n s qu'une
L'hritage simple
49
c o n s t a n t e ne p e u t tre qu'' initialise (et j a m a i s affecte), 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 crent 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 initialiss d i r e c t e m e n t , d ' o gain de t e m p s certain. Rsum 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(paramtres) : liste_ini { // corps du constructeur de Classe
}
La surcharge
Le C++ offre aux programmeurs la possibilit de dfinir des fonctions homonymes. Ce principe est appel surcharge de fonction .
Notions de base
L'ide 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 rels, v o u s d e v e z rcrire la fonction, en lui donn a n t un autre n o m :
void tri_reels(float tab[], int taille_tableau);
52
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 diffrencier grce 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 dj un type de surcharge p o u r les o p r a t e u r s arithmtiques : 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'oprateur.
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 rfrence 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 diffrence 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 situes d a n s la porte de l'appel c'est--dire visibles sont p r i s e s en compte. E n s u i t e , les critres 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 diffrent 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 reconnat la b o n n e fonction s e l o n les types rels passs 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 illgal de dclarer les d e u x fonctions suivantes :
int float truc(int); truc(int);
P u i s q u e , cette fois, le type des p a r a m t r e s est diffrent. 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 rels 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 drives seront convertis en p o i n t e u r s de classes de base*, etc. 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.
* Les classes de base et les classes drives seront expliques au chaprtre 3, sur l'hritage.
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 dfinissez plusieurs fonctions traite_donnees , qui font des c h o s e s t o t a l e m e n t diffrentes, le gain en clart sera plutt 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 diffrent, v o u s a v e z intrt 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 diffrents, 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 + +
Complments
Surcharge d'oprateurs
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 oprateur op sur un objet, le c o m p i l a t e u r gnre 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 ralit u n n o m d e fonction c o m m e u n autre, qui est dfini p a r dfaut 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 dj 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, dfinie en d e h o r s de toute classe. Elle p r e n d d e u x paramtres, 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+, dfinie 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 paramtre de l'opration 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 diffrence. 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
55
%
A
&
<
>
%= = || ++
&= --
|= ,
==
void {
* Le passage par rfrence est une exclusivit C++ ! Vous en saurez plus en lisant le chapitre 9, page 113.
// fonction redfinissant l'oprateur + : // RESULTAT = A + B / / A est l'objet Color courant // B est le paramtre "droite" // RESULTAT est la valeur retourne Color rsultat) (rouge + droite.rouge) / 2, (vert + droite.vert) / 2, (bleu + droite.bleu) / 2 ) ;
56
return rsultat;
}
void {
// //
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 rfrence (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 rfrence, 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 rfrence revient modifier le p a r a m t r e rel, 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 crer un objet de cette classe d a n s la fonction oprateur, l'initialiser en fonction des d e u x p a r a m t r e s de l'oprateur, et le retourner.
P o u r q u o i passer droite par rfrence dans o p e r a t o r + ? E x p l i q u o n s plutt 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 dfini 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 dfaut serait m a u v a i s e . A v e c un p a s s a g e p a r rfrence, 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 . Rcapitulons : Soit v o u s utilisez u n p a s s a g e p a r rfrence 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 vrifier q u e la copie s'effectue bien. Si n c e s s a i r e , dfinissez 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 prciser 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 intressante q u e la s e c o n d e .
Surcharge de l'oprateur =
P o u r q u o i redfinir l'oprateur = ? L'affectation d ' u n objet, via l'oprateur = , est u n e o p r a t i o n sensible qui mrite q u ' o n s'y attarde. Si v o u s ne dfinissez p a s d'oprateur = 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 dfaut qui effectue u n e affectation b t e , d o n n e s membre donnes 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> 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("%s\n", cri); )
};
Le constructeur Exclamation ainsi que la fonction Affiche sont dfinis inline (voir page 21 ). new et delete remplacent malloc et free (voir chapitre 6 page 83).
58
void Exclamation::set_cri(char *nc) { // vrifions si nous n'avons pas affaire aux // mme pointeurs if (ne == cri) return; // librons la mmoire de l'ancienne valeur de cri delete cri; // allouons de la mmoire pour la nouvelle valeur cri = new char[strlen(nc)+1]; // copions le paramtre dans la donne cri. strcpy(cri, n e ) ;
}
beurk("Beurk"), bof("Bof"); // affiche "Beurk" // affiche "Bof" // operateur= dfini par dfaut // affiche "Beurk" // affiche "Beurk" / / o n modifie bof et beurk
La c o p i e p a r dfaut 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 rsultat 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 libre 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 redfinir l'oprateur = ? C e s constatations affreuses n o u s p o u s s e n t crire notre propre oprateur 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("%s\n", cri); ) Exclamation
};
60
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 rfrence, est explique la suite du listing.
Exclamation &Exclamation::operator =(Exclamation Ssource) { // vitons d'affecter deux objets identiques if (this == &source) return *this; // retournons l'objet courant delete cri; cri = new char[ strlen(source.cri)+1 ]; strcpy(cri, source.cri); return *this; // retournons l'objet courant
}
beurk("Beurk"), bof("Bof"); // affiche "Beurk" // affiche "Bof" // operateur= dfini par nos soins // affiche "Beurk" // affiche "Beurk" // on ne modifie que bof
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 rfrence plutt 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 rsultat, 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 vrifier 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 ralis, 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 trs dsagrable. R e g a r d e z un instant la p r e m i r e ligne aprs 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.
L'opration rciproque, savoir la conversion d'un type quelconque en un objet de la classe, est tout fait possible ! Il suffit d'utiliser un constructeur spcial (voir page 35),
P o u r autoriser et contrler 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 dfinir 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 lis 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 utilise 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 dfinir operator float ( ) ;
#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(); };
On ne devrait pas spcifier la taille du tableau directement dans la classe, mais plutt utiliser une constante. Toutes mes excuses p o u r cette hrsie honte.
62
return prix; } void main() { float total; Article b("Bonne bire", 12., 3 0 ) ; total = 5 * b; // conversion de b en float printf("5 bonnes bires valent %.2f F", total);
}
Pour conclure cette premire partie, attardons-nous sur les petits changements qui rendent la vie du programmeur plus agrable. Contrairement aux autres chapitres, celui-ci ne se dcompose pas en deux parties (notions de base et complments). 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()
64
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 * / ) , trs 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, trs utile si ces lignes c o n t i e n n e n t dj 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
{
char
*pointeur;
Rappelons que # i f expression est v a l u p a r l e prp 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
Ici, on donne une valeur par dfaut (18.6) au deuxime paramtre 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... */ }
}
main() res;
float
res = calcul_tva(100.); // 2me argument vaut 18.6 res = calcul_tva(100., 5.5); res = calcul_tva(100., 18.6);
}
I l existe d e u x possibilits 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 dfaut est c r a s p a r la v a l e u r d'appel du deuxime paramtre. 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 dfaut (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 dfaut d o i v e n t tre dclars aprs 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
P l u s i e u r s p a r a m t r e s par dfaut Il est p o s s i b l e de dfinir u n e v a l e u r par dfaut 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 dclarer aprs 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 dclaration 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 dfaut, 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 troisime 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 dclarer les v a l e u r s par dfaut la file, en fin d ' e n t t e :
void phonquession(int // mieux. b, int a = 1, int c = 3 ) ;
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 dfinir 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 possibilits d e const. N o u s allons voir tout cela au travers des diffrents 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 dclarer 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 dclaration. On ne p e u t p a s s i m p l e m e n t dclarer 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 dclare. 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 spcifier 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 donnes pointes 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 figs. Dtaillons ces trois c a s :
68
// 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 (aprs 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 chane " b a l i v e r n e s " n e p e u v e n t tre modifis. Illustrons u n e dernire 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[] = "billeveuses"; 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]; p2[2] = 'X; p2 = new char[10]; p3[2] = 'X'; p3 = new char[10];
}
69
clare 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 accs 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 intermdiaires, 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 dclarer 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 dfinie 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 dfinie ailleurs , d a n s un autre fichier (c'est l e rle d u m o t - c l e x t e r n ) .
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 rfrences, 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 rfrence 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'intrt 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'intgrit de l'objet. E x e m p l e :
const int &f() { static big_objet big; // manipulations sur big return big;
}
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 intressant : c o m m e n t dclarer 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, hlas
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 dclaration d ' u n e classe. V o u s n e p o u v e z q u e dclarer 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
} ;
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 (lie un objet prcis) et n o n de classe. Elle ne p e u t d o n c p a s tre initialise 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 dclare 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 dclare 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
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 dclarer et les initialiser l'intrieur 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 immdiatement. 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 :
73
>;
Dclarations
E n C + + v o u s p o u v e z dclarer 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 srie d'instructions. La p o r t e de telles variables va de l'endroit de leur dclaration 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 gnral).
void
{
main()
V o u s p o u v e z dclarer 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 suprieur. 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 utilise. E x e m p l e :
void
{
main()
74
Variable de boucle Voici u n e construction intressante : v o u s p o u v e z dclarer 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
}
)
Rsum
La dcouverte du C++ est un peu droutante, tant les termes et concepts indits sont nombreux. C'est pourquoi nous vous proposons ici un rsum rapide de la premire partie du livre. Il vous permettra d'avoir les ides claires avant d'aborder la suite, riche en rebondissements...
77
Encapsulation
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 oriente-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, entits 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 lies. 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 cres 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 dfinir 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 appeles fonctions-membres. De m u l t i p l e s objets p e u v e n t tre crs 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 diffrentes. 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
{
NomClasse NomClasse
objetl.nom_de_fonction_public();
}
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 dfinit pas, le c o m p i l a t e u r en g n r e p a r dfaut. Le c o n s t r u c t e u r est appel c h a q u e cration 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 ds q u ' u n objet sort de sa p r o p r e p o r te 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
Hritage
L'hritage p e r m e t de mettre en relation d e s classes. Q u a n d u n e classe B hrite d ' u n e classe A, A est a p p e l e classe de b a s e et B classe drive. 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 intgre donc les d o n n e s et fonctions accessibles de A.
La classe drive B p e u t redfinir des fonctions de la classe de b a s e A. On parle alors de redfinition de fonction membre, ce qui signifie q u ' u n m m e entte de fonction est partag p a r toute u n e hirarchie de classes.
79
Surcharge
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 situes 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) ;
La fin du malloc
new et delete
Nous sommes au regret de vous annoncer le dcs de malloc, survenu aprs de nombreuses annes de bons et loyaux services... En ralit, bien que vous puissiez toujours utiliser malloc, calloc et free, le C++ met votre disposition new et delete, leurs talentueux remplaants.
Notions de base
L'ide L e C + + propose deux nouveaux mots-cls, new e t d e l e t e , p o u r r e m p l a c e r leurs ans 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 caractristiques d u C + + q u e sont l'hritage, 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 intgrs au l a n g a g e C + + . Ce sont la fois d e s m o t s - c l s (rservs) et d e s oprateurs. Le rle 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 crs.
84
L e rle 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 crs p a r new, p u i s libre l ' e s p a c e m m o i r e rserv.
L'oprateur new C o m m e m a l l o c , l'oprateur 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 alloue, soit 0 (zro) si l'opration a chou. On p e u t utiliser n e w de trois m a n i r e s diffrentes :
Dans les cas cicontre, la variable pt dsigne toujours un pointeur de T (dclar avec T *pt).
pt = new T;
L'oprateur new recherche un espace mmoire 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 dfaut 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 dfaut 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 paramtres indiqus. 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 conscutifs 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 dfaut de la c l a s s e T est invoqu pour chaque lment. L'oprateur 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 librer 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 libre 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 librer u n tableau allou a v e c new T [ taille tableau ].
A t t e n t i o n ! Dtruire un objet individuel avec l'instruction delete [ ] p r o v o q u e quelque chose d'indfini mais de gnralement nuisible (loi de Murphy). De mme, utiliser delete p o u r dtruire un tableau entraine des consquences t o u t aussi alatoires !
La fin du malloc
85
L e s spcifications 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 librer des c h a n e s de caractres. P r e n o n s c o m m e e x e m p l e la classe CDRom, dfinie ci-dessous :
class CDRom { protected : char * titre; public : CDRom(); CDRom(char *tit); -CDRom();
} ;
CDRom::CDRom(char *tit) { titre = new char[ strlen(tit) + 1 ]; if (titre == 0) badaboum(); strcpy(titre, tit); } // dfinition du destructeur CDRom::-CDRom()
{
delete [] titre;
}
Vous constarerez que nous avons bien respect la symtrie 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 demande. 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
void
{
main() *pt_inconnu, *pt_cd_X; rebel_assault("Rebel Assault"); // (1) // (2) // (3) // // // // // // (4) (5) (6) (7) (8) (9)
CDRom CDRom
pt_inconnu = new CDRom; pt_cd_X = new CDRom("Virtual Escort"); // blabla delete pt_inconnu; delete pt_cd_X; }
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 dfaut, p u i s q u ' i l ne p r e n d aucun paramtre). 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 crer 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 dtruire 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 librer les c h a n e s de caractres alloues.
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'intrt 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'entte. Par ailleurs, l'utilisation du n e w p e r m e t la vrification 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 sret 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 oprateur, il p e u t tre redfini p a r v o s soins* et il peut tre hrit. Q u e v o u l e z - v o u s de p l u s ?
La fin du malloc
87
Que celui qui n'a pas pest la premire fois qu'il a dcouvert le profil de malloc se lve.
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 spcifiques 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 rcrit 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
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 trs bien le concept
la gobde
Notions de base
L e C + + intgre d a n s s a librairie s t a n d a r d diffrents 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 rle des trois flux s t a n d a r d s du C + + * :
90
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 dfini 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 intressants q u e c e u x d e C .
cout << "Entrez le nom de l'tudiant : cin >> nom; for (i=0; i<3; 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 entrer d'espace(s) pendant la saisie d'une chane. En fait, p o u r tre exact, vous pouvez le faire, mais vous rendrez v o t r e programme compltement fou, et vous avec.
}
cout << "Entrez la note n" << i+1 << ' '; // (1) cin >> notes[ i ] ; cumul += notes[i]; cout << nom << " a pour moyenne " << cumul/3. << endl; } / / C e programme affiche ceci : // (les caractres gras sont taps par l'utilisateur) Entrez le Entrez la Entrez la Entrez la GaryMad a nom de l'tudiant : GaryMad note nl 0.5 note n2 7 note n3 19.5 pour moyenne 9
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 caractre 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 prciser l e form a t d'affichage, celui tant dfini p a r dfaut 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 rel par e x e m p l e ) , inutile de revenir printf, cout sait le faire trs 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 connatre 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'intrt principal de cout, cin, cerr, << et >> est la vrification 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 vrifier le type des d o n n e s q u ' e l l e s traitent (ce qui accrot 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 affiches 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 vrit 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 frres 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 (infme, n'est-ce pas ?) printf("%s%d%c%f\n", "Voici i:", entier, '+', rel); scanf("%f", &reel); // C++ (mieux, tout de mme !) cout << "Voici i:" << entier << '+' << rel << endl; cin >> rel;
M a i s l'intrt ne se limite p a s l : v o u s p o u v e z surtout redfinir les oprateur << 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 utilises ! 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 ralisateur, opration ralise de la m m e m a n i r e q u e p o u r un entier :
92
Utilisateurs de MSD O S , prenez garde ! Le n o m de fichier utilis dans l'include fait plus de huit caractres !
main()
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 dfinie, 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 raliser 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.
Complments
Surcharger et
Avant de lire ce complment, vous devez connatre les fonctions amies (page 123), ainsi que les principes de la surcharge, dvelopps 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 rsultat, a = 1, b = 1; rsultat = a + b; // quivaut rsultat = 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;
Il s'agit ici de l'oprateur << 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
La fonction s u r c h a r g e r est identifie. 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 dclarer 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
#include <iostream.h> class NomClasse { private: int n; public : NomClasse(int i) : n(i) {} int get_n() const { return n; } // dclarons que l'oprateur global << est // un ami de notre classe (NomClasse) : friend ostreamS operator<< (ostream& out, const NomClasse& obj);
};
Nous utilisons ici des passages par rfrence (voir chapitre 9, page I 13).
// surchargeons l'oprateur global << pour qu'il // sache traiter des objets de classe NomClasse : ostream& operator<< (ostreamk out, const NomClasse& obj)
{
// out est l'objet comparable cout // obj est l'objet afficher return out << '[' << obj.n << ' ] ' << endl;
}
void {
maint)
NomClasse
objet(1);
Pour redfinir >>, utilisez : istream& operator>> (istreamk in, NomClasse& obj);
94
*/
// affichage: // [1]
L a fonction o p e r a t o r < < r e t o u r n e u n e rfrence 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 rfrence v e r s cet objet.
L e s indications de formatage (prsentation) 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'oprateur < < . 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 diffrent. V o u s t r o u v e r e z e n s u i t e un tableau rcapitulatif 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 caractres L'identificateur s e t w ( n ) force c o u t afficher a u m o i n s n caractres, et plus si ncessaire, s e t w est l'abbrviation de set zuidth ( spcifie la largeur ). Par dfaut 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 caractres q u e n c e s s a i r e , spcifiez 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 dfaut. 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 !
}
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 caractres. Par dfaut, 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 affiche (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 caractres est toujours valable. Cette fois-ci, les trois lettres de W a x sont affiches, 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 dfaut d e c o u t . S i v o u s v o u l e z l e rtablir, 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 prcdent. A f f i c h e r n chiffres aprs 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
// affiche : 1.23
main()
cout << setw(5) << setiosflags(ios:: showpoint) << 1.2 << endl; } // affiche : 1.200
Ne pas afficher les zros aprs 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;
}
maint)
La fin du printf
97
R e m p l a c e r le caractre blanc par un autre caractre N o u s p a r l o n s ici d e s e s p a c e s affichs 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 spcifi 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 affichs 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 caractre 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 dfinies trois b a s e s : l'octal (base 8 ) , le d c i m a l (par dfaut) et l ' h e x a d c i m a l (base 16). il suffit de spcifier l'abrviation ( 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()
// 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 rel 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 rel.
98
main()
// affiche : .123 e2
main()
cout << setiosflags(ios:: scientific) << 12.3 << endl; cout << setiosflags(ios::fixed) << 12.3 << endl; } // affiche : .123 e2 12 .3
La fin du printf
99
Tableau rcapitulatif
Effet voulu
au moins n caractres aligner gauche aligner droite n caractres aprs la virgule afficher les 0 aprs la virgule ne pas afficher les 0 aprs la virgule '+' devant les nombres positifs changer le caractre de remplissage afficher en dcimal, octal ou hexadcimal notation scientifique notation virgule fixe
cout ...
setw(n) setiosflags(ios::left) setiosflags(ios::right) setprecision(n) setiosf lags(ios: :showpoint) setiosflags(ios::showbase) setiosf lags(ios: :showpos) setfill(c); // c est un char dec, oct ou hex setiosf lags(ios::scientific) setiosflags(ios::fixed)
Le polymorphisme
et la virtualit
Vous connaissez dj les vertus de l'hritage. Dcouvrez maintenant celles du polymorphisme, mis en uvre par la virtualit en C++. Si vos notions sur l'hritage sont encore un peu floues, vous gagnerez relire le chapitre 3 avant d'aborder celui-ci.
Notions de base
L'ide 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 redfinition 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 diffrence grce aux p a r a m t r e s . La redfinition 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'hritage, 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 entte 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 redfinition 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
qute et va jusqu' retrouver la classe relle d'un objet point. Limites de la redfinition 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 rellement vers un T e x t e M i s E n F o r m e , la redfinition 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;
}
// hritage
{
public: void
} ;
I
! Imprimer(int n b ) ;
void TexteMisEnForme::Imprimer(int nb) { cout << "Imprimer de la classe TexteMisEnForme" < < endl;
i i
}
void main() { TexteBrut TexteMisEnForme
|
i i *txt; joli_t;xt; j
i
i
Ce n'est pas le rsultat espr : il faudrait que la fonction Imp r i m e r dfinie dans la classe T e x t e M i s E n F o r m e soit appele, car l'objet rellement point appartient cette classe. Le polymorphisme permet justement de rsoudre ce problme.
Le polymorphisme et la virtualit
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'entte 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 prcdent ... 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 rle du mot-cl virtual d a n s n o t r e petite architecture de classes. Le rle 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 dfinie d a n s la classe de l'objet rel 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 trs 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
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 contrle 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 !
En temps normal, quand vous dclarez un pointeur de type A, vous ne pouvez pas le f a i r e pointer sur une donne de type B*. En tous cas, cela devrait tre contraire votre thique. M a i s i c i , nous sommes dans une situation spciale : nous dclarons un pointeur sur une classe de base. D a n s ce cas prcis, et le C + + nous y autorise, vous pouvez faire point e r ce pointeur vers une classe drive de cette classe de base. C'est exactement ce que nous faisons dans l'avant-dernire ligne du l i s t i n g . La ligne qui suit (txt -> Imprimer ( 1 ) ) devrait donc f a i r e appel la fonction Imprimer dfinie 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 hirarchie de classe, p o u r dclarer 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 rsolus d y n a m i q u e m e n t , il faut qu'elles soient virtuelles. Il suffit de dclarer 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 drives soient elles aussi virtuelles*. P e n d a n t l'excution, la fonction appele 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 rellement 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 orientsobjets est appel polymorphisme. La virtualit est sa m i s e en uvre C + + . N o u s avions dj v u q u ' a v e c l'hritage, l e C + + proposait q u e l q u e c h o s e de trs intressant, 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 hirarchie de classe, en bnficiant de la r s o l u t i o n d y n a m i q u e des appels.
Rsum
* C'est--dire m m e n o m b r e et mmes types d'arguments.
* Pour la clart du listing, on pourra rpter le mot-cl virtual devant chaque fonction concerne, t o u t au long de la hirarchie.
Le polymorphisme et la virtualit
105
Rions ensemble. Un utilisateur de Macintosh dclarait rcemment : Windows p o u r PC, c'est mettre du rouge lvre un poulet
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 Fentres 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 droulant, 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 :
ObjetGraphiqueGenerique *obj[10]; // tableau de 10 pointeurs // ... affectation d'objets divers aux pointeurs du tableau // (nous ne dtaillons pas cette partie) for (i = 0; i < 10; i++) { obj[i] -> Afficher(paramtres);
}
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*, dfinir p l u s i e u r s structures figes 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 trs 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'hritage. S e u l l'hritage, 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
105
Rions ensemble. Un utilisateur de Macintosh dclarait rcemment : Windows p o u r PC, c'est mettre du rouge lvre un poulet
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 Fentres 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 droulant, 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 :
ObjetGraphiqueGenerique *obj[10]; // tableau de 10 pointeurs // ... affectation d'objets divers aux pointeurs du tableau // (nous ne dtaillons pas cette partie) for (i = 0; i < 10; i++) { obj[i] -> Afficher(paramtres) ;
)
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 * , dfinir p l u s i e u r s structures figes 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 trs 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'hritage. S e u l l'hritage, 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
Complments
Destructeurs virtuel
Il est tout fait p o s s i b l e de dfinir 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 dtruire 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 Vhicule { public : virtual -Vhicule!) { cout << "-Vhicule" << endl; }
>;
class Camion : public Vhicule { public : virtual -Camion() { cout << "-Camion" << endl; }
};
void main() { Vhicule *pt_inconnu; Camion *mon_beau_camion = new Camion; pt_inconnu = mon_beau_camion; delete pt_inconnu;
}
Le delete en dernire 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 Vhicule, 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 srie 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 Vhicule 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 Vhicule, m a i s p o i n t e en ralit 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
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 orients-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 drives. 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 rside d a n s ses classes drives. 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 dclare 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 retourne, n o m et p a r a m t r e s ) est dfini. On dclare u n e fonction virtuelle en ajoutant = 0; aprs l'entte d e l a fonction, e n plus d u m o t - c l v i r t u a l . Consquences Q u a n d v o u s dclarez 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 dcrte classe abstraite : elle ne p e u t d o n c pas tre utilise p o u r crer un objet. la fonction virtuelle p u r e ne peut tre dfinie : elle n ' a q u ' u n profil, u n entte, m a i s pas d e c o r p s les classes futures qui hriteront de cette classe abstraite devront dfinir 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'intrt 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 driv e s redfinir 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
E x e m p l e court D a n s le listing qui suit, n o u s dclarons 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 aprs s o n entte. N o u s n e p o u v o n s d o n c p a s crer 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 crer d e s objets de la classe Driv e qui dfinit 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
} ;
void
{
Drive::afficher(int x, int y)
// traitement } void
{
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'intrt 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 dfinir 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 caractristiques : 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 fentre. 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 dcrirons 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
classe ObjetGraphique
Nous utilisons ici plusieurs techniques propres au C + + et indpendantes des fonctions virtuelles : les paramtres par dfaut (voir page 65), la liste d'initialisation (voir page 47), et la dfinrtion inline des fonctions (voir page 21),
public : // constructeur ObjetGraphique(ObjetGraphique *pi = 0, int xi = 0, int yi = 0) : pere(pi), x(xi), y(yi) { } // fonctions d'accs ObjetGraphique *get_pere() { return pere; ) int get_x() { return x; } int get_y() { return y; } // fonctions de modification virtual Boolean set_x(int me) = 0; virtual Boolean sety(int ny) = 0; // fonctions de haut niveau virtual void Redessine() = 0;
} ;
110
//
classe BoutonOnOff
class BoutonOnOff : public ObjetGraphique { private : const char CAR_ON; //caractre affich si ON const char CAR_OFF; //caractre 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 dfinir // 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(); }
} ;
Boolean
{
return True;
}
void
{
BoutonOnOff::Redessine()
Le polymorphisme et la virtualit
111
// void
{
BoutonOnOff
bouton(0, 10, 1 0 ) ;
Ce programme met en vidence l'intrt 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 dclares 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'ide, 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 forant les classes d r i v e s dfinir 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 rfrences
Seul le passage par valeur est autoris en C standard. Mme lorsque vous passez un pointeur une fonction, vous passez la valeur de ce pointeur. Le C++ introduit une nouvelle possibilit : le passage par rfrence (connu depuis longtemps en Pascal). Par ailleurs, ce mme mcanisme de rfrence vous permet de dclarer des synonymes de variables : il s'agit des rfrences libres.
Notions de base
L'ide 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 rfrence, cette fonction reoit un s y n o n y m e du p a r a m t r e rel. En o p r a n t sur le p a r a m t r e p a s s p a r rfrence, 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 rel. 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 . Le caractre & est utilis d a n s l'entte 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 rfrence est d c l a r :
void plus_l(int Snombre) // passage par rfrence attendu { nombre++;
114
void {
main()
C o m m e v o u s le constatez, l'appel se fait de m a n i r e trs 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 rfrence vers l e p a r a m t r e rel 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 rpercute sur l a variable i d u p r o g r a m m e principal. Dclarer une rfrence libre V o u s p o u v e z utiliser les rfrences en d e h o r s de toute fonction : en dclarant 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 rfrence 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 rfrence nombre int *pointeur; bis = 1 0 ; nombre = 20; pointeur = &bis; (*pointeur) = 3 0 ;
}
Le fait d'utiliser le caractre & 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 paratre 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 rfrences, tout en bnficiant 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 rfrence. 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 rfrences se rvlent trs p r a t i q u e s !
Les rfrences
115
Complments
Rfrences et objets provisoires
* Petite prcision de vocabulaire : dans la ligne int &ref = ini; ref dsigne l'objet rfrence et ini l'objet initial.
P o u r q u ' u n e rfrence soit c o r r e c t e m e n t utilise, il faut q u e l'objet rfrence 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 prsenter : 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
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 rfrence 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 gnrer u n e erreur d e type. (2) La rfrence est constante. Un objet t e m p o r a i r e est cr ; la rfrence le dsigne.
D a n s s o n dsir 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 , grce a u x rfrences, 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 rfrence, 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'lments de tab int &fonction(int j) // retourne une rfrence { if (j >= 0 && j < nb_elem) return tab[j]; else return tab[0];
}
void main()
116
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 rfrence 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 gnricit
Toujours plus, toujours : dans sa C + + nous offre les templates, qui les fonctions ou les classes par des sants, mais souvent dlicats mettre teurs relativement rcents autorisent et fiable.
dbauche d'effets spciaux, le vous permettent de paramtrer types. Les templates sont puisen uvre : seuls les compilaleur usage de manire simple
Notions de base
L'ide
Juste une remarque de vocabulaire : la
gnricit est le
concept orientobjet, alors que les
templates sont la
solution apporte par le C + + pour mettre en uvre ce concept.
J u s q u ' prsent, 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 rle et l'intrt de la gnricit 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 dclares 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 gre 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 rel 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 intrt tre p a r a m t r e
118
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. 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
Templates de fonctions
cout << "min entier : " << min(il, i2) << endl; cout << "min char : " << min(cl, c2) << endl;
}
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 obligs 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 reconnat 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
Templates de classes
* Il faudrait bien entendu grer un nombre variable d'objets, avec allocations et dsallocations la demande . Cela compliquerait la classe et nuirait l'explication des templates. Par ailleurs, la gestion d'erreurs est rduite ici sa plus simple expression. N o u s verrons au chapitre I 3, page I 37, comment implanter une vritable gestion des erreurs.
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 gre 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 dclarer ?
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]; public : TYPE void void
} ;
template <class TYPE> TYPE Liste<TYPE> ::get_elem(int n) // retourne le nime lment class
{
if (n >= 1 && n <= MAX) return elem[ n-1 ]; else { cerr <<"Liste::get_elem - Indice hors limite :" << n << endl;
120
exit(1); }
}
template <class T Y P E > void Liste<TYPE>: :set_elem(int n, TYPE e) // stocke e la nime 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);
}
}
void
Liste<TYPE> ::affiche()
for (i = 0; i < MAX ; i + +) cout << "Num " << i+1 << ':' << elem[i] << endl;
}
main()
{
Liste<int>
liste;
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 spcifique la classe Liste, dfinie l'aide d ' u n enum (voir p a g e 7 1 ) . La dclaration 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
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
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 dclare u n e classe t e m p l a t e :
122
Complments
Attention l'ambigut
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 grer 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 thorie 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 dclarer un objet Liste :
Liste<Arbre<int>> ma_liste_d_arbres; // NON
D a n s cette dclaration, 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 dclaration 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 caractres s u p r i e u r s r e p r s e n t e n t un oprateur. 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 dclaration d e v i e n t donc :
Liste< Arbre<int> > ma_liste_d_arbres; // OUI
L'amiti n'est pas une vertu rserve 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'ide
* Rappelons que l'encapsulation c o n siste manipuler des objets composs de donnes et de fonctions de manipulation de ces donnes. Les donnes sont caches au monde extrieur.
L o r s q u ' u n e fonction est dclare 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 dclare 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 sige, 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 rgle 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 crateur 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
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'oprateur , p a g e 92. C e l a dit, mfiez-vous et n'utilisez les a m i e s q u e si v o u s y tes contraint...
friend:
class A { friend void fonction_B(); friend class C;
//
> ;
...
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 spcifier de quelle classe elle est m e m b r e , avec u n e ligne du style :
friend void AutreClasse : :fonction_B();
protected de la classe A.
C o m m e v o u s l'avez constat, la dclaration 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 :
125
Attention !
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 intressantes 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 dclar d'autres c l a s s e s a m i e s doit se rpercuter 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 tte c o n t r e les m u r s .
L'hritage multiple
simple vous permet d'utiliser des classes qui existent en crer d'autres. L'hritage multiple est identique, la prs que vous pouvez hriter simultanment de plusieurs Ce qui n'est pas sans engendrer quelques problmes re-
Notions de base
L'ide 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 crer des classes d r i v e s qui hritent 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 hritage 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 hritage m u l t i p l e s i m u l t a n :
128
D a n s le cas p r s e n t droite du s c h m a ci-dessus, la classe C hrite 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 spcificateurs p r o t e c t e d , p r i v a t e e t p u b l i c ) .
U n hritage multiple s e spcifie c o m m e u n hritage s i m p l e , en sparant p a r d e s virgules les classes de b a s e s dsires. P o u r dclarer l'hritage multiple du s c h m a p r c d e n t , il faudrait crire :
class A { // blabla public : void
} ;
fonction_a();
fonction_b();
fonction_c();
main() objet_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 dceler au p r e m i e r a b o r d . . .
L'hritage multiple
129
O r d r e d'appel des constructeurs D a n s le cas d ' u n hritage m u l t i p l e , la cration d ' u n objet de la classe drive 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 dclaration de l'hritage. P a r e x e m p l e :
class A {
} ;
class B {
} ;
class C : public B, public A // la ligne ci-dessus dtermine l'ordre d'appel // des constructeurs
{
} ;
void
{
main()
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() ;
} ;
void
{
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'hritage 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'hritage 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 faon p l u s juste l e m o n d e rel.
730
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 considrer 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'hritage multiple vous permettra de garder un schma cohrent :
exemple
d'hritage multiple
Il faut n a n m o i n s t e m p r e r cette excitation : l'hritage 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 dceler. E s s a y e z autant q u e p o s s i b l e d'viter l'hritage 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 hirarchie 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'hritage multiple
131
Complments
Duplication de donnes 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 hritage m u l t i p l e hritent e l l e s - m m e s d ' u n e m m e classe de b a s e :
Traduisons ce schma en C + + :
class Base { int variable_base; // 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 hrites 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 hrites de la classe Base. Or la classe C hrite de la c l a s s e A et de la
132
classe B. C bnficie donc de d e u x copies distinctes des donn e s hrites 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 , ds 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'oprateur de rsolution de p o r t e : :. Il suffit de spcifier 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 hrit 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 public : int variable_base; // dsol *
* Pour un exemple simple et court, la variable est dclare public. C'est un exemple ne pas suivre (en temps normal). Il faudrait bien entendu la dclarer private ou protected et dfinir des fonctions public d'accs et de modification.
void main()
L'hritage multiple
133
L a ligne (1) n e prcise 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 diffrentes e t b i e n distinctes e n mmoire. 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'hritage 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 rfrence 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 considrons qu'un priphrique est la fois une ressource et un objet rel, physique. N o u s dfinissons 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
void
ObjetReel: : Info() { }
} ;
void
Imprimante::Info() { }
Le problme
* En effet inconnu est un pointeur de Priphrique, mais il pointe en ralit sur un objet de classe Imprimante.
Voici le problme : en temps normal, grce au mcanisme de virtualit, la ligne (2) du m a i n devrait appeler la fonction I n f o dfinie dans la classe I m p r i m a n t e * . M a i s hlas, cause de l'hritage multiple, c'est la fonction-membre dfinie par la classe P r i p h r i q u e qui est appele, notre grand dam ! P o u r plus de clart, je vais reprendre l'explication d'une autre manire. Quand une classe, ici P r i p h r i q u e , utilise l'hritage multiple, cela peut poser un problme d'ambiguit : il suffit que les classes de base hrites (ici R e s s o u r c e et O b j e t R e e l ) possdent 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 rside justement dans cette espce de flou permis au pro-
L'hritage 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'excution d u p r o g r a m m e , s e l o n l'objet rell e m e n t point.
Hritage virtuel
Il vaut mieux lire les paragraphes prcdents avant de s'aventurer dans celui-ci.
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 forcm e n t le dsir 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'hritage virtuel p e r m e t de n'avoir qu'une seule occurrence d e s d o n n e s hrites d ' u n e classe d e b a s e .
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 hritent 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 placs) :
class Base { public : int variable_base; // blabla
};
136
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 spcifier 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 mcanisme des exceptions pour coordonner la lutte contre ces erreurs indsirables. Attention cependant : les exceptions sont encore peu rpandues, tant au niveau des compilateurs que des programmeurs C++. C'est donc en quelque sorte un secteur en chantier ...
Notions de base
L'ide 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 intercepte p a r un autre b o u t de p r o g r a m m e . L'intrt de ce s y s t m e est de sparer la dtection d'erreur d e s t r a i t e m e n t s associs. 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 hirarchie de classes, il est j u d i c i e u x de dfinir les e x -
138
ceptions q u e v o s classes p o u r r o n t lancer e n cas d e problme. De s o n ct, 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 diffrents trait e m e n t s p o u r r a d i q u e r le mal.
A t t e n t i o n : les exceptions f o n t uniquement rfrence aux vnements internes au programme, et n o n aux vnements extrieurs c o m m e par exemple un clic souris ou l'appui sur une t o u c h e du clavier.
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 ralisera les oprations d a n g e r e u s e s , c'est--dire susceptibles de gnrer 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 prcd 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 :
#include <iostream.h> #include <except.h> void main() { try
{
int *p = new int[1E99]; // difficile ! cout << "Fin normale" << endl;
}
catch (xalloc)
{
A l l o u e r 1 9 9 entiers est u n e opration dlicate, 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 dtaillons 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 traite. 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 dtecte 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 excut. 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 appels 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 dcrits 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 diffrent. 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 appele. Par dfaut, 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 dtail d a n s les c o m p l m e n t s d e ce chapitre. Prcisions 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 drives si Type est une classe. Idem que ci-dessus, mais lxception est reprsente dans le bloc catch par un objet rel : obj. Intercepte toutes les exceptions non-traites par les blocs catch prcdents.
Intercepter plusieurs exceptions I l suffit p o u r cela d'crire plusieurs b l o c s c a t c h aprs 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 dclencher // Exceptionl, Exception2 ou une autre exception } catch (Exceptionl) {
140
Crer 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 crer 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 dernire exception lance.
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 dernire exception, en esprant 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
Les exceptions
141
* Dans la ligne cicontre, MonErreurO cre un objet temporaire de classe MonErreur. C'est donc bel et bien un objet qui est spcifi aprs t h r o w .
void MaClasse::FonctionBoguee() { // admettons que cette fonction soit bogue; // elle gnrera donc une exception : throw MonErreur(); // * voir dans la marge } void main() { try
{
C e t exemple nous montre que l'exception gnre 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 intercepte 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 dtecte 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 rencontre. L'intrt 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 dtecte ? 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 entire, d c l e n c h a b l e s puis rcuprables en c a s c a d e , du b l o c le
142
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 gnral 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.
Rsum
L e C + + p r o p o s e d e grer 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 dfaut, 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 ( ). N o u s allons m a i n t e n a n t dtailler 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; }
} ;
Exemple complet
class Tableau { protected: enum { MAX = 10 }; // constante int tab[MAX]; public : int get_MAX() { return MAX; } int &operator[](int i ) ;
} ;
Les exceptions
143
* L'oprateur [ ] retourne ici une rfrence (un synonyme) du tableau pour que l'utilisateur puisse consulter mais aussi modifier sa valeur dans une expression du style t[n] = i. V o i r le chapitre sur les rfrences, page I I 3.
int &Tableau: :operator[] (int i) { if (i<0 || i>=MAX) throw ErreurBorne(i); else return tab[i];
}
i = t[9]; // ligne jamais excute } catch (ErreurBorne err) { cout << "Erreur de borne : " << err.get_borne_fautive() << endl;
}
}
Complments
Spcifier 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'entte n o r m a l . E x e m p l e :
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
Attention : il faut que le throw soit spcifi dans l'entte de dclaration et dans l'entte de dfinition 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 driv e s . f2 p e u t lancer des e x c e p t i o n s MonErreur ou Ma-
144
C r a i n t e ou de classes drives, 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 entte, l a fonction u n e x p e c t e d (inattendue) est appele. Par dfaut, 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 dsirez, 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 dfaut 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'entte s u i v a n t :
PFV VotreNomDeFonction(void);
void MaClasse::FonctionBoguee() throw(MonDilemne) { throw MonErreur(); //n'est pas autoris par l'entte
}
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);
}
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 dfaut, 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 dfinir 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'entte suivant (o P F V est dfini 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, dfinissons-le #ifndef PFV typedef void( *PFV ) (); #endif class MonErreur {}; class MonDilemne {}; class MaClasse { public : void FonctionBoguee() throw(MonErreur);
146
) ;
throw(MonErreur)
PFV
{
MonTerminator() // ne doit pas lancer d'exception // ne doit pas retourner son appelant cout << "On dirait qu'il y a un problme" << endl; exit(1);
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 traite 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
147
void MaClasse::FonctionBoguee() throw(MonErreur) { throw MonErreur(); // autoris par l'entte } 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()
// dclenchera un MonDilemne
// ce catch est appel cout << "Dilemne dans le main" << endl;
} }
La compilation spare
Bien que la compilation spare 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++ diffre lgrement du C dans ce domaine.
Notions de base
L'ide La c o m p i l a t i o n spare p e r m e t de rpartir 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 dernire 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 brivement 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 franais officiels mea culpa...). La compilation d ' u n fichier C ou C + + d o n n e un fichier objet,
250
U n e librairie est en quelque sorte un ensemble de fichiers .o que vous incluez l'dition des liens.
en gnral suffixe p a r . o. C e s fichiers . o sont e n s u i t e lis 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 links 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 , aprs 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 rutiliser 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 raliser 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 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 diffrentes : 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 spare
151
On parlera ici de fichiers .cpp pour dsigner les fichiers contenant du codesource C + + . Votre compilateur utilise peut-tre une autre extension (.cxx ou .C par exemple).
Principe gnral 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 dclarer u n e classe ou d e s enttes de fonctions, et un fichier s u f f i x e p a r . cpp p o u r la dfinition 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 dfinie ailleurs, il faut inclure au d b u t de A le fichier . h qui dclare 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 dcrit c o m m e n t c'est implant. Conseils La dclaration 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 dfinition 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 dfinition 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 dfinis 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 dclaration 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 dclaration 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 dclarations 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 situs u n p e u plus loin d a n s c e chapitre.
252
Compilation
spare
exemple
Remarques A v e c l a c o m p i l a t i o n spare, l e p r o g r a m m e u r est contraint de respecter certaines rgles : 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 dclarant 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 spare
153
Classl . h .
Q u a n d v o u s modifiez l'entte 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 dclar 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 dtaille 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 complments #include "fonc_c.h"
}
// fichier global.cpp // constantes globales const float PI = 3.1415; // variables globales int _statut;
Cette manire d'oprer (avec #ifndef) est explique dans les complments, page I 57.
// fichier extern.h tifndef _EXTERN_H #define _EXTERN_H // constantes globales extern const float // variables globales extern int _statut;
PI;
754
#endif
// fichier Classl.h #ifndef _CLASS1_H #define _CLASS1_H class Classl { protected: // ... public : Classl ( ) ; void FoncMemb ( ) ;
} ;
#endif
// fichier fonc_c.h #ifndef _FONC_C_H #define _FONC_C_H #define CONST_C void #endif 100
fonction_c(int a ) ;
La compilation spare
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 excuter
Si une ligne de votre Makefile est t r o p longue, vous pouvez la couper en deux condition que la premire 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 dduit 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 rcent (au n i v e a u d e s dates d e dernire 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 modifis 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 dp 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 aprs 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
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 :
excutable : fichier.o autre_fichier.o CC -o excutable fichier.o autre_fichier.o
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
La compilation spare
157
Complments
Comment viter les redclarations ?
L e c o m p i l a t e u r n ' a i m e p a s les dclarations 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 redclaration. 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 :
// dbut du fichier extern.h #ifndef _EXTERN_H #define EXTERN_H // corps complet du fichier #endif // fin du fichier extern.h
158
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 dbut, 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 dfinit 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 .
Le g a i n de t e m p s et de clart n ' e s t p a s le seul intrt 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 dclare s t a t i c ainsi q u e toute v a r i a b l e dclare 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 dfinie. E x e m p l e : PORTE 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 dclars 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'intrieur de f ichl. cpp.
La compilation spare
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 dclarez 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 dclare. Constantes globales U n e c o n s t a n t e dclare en global n ' e s t visible q u e d a n s le fichier o elle est dfinie, m o i n s de spcifier e x p l i c i t e m e n t c o n s t d a n s l a dcla'ration externe. E x e m p l e 1, o PI est dclar l o c a l e m e n t f i c h l . c p p :
7 60
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 grce l a c l a u s e e x t e r n " n o m " , o nom reprsente 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" lment; dclare que l'dition des liens de lment (un entte de fonction ou une variable) se fait selon la convention nom. Idem que ci-dessus, mais tous les lments dclars dans bloc sont concerns. Idem que ci-dessus, mais toutes les dclarations faites dans fichier.h sont concernes.
extern
"nom"{
) Prenons l'exemple d'un programme C + + ayant besoin de fonctions C. On utilise e x t e r n " C " :
// fichier sriai.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 { /*...*/ }
La compilation spare
161
Guide de survie
Les concepts prsents dans les deux premires parties vous laissent peut-tre perplexe : comment utiliser un tel arsenal pour venir bout d'un problme rel ? Cette partie expose quelques conseils de base pour concevoir une application en C++, ainsi qu'une srie de questions/rponses sur le langage.
Conseils
Ce chapitre aborde quelques conseils de programmation orienteobjets en C++. Il ne s'agit pas d'noncer des rgles d'or, mais plutt de donner quelques pistes pour dbuter. Par la suite, l'exprience aidant, vous pourrez concevoir et coder dans un style qui ne vous sera dict par personne...
M a l g r tout le soin p o u r les sparer, 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
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 dbut. 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 oriente-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.
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 ide 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 crer 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 franais. 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 lies 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 ralit du p r o b l m e . Inutile,
Conseils
167
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 diffrence 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 dtaillerons p a s le dernier point, d o n t le rle 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'hritage. La f o r m u l a t i o n est u n e sorte de fonctionne trs 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 spcialisation 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'hritage 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 entits 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 hritent 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 frquentes, 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 spcialisation d e s v o i t u res. On p e u t aussi crer u n e classe vhicule, 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
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 dclars 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 indpendamment. 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 reprsenter cette relation en t e r m e de l a n g a g e orient-objet. Soit l'objet B est dfini 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 utiliss 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'intermdiaire d ' u n e autre classe qui centralise les entres.
L e s classes sont c o n n u e s et leurs relations identifies. Il faut m a i n t e n a n t dtailler 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 dvoiler un m i n i m u m D a n s l e m m e ordre d'ides, 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 manire 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'extrieur, 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
valuer l'ensemble
C o n f r o n t e z plusieurs scnarios votre architecture de class e s , e t vrifiez 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 fonctionnalits 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'avre 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 faon de parler.
V o t r e projet est dtaill 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 tche. 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 rutilisabilit, 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 dcrivez 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'hritage. 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
Quels outils ?
Conseils
171
telle factorisation rside 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. Exemple Vous concevez un environnement graphique compos de divers objets : fentres, 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 rfrence 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 bnficions d ' u n autre atout : tous les objets fentres, 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 orients-objets est l a rutilisabilit 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'hritage q u e n o u s a v o n s dtaill 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 gnricit 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'hritage. U n e x e m p l e parfait serait u n e fonction d e rotation virtuelle dfinie 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 srie d e c l a s s e s consiste crer u n e classe de b a s e abstraite, o des fonctions virtuelles p u r e s sont dclares. C e g e n r e d e c l a s s e fixe u n c a dre gnral d'utilisation : les classes drives d o i v e n t dfinir les fonctions virtuelles p u r e s , en r e s p e c t a n t leur entte. 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 crerait d o n c a u c u n objet, m a i s qui dfinirait les enttes 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 hritent d'ObjetVirtuel (voir s c h m a p a g e suivante).
172
La gnricit et les templates Les templates permettent de paramtrer 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 forcm 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'entte 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 ) .
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 intressant d e c o n s i d r e r q u e l q u e s points : Respectez la cohrence des fonctions d'accs U n e classe c o m p t e trs 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 dclar 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'allgement et de clart.
Prfrez 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 hrites. 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 drives 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 intressant d'utiliser pro-
tected.
Respectez la symtrie 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 libre. 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 diffrente 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 tche d'initialisation un c o n s t r u c t e u r irresponsable. Le rle du
74
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 rle 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 dj 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'ide 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, redfinissez l'oprateur = 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 hrit 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 redfinissiez l'oprateur 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, redfinissez 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-Rponses
Si vous n'avez pas trouv les rponses vos questions dans les chapitres prcdents ou dans l'index, tentez votre chance ici. Mme si vous ne vous posez pas de question, il peut tre instructif de lire les rponses... Q u ' e s t - c e q u ' u n c o n s t r u c t e u r par dfaut ? 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 dfaut. L e C + + g n r e u n c o n s t r u c t e u r p a r dfaut s i v o u s n ' a v e z dfini a u c u n constructeur. Un c o n s t r u c t e u r p a r dfaut 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 initialiss. 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 drive Utilisez u n e liste d'initialisation (plus de dtails p a g e 4 7 ) .
class Drive : public Base { public : Drive ( ) : B a s e O { /* ... */ }
} ;
76
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'oprateur = 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 rle 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 ralise p a r l'oprateur -. 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 forcment tre dclar 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'oprateur 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'oprateur global. O r , ce dernier n ' a p a s accs 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 : dclarer 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'oprateur < < 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-Rponses
177
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 dfinis d a n s i o s t r e a m . h , v o u s bnficiez 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 prciser 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 intgrent les vrifications d e type ! 2. V o u s p o u v e z redfinir 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 prdfinis, 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 obligs 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 frres 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'intrt 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 plutt qu'au dbut ? P e r s o n n e l l e m e n t , je prfre dclarer 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 prcis. M a i s les dclarations tardives ont leur c h a r m e : elles p e u v e n t acclrer le p r o g r a m m e . En effet, la cration 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 intrt les dclarer d a n s A plutt 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
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 dclarer d e s fonctions d'accs 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 , insr 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 dclare 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 dclar 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 drives. Il se p e u t g a l e m e n t q u e votre hritage 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'hritage, s p c i a l e m e n t page 39.
Index
Symboles
surcharger 92 surcharger 57
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 92 [] surcharger 142 A accs aux membres dans un hritage 39 affectation oprateur= 57 affichage cout, cin et cerr 89 formater 94 surcharger 92 allocation mmoire
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
conversion de pointeurs 104 de base 38 dclaration 25 dfinir 15 drive 38 diffrences avec les objets 14 template 119 commentaires 63 ruse de sioux 64 compilateur principes de base 149 compilation spare 149 conseils 151 make 155 conception conseils 166 conseils de codage 170 de conception 166 constantes 67 et compilation spare 159 paramtres 70 propres une classe 71 constructeurs 27 copie 31 et hritage 43 ordre d'appel dans un hritage multiple 129 par dfaut 175 vue d'ensemble 27 conversion constructeur 35 de classe vers un type 61 de type vers une classe 35 drive* vers base* 42; 104 copie constructeur 31 par dfaut 58 cout 89 surcharger 92 tableau rcapitulatif 99 D dec 97
dclaration de classe 25 n'importe o 73 dfinir classe 15 fonction-membre 16 delete 83 syntaxe 84 dmarrer en C + + 20 drive classe... 38 destructeurs 27 et hritage 44 virtuels 106 vue d'ensemble 27
E
encapsulation 14 erreurs gestion des... 137 exceptions 137 crer ses propres... 140 en boucle 146 intercepter plusieurs... 139 non intercepte 145 spcifier dans l'entte 143 extern "nom" 160
F
fichiers sparation en plusieurs... 149 fonctions amies 123 dfinir fonction-membre 16 inline 21 redfinition de fonction-membre 41 retournant une rfrence 115 spcifier les exceptions dans un entte 143 templates 118 virtuelles 101 virtuelles pures 107
Index
183
formater les sorties 94 friend classes et fonctions 123 et o p r a t e u r 176 et religion 178
G
langages travailler avec d'autres... 160 listes d'initialisations 47 M make 155 malloc la fin du... 83 modles Voir templates N new 83 syntaxe 84 notation scientifique 97 O objet accder la partie public 17 crer 17 diffrences avec les classes 14 pourquoi comparer deux objets identiques 61 provisoire et rfrences 115 oct 97 oprateurs Voir Symboles, au dbut de l'index de conversion de classe vers un type 61 surcharge (vue d'ensemble) 54
hritage accs aux membres 39 conseils 167 conversion de pointeurs 104 et constructeurs 43 et destructeurs 44 exemple complet 45 multiple 127 multiple, duplication de donnes 131 multiple, masquage de la virtualit 133 rsum 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
P
paramtres par dfaut 65 passage par rfrence 113 patrons Voir templates pointeurs sur classe de base 104 polymorphisme 101 printf la fin du... 89 protected 40 conseils 173
184
Q
questions 175 R redclarations comment viter les... 157 redfinition d'une fonction-membre 41 rfrences 113 et objets provisoires 115 retournes par une fonction 115 rsum 1re partie 75 rutilisabilit 171 S set_terminate 145 set_unexpected 144 setfill 97 setiosflags 95 setprecision 95 setw 94 sioux ruse de 64 static 24 porte 158 surcharge 51
T
template ambigut 122 templates 117 terminate 145 this 23 pourquoi retourner *this? 60 throw 140 try 138
U
unexpected 144 V virtualit 101 destructeurs 106 hritage virtuel 135 masquage dans un hritage multiple 133 pure 107