Vous êtes sur la page 1sur 53

Facult des Sciences de Luminy e

Dpartement dInformatique e

Le langage C++
Henri Garreta

Table des mati`res e


1 Elments pralables e e 1.1 Placement des dclarations de variables . . . . e 1.2 Boolens . . . . . . . . . . . . . . . . . . . . . . e 1.3 Rfrences . . . . . . . . . . . . . . . . . . . . . ee 1.3.1 Notion . . . . . . . . . . . . . . . . . . . 1.3.2 Rfrences param`tres des fonctions . . ee e 1.3.3 Fonctions renvoyant une rfrence . . . ee 1.3.4 Rfrences sur des donnes constantes . ee e 1.3.5 Rfrences ou pointeurs ? . . . . . . . . ee 1.4 Fonctions en ligne . . . . . . . . . . . . . . . . 1.5 Valeurs par dfaut des arguments des fonctions e 1.6 Surcharge des noms de fonctions . . . . . . . . 1.7 Appel et dnition de fonctions crites en C . . e e 1.8 Entres-sorties simples . . . . . . . . . . . . . . e 1.9 Allocation dynamique de mmoire . . . . . . . e . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3 3 3 3 3 4 4 4 5 5 6 6 6 7 8 8 8 9 9 10 10 11 11 11 11 12 13 13 15 15 16 17 18 19 19 19 20 20 21 21 21 23

2 Classes 2.1 Classes et objets . . . . . . . . . . . . . . . . . . . . . 2.2 Acc`s aux membres . . . . . . . . . . . . . . . . . . . . e 2.2.1 Acc`s aux membres dun objet . . . . . . . . . e 2.2.2 Acc`s ` ses propres membres, acc`s ` soi-mme e a e a e 2.2.3 Membres publics et privs . . . . . . . . . . . . e 2.2.4 Encapsulation au niveau de la classe . . . . . . 2.2.5 Structures . . . . . . . . . . . . . . . . . . . . . 2.3 Dnition des classes . . . . . . . . . . . . . . . . . . . e 2.3.1 Dnition spare et oprateur de rsolution de e e e e e 2.3.2 Fichier den-tte et chier dimplmentation . . e e 2.4 Constructeurs . . . . . . . . . . . . . . . . . . . . . . . 2.4.1 Dnition de constructeurs . . . . . . . . . . . e 2.4.2 Appel des constructeurs . . . . . . . . . . . . . 2.4.3 Constructeur par dfaut . . . . . . . . . . . . . e 2.4.4 Constructeur par copie (clonage) . . . . . . . . 2.5 Construction des objets membres . . . . . . . . . . . . 2.6 Destructeurs . . . . . . . . . . . . . . . . . . . . . . . 2.7 Membres constants . . . . . . . . . . . . . . . . . . . . 2.7.1 Donnes membres constantes . . . . . . . . . . e 2.7.2 Fonctions membres constantes . . . . . . . . . 2.8 Membres statiques . . . . . . . . . . . . . . . . . . . . 2.8.1 Donnes membres statiques . . . . . . . . . . . e 2.8.2 Fonctions membres statiques . . . . . . . . . . 2.9 Amis . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2.9.1 Fonctions amies . . . . . . . . . . . . . . . . . . 2.9.2 Classes amies . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . porte e . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

3 Surcharge des oprateurs e 3.1 Principe . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3.1.1 Surcharge dun oprateur par une fonction membre . . . e 3.1.2 Surcharge dun oprateur par une fonction non membre e 3.2 Quelques exemples . . . . . . . . . . . . . . . . . . . . . . . . . 3.2.1 Injection et extraction de donnes dans les ux . . . . . e 3.2.2 Aectation . . . . . . . . . . . . . . . . . . . . . . . . . 3.3 Oprateurs de conversion . . . . . . . . . . . . . . . . . . . . . e 3.3.1 Conversion vers un type classe . . . . . . . . . . . . . . 3.3.2 Conversion dun objet vers un type primitif . . . . . . . 4 Hritage e 4.1 Classes de base et classes drives . . . . . . . . . . . . e e 4.2 Hritage et accessibilit des membres . . . . . . . . . . e e 4.2.1 Membres protgs . . . . . . . . . . . . . . . . e e 4.2.2 Hritage priv, protg, public . . . . . . . . . e e e e 4.3 Rednition des fonctions membres . . . . . . . . . . . e 4.4 Cration et destruction des objets drivs . . . . . . . e e e 4.5 Rcapitulation sur la cration et destruction des objets e e 4.5.1 Construction . . . . . . . . . . . . . . . . . . . 4.5.2 Destruction . . . . . . . . . . . . . . . . . . . . 4.6 Polymorphisme . . . . . . . . . . . . . . . . . . . . . . 4.6.1 Conversion standard vers une classe de base . . 4.6.2 Type statique, type dynamique, gnralisation e e 4.7 Fonctions virtuelles . . . . . . . . . . . . . . . . . . . . 4.8 Classes abstraites . . . . . . . . . . . . . . . . . . . . . 4.9 Identication dynamique du type . . . . . . . . . . . . 4.9.1 Loprateur dynamic cast . . . . . . . . . . . . e 4.9.2 Loprateur typeid . . . . . . . . . . . . . . . e . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . . . . . . .

24 24 24 24 25 25 27 28 28 29 29 29 30 30 31 32 34 34 34 35 35 35 37 39 41 42 43 44

5 Mod`les (templates) e 44 5.1 Mod`les de fonctions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 45 e 5.2 Mod`les de classes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 46 e 5.2.1 Fonctions membres dun mod`le . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 47 e 6 Exceptions 6.1 Principe et syntaxe . . . . 6.2 Attraper une exception . . 6.3 Dclaration des exceptions e 6.4 La classe exception . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . quune fonction laisse chapper e . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 48 48 50 51 52

31 mai 2006 henri.garreta@luminy.univ-mrs.fr

Elments pralables e e

Ce document est le support du cours sur le langage C++, considr comme une extension de C (tel que ee normalis par lISO en 1990), langage prsum bien connu. e e e Attention, la prsentation faite ici est dsquilibre : des concepts importants ne sont pas expliqus, pour e ee e e la raison quils sont raliss en C++ comme en C, donc supposs acquis. En revanche, nous insistons sur les e e e dirences entre C et C++ et notamment sur tous les lments  orients objets  que C++ ajoute ` C. e ee e a Cette premi`re section expose un certain nombre de notions qui, sans tre directement lis ` la mthodologie e e e a e objets, font dj` appara C++ comme une amlioration notable de C. ea tre e

1.1

Placement des dclarations de variables e

En C les dclarations de variables doivent appara au dbut dun bloc. En C++, au contraire, on peut e tre e mettre une dclaration de variable partout o` on peut mettre une instruction1 . e u Cette dirence para mineure, mais elle est importante par son esprit. Elle permet de repousser la dclaration e t e dune variable jusqu` lendroit du programme o` lon dispose dassez lments pour linitialiser. On lutte aussi a u ee contre les variables  dj` dclares mais non encore initialises  qui sont un important vivier de bugs dans les ea e e e programmes. Exemple, lemploi dun chier. Version C : { FILE *fic; ... obtention de nomFic, le nom du chier ` ouvrir a Danger ! Tout emploi de fic ici est erron e ... fic = fopen(nom, "w"); ... ici, lemploi de fic est lgitime e ... } Version C++ : { ... obtention de nomFic, le nom du chier ` ouvrir a ici pas de danger dutiliser fic ` tort a ... FILE *fic = fopen(nom, "w"); ... ici, lemploi de fic est lgitime e ... }

1.2

Boolens e

En plus des types dnis par lutilisateur (ou classes, une des notions fondamentales de la programmation e oriente objets) C++ poss`de quelques types qui manquaient ` C, notamment le type boolen et les types e e a e rfrences. ee Le type bool (pour boolen) comporte deux valeurs : false et true. Contrairement ` C : e a le rsultat dune opration logique (&&, ||, etc.) est de type boolen, e e e a u e l` o` une condition est attendue on doit mettre une expression de type boolen et il est dconseill de prendre les entiers pour des boolens et rciproquement. e e e e Consquence pratique : ncrivez pas  if (x) ...  au lieu de  if (x != 0) ...  e e

1.3
1.3.1

Rfrences ee
Notion

A ct des pointeurs, les rfrences sont une autre mani`re de manipuler les adresses des objets placs dans oe ee e e la mmoire. Une rfrence est un pointeur gr de mani`re interne par la machine. Si T est un type donn, le e ee ee e e
1 Bien

entendu, une r`gle absolue reste en vigueur : une variable ne peut tre utilise quapr`s quelle ait t dclare. e e e e e e e e

type  rfrence sur T  se note T &. Exemple : ee int i; int & r = i; // r est une rfrence sur i ee

Une valeur de type rfrence est une adresse mais, hormis lors de son initialisation, toute opration eectue ee e e sur la rfrence agit sur lobjet rfrenc, non sur ladresse. Il en dcoule quil est obligatoire dinitialiser une ee ee e e rfrence lors de sa cration ; apr`s, cest trop tard : ee e e r = j; // ceci ne transforme pas r en une rfrence ee // sur j mais copie la valeur de j dans i

1.3.2

Rfrences param`tres des fonctions ee e

Lutilit principale des rfrences est de permettre de donner aux fonctions des param`tres modiables, sans e ee e utiliser explicitement les pointeurs. Exemple : void permuter(int & a, int & b) { int w = a; a = b; b = w; } Lors dun appel de cette fonction, comme permuter(t[i], u); ses param`tres formels a et b sont initialiss avec les adresses des param`tres eectifs t[i] et u, mais cette e e e utilisation des adresses reste cache, le programmeur na pas ` sen occuper. Autrement dit, ` loccasion dun e a a tel appel, a et b ne sont pas des variables locales de la fonction recevant des copies des valeurs des param`tres, e mais dauthentiques synonymes des variables t[i] et u. Il en rsulte que lappel ci-dessus permute eectivement e les valeurs des variables t[i] et u2 . 1.3.3 Fonctions renvoyant une rfrence ee

Il est possible dcrire des fonctions qui renvoient une rfrence comme rsultat. Cela leur permet dtre le e ee e e membre gauche dune aectation, ce qui donne lieu ` des expressions lgantes et ecaces. a ee Par exemple, voici comment une fonction permet de simuler un tableau index par des cha e nes de caract`res3 : e char *noms[N]; int ages[N]; int & age(char *nom) { for (int i = 0; i < N; i++) if (strcmp(nom, noms[i]) == 0) return ages[i]; } Une telle fonction permet lcriture dexpressions qui ressemblent ` des acc`s ` un tableau dont les indices e a e a seraient des cha nes : age("Amlie") = 20; e ou encore age("Benjamin")++; 1.3.4 Rfrences sur des donnes constantes ee e

Lors de lcriture dune fonction il est parfois souhaitable dassocier lecacit des arguments rfrences e e ee (puisque dans le cas dune rfrence il ny a pas recopie de la valeur de largument) et la scurit des arguments ee e e par valeur (qui ne peuvent en aucun cas tre modis par la fonction). e e Cela peut se faire en dclarant des arguments comme des rfrences sur des objets immodiables, ou e ee constants, ` laide du qualieur const : a
2 Notez quune telle chose est impossible en C, o` une fonction appele par lexpression permuter(t[i], u) ne peut recevoir que u e des copies des valeurs de de t[i] et u. 3 Cest un exemple simpliste, pour faire court nous ny avons pas trait le cas de labsence de la cha e ne cherche. e

void uneFonction(const unType & arg) { ... ici arg nest pas une recopie de largument eectif (puisque rfrence) ee mais il ne peut pas tre modi par la fonction (puisque const) e e ... } 1.3.5 Rfrences ou pointeurs ? ee

Il existe quelques situations, nous les verrons plus loin, o` les rfrences se rv`lent indispensables. Cependant, u ee e e la plupart du temps elles font double emploi avec les pointeurs et il nexiste pas de crit`res simples pour choisir e des rfrences plutt que des pointeurs ou le contraire. ee o Par exemple, la fonction permuter montre ` la page prcdente peut scrire aussi : e a e e e void permuter(int *a, int *b) { int w = *a; *a = *b; *b = w; } son appel scrit alors e permuter(&t[i], &u); Attention On notera que loprateur & (` un argument) a une signication tr`s dirente selon le contexte e a e e dans lequel il appara : t employ dans une dclaration, comme dans e e int &r = x; il sert ` indiquer un type rfrence :  r est une rfrence sur un int  a ee ee employ ailleurs que dans une dclaration il indique lopration  obtention de ladresse , comme dans e e e lexpression suivante qui signie  aecter ladresse de x ` p  : a p = &x; enn, on doit se souvenir quil y a en C++, comme en C, un oprateur & binaire (` deux arguments) qui e a exprime la conjonction bit-`-bit de deux mots-machine. a

1.4

Fonctions en ligne

Normalement, un appel de fonction est une rupture de squence : ` lendroit o` un appel gure, la machine e a u cesse dexcuter squentiellement les instructions en cours ; les arguments de lappel sont disposs sur la pile e e e dexcution, et lexcution continue ailleurs, l` o` se trouve le code de la fonction. e e a u Une fonction en ligne est le contraire de cela : l` o` lappel dune telle fonction appara il ny a pas de a u t rupture de squence. Au lieu de cela, le compilateur remplace lappel de la fonction par le corps de celle-ci, en e mettant les arguments aectifs ` la place des arguments formels. a Cela se fait dans un but decacit : puisquil ny a pas dappel, pas de prparation des arguments, pas de e e rupture de squence, pas de retour, etc. Mais il est clair quun tel traitement ne peut convenir qu` des fonctions e a frquemment appeles (si une fonction ne sert pas souvent, ` quoi bon la rendre plus rapide ?) de petite taille e e a (sinon le code compil risque de devenir dmesurment volumineux) et rapides (si une fonction eectue une e e e opration lente, le gain de temps obtenu en supprimant lappel est ngligeable). e e En C++ on indique quune fonction doit tre traite en ligne en faisant prcder sa dclaration par le mot e e e e e inline : inline int abs(int x) { return x >= 0 ? x : -x; } Les fonctions en ligne de C++ rendent le mme service que les macros (avec arguments) de C, mais on notera e que les fonctions en ligne sont plus ables car, contrairement ` ce qui se passe pour les macros, le compilateur a peut y eectuer tous les contrles syntaxiques et smantiques quil fait sur les fonctions ordinaires. o e La porte dune fonction en ligne est rduite au chier o` la fonction est dnie. Par consquent, de telles e e u e e fonctions sont gnralement crites dans des chiers en-tte (chiers  .h ), qui doivent tre inclus dans tous e e e e e les chiers comportant des appels de ces fonctions.

1.5

Valeurs par dfaut des arguments des fonctions e


void trier(void *table, int nbr, int taille = sizeof(void *), bool croissant = true);

Les param`tres formels dune fonction peuvent avoir des valeurs par dfaut. Exemple : e e Lors de lappel dune telle fonction, les param`tres eectifs correspondants peuvent alors tre omis (ainsi que e e les virgules correspondantes), les param`tres formels seront initialiss avec les valeurs par dfaut. Exemples : e e e trier(t, n, sizeof(int), false); trier(t, n, sizeof(int)); // croissant = true trier(t, n); // taille = sizeof(void *), croissant = true

1.6

Surcharge des noms de fonctions

La signature dune fonction est la suite des types de ses arguments formels (et quelques lments supplee e mentaires, que nous verrons plus loin). Le type du rsultat rendu par la fonction ne fait pas partie de sa e signature. La surcharge des noms des fonctions consiste en ceci : en C++ des fonctions direntes peuvent avoir le e mme nom, ` la condition que leurs signatures soient assez direntes pour que, lors de chaque appel, le nombre e a e et les types des arguments eectifs permettent de choisir sans ambigu e la fonction ` appeler. Exemple : t a int puissance(int x, int n) { calcul de xn avec x et n entiers } double puissance(double x, int n) { calcul de xn avec x ottant et n entier } double puissance(double x, double y) { calcul de xy avec x et y ottants } On voit sur cet exemple lintrt de la surcharge des noms des fonctions : la mme notion abstraite  x ee e a ` la puissance n  se traduit par des algorithmes tr`s dirents selon que n est entier (xn = x.x...x) ou rel e e e (xn = en log x ) ; de plus, pour n entier, si on veut que xn soit entier lorsque x est entier, on doit crire deux e fonctions distinctes, une pour x entier et une pour x rel. e Or le programmeur naura quun nom ` conna a tre, puissance. Il crira dans tous les cas e c = puissance(a, b); le compilateur se chargeant de choisir la fonction la plus adapte, selon les types de a et b. e Notes. 1. Le type du rsultat rendu par la fonction ne fait pas partie de la signature. Par consquent, on e e ne peut pas donner le mme nom ` deux fonctions qui ne dirent que par le type du rsultat quelles rendent. e a e e 2. Lors de lappel dune fonction surcharge, la fonction eectivement appele est celle dont la signature e e correspond avec les types des arguments eectifs de lappel. Sil y a une correspondance exacte, pas de probl`me. e Sil ny a pas de correspondance exacte, alors des r`gles complexes (trop complexes pur les expliquer ici) e sappliquent, pour dterminer la fonction ` appeler. Malgr ces r`gles, il existe de nombreux cas de gure e a e e ambigus, que le compilateur ne peut pas rsoudre. e Par exemple, si x est ottant et n entier, lappel puissance(n, x) est erron, alors quil aurait t correct sil e ee y avait eu une seule dnition de la fonction puissance (les conversions entier ottant ncessaires auraient e e alors t faites). ee

1.7

Appel et dnition de fonctions crites en C e e

Pour que la compilation et ldition de liens dun programme avec des fonctions surcharges soient possibles, e e le compilateur C++ doit fabriquer pour chaque fonction un nom long comprenant le nom de la fonction et une reprsentation code de sa signature ; cest ce nom long qui est communiqu ` lditeur de liens. e e ea e Or, les fonctions produites par un compilateur C nont pas de tels noms longs ; si on ne prend pas de disposition particuli`re, il sera donc impossible dappeler dans un programme C++ une fonction crite en C, e e ou rciproquement. On remdie ` cette impossibilit par lutilisation de la dclaration  extern C  : e e a e e extern "C" { dclarations et dnitions dlments e e ee dont le nom est reprsent ` la mani`re de C e ea e }

1.8

Entres-sorties simples e

Cette section traite de lutilisation simple des ux standard dentre-sortie, cest-`-dire la mani`re de faire e a e en C++ les oprations quon fait habituellement en C avec les fonctions printf et scanf. e Un programme qui utilise les ux standard dentre-sortie doit comporter la directive e #include <iostream.h> ou bien, si vous utilisez un compilateur rcent et que vous suivez de pr`s les recommandations de la norme4 : e e #include <iostream> using namespace std; Les ux dentre-sortie sont reprsents dans les programmes par les trois variables, prdclares et prinie e e e e e e tialises, suivantes : e cin, le ux standard dentre (lquivalent du stdin de C), qui est habituellement associ au clavier du e e e poste de travail, cout, le ux standard de sortie (lquivalent du stdout de C), qui est habituellement associ ` lcran du e ea e poste de travail, cerr, le ux standard pour la sortie des messages derreur (lquivalent du stderr de C), galement associ e e e a e ` lcran du poste de travail. Les critures et lectures sur ces units ne se font pas en appelant des fonctions, mais ` laide des oprateurs e e a e <<, appel oprateur dinjection ( injection  de donnes dans un ux de sortie), et >>, appel oprateur e e e e e dextraction ( extraction  de donnes dun ux dentre). Or, le mcanisme de la surcharge des oprateurs e e e e (voir la section 3) permet la dtection des types des donnes ` lire ou ` crire. Ainsi, le programmeur na pas ` e e a ae a sencombrer avec des spcications de format. e La syntaxe dun injection de donne sur la sortie standard cout est : e cout << expression ` crire ae le rsultat de cette expression est lobjet cout lui-mme. On peut donc lui injecter une autre donne, puis encore e e e une, etc. : ((cout << expression ) << expression ) << expression ce qui, loprateur << tant associatif ` gauche, se note aussi, de mani`re bien plus agrable : e e a e e cout << expression << expression << expression Le mme procd existe avec lextraction depuis cin. Par exemple, le programme suivant est un programme e e e C++ complet. Il calcule xn (pour x ottant et n entier). #include <iostream.h> double puissance(double x, int n) { algorithme de calcul de xn } void main() { double x; int n; cout << "Donne x et n : "; cin >> x >> n; cout << x << "^" << n << " = " << puissance(x, n) << "\n"; } Exemple dexcution de ce programme : e Donne x et n : 2 10 2^10 = 1024
4 Cest-`-dire, si vous utilisez les espaces de noms, ou namespace (tous les lments de la biblioth`que standard sont dans lespace a ee e de noms std).

1.9

Allocation dynamique de mmoire e

Des dirences entre C et C++ existent aussi au niveau de lallocation et de la restitution dynamique de e mmoire. e Les fonctions malloc et free de la biblioth`que standard C sont disponibles en C++. Mais il est fortement e conseill de leur prfrer les oprateurs new et delete. La raison principale est la suivante : les objets crs ` e ee e ee a laide de new sont initialiss ` laide des constructeurs (cf. section 2.4) correspondants, ce que ne fait pas malloc. e a De mme, les objets libers en utilisant delete sont naliss en utilisant le destructeur (cf. section 2.6) de la e e e classe correspondante, contrairement ` ce que fait free. a Pour allouer un unique objet : new type Pour allouer un tableau de n objets : new type[n] Dans les deux cas, new renvoie une valeur de type pointeur sur un type, cest-`-dire  type * . Exemples (on a suppose que Machin est un type dni par ailleurs) : e Machin *ptr = new Machin; int *tab = new int[n]; // un objet Machin // un tableau de n int

Si type est une classe possdant un constructeur par dfaut, celui-ci sera appel une fois (cas de lallocation e e e dun objet simple) ou n fois (allocation dun tableau dobjets) pour construire lobjet ou les objets allous. Si e type est une classe sans constructeur par dfaut, une erreur sera signale par le compilateur. e e Pour un tableau, la dimension n peut tre donne par une variable, cest-`-dire tre inconnue lors de la e e a e compilation, mais la taille des composantes doit tre connue. Il en dcoule que dans le cas dun tableau ` e e a plusieurs indices, seule la premi`re dimension peut tre non constante : e e double (*M)[10]; ... acquisition de la valeur de n ... M = new double[n][10]; // pointeur de tableaux de 10 double

// allocation dun tableau de n tableaux de 10 double

M pourra ensuite tre utilis comme une matrice ` n lignes et 10 colonnes. e e a Loprateur delete restitue la mmoire dynamique. Si la valeur de p a t obtenue par un appel de new, on e e ee crit e delete p; dans le cas dun objet qui nest pas un tableau, et delete [] p; si ce que p pointe est un tableau. Les consquences dune utilisation de delete l` o` il aurait fallu utiliser e a u delete[], ou inversement, sont imprvisibles. e

2
2.1

Classes
Classes et objets

Un objet est constitu par lassociation dune certaine quantit de mmoire, organise en champs, et dun e e e e ensemble de fonctions, destines principalement ` la consultation et la modication des valeurs de ces champs. e a La dnition dun type objet sappelle une classe. Dun point de vue syntaxique, cela ressemble beaucoup ` e a une dnition de structure, sauf que e e e le mot rserv class remplace5 le mot struct, certains champs de la classe sont des fonctions. Par exemple, le programme suivant est une premi`re version dune classe Point destine ` reprsenter les e e a e points achs dans une fentre graphique : e e
5 En

fait on peut aussi utiliser struct, voyez la section 2.2.5.

class Point { public: void afficher() { cout << ( << x << , << y << ); } void placer(int a, int b) { validation des valeurs de a et b; x = a; y = b; } private: int x, y; }; Chaque objet de la classe Point comporte un peu de mmoire, compose de deux entiers x et y, et de deux e e fonctions : afficher, qui acc`de ` x et y sans les modier, et placer, qui change les valeurs de x et y. e a Lassociation de membres et fonctions au sein dune classe, avec la possibilit de rendre privs certains dentre e e eux, sappelle lencapsulation des donnes. Intrt de la dmarche : puisquelles ont t dclares prives, les e ee e ee e e e coordonnes x et y dun point ne peuvent tre modies autrement que par un appel de la fonction placer e e e sur ce point. Or, en prenant les prcautions ncessaires lors de lcriture de cette fonction (ce que nous avons e e e not  validation des valeurs de a et b ) le programmeur responsable de la classe Point peut garantir aux e utilisateurs de cette classe que tous les objets cres auront toujours des coordonnes correctes. Autrement dit : e e chaque objet peut prendre soin de sa propre cohrence interne. e Autre avantage important : on pourra ` tout moment changer limplmentation (i.e. les dtails internes) de a e e la classe tout en ayant la certitude quil ny aura rien ` changer dans les programmes qui lutilisent. a Note. Dans une classe, les dclarations des membres peuvent se trouver dans un ordre quelconque, mme e e lorsque ces membres se rfrencent mutuellement. Dans lexemple prcdent, le membre afficher mentionne ee e e les membres x et y, dont la dnition se trouve apr`s celle de afficher. e e Jargon. On appelle objet une donne dun type classe ou structure, e fonction membre un membre dune classe qui est une fonction6 , donne membre un membre qui est une variable7 . e

2.2
2.2.1

Acc`s aux membres e


Acc`s aux membres dun objet e

On acc`de aux membres des objets en C++ comme on acc`de aux membres des structures en C. Par exemple, e e a ` la suite de la dnition de la classe Point donne prcdemment on peut dclarer des variables de cette classe e e e e e en crivant8 : e Point a, b, *pt; // deux points et un pointeur de point Dans un contexte o` le droit de faire un tel acc`s est acquis (cf. section 2.2.3) lacc`s aux membres du point u e e a scrit : e a.x = 0; d = a.distance(b); pt = new Point; pt->x = 0; d = pt->distance(b); // un acc`s bien crit au membre x du point a e e // un appel bien crit de la fonction distance de lobjet a e // allocation dynamique dun point // un acc`s bien crit au membre x du point point par pt e e e // un appel de la fonction distance de lobjet point par pt e

Si on suppose que le pointeur pt a t initialis, par exemple par une expression telle que ee e alors des acc`s analogues aux prcdents scrivent : e e e e

A propos de lacc`s ` un membre dun objet, deux questions se posent. Il faut comprendre quelles sont tout e a a ` fait indpendantes lune de lautre : e lacc`s est-il bien crit ? cest-`-dire, dsigne-t-il bien le membre voulu de lobjet voulu ? e e a e e e a u e e cet acc`s est-il lgitime ? cest-`-dire, dans le contexte o` il est crit, a-t-on le droit dacc`s sur le membre en question ? La question des droits dacc`s est traite ` la section 2.2.3. e e a
la plupart des langages orients objets, les fonctions membres sont appeles mthodes. e e e beaucoup de langages orients objets, les donnes membres sont appeles variables dinstance et aussi, sous certaines e e e conditions, proprits ee 8 Notez que, une fois la classe dclare, il nest pas obligatoire dcrire class devant Point pour y faire rfrence. e e e ee
7 Dans 6 Dans

2.2.2

Acc`s ` ses propres membres, acc`s ` soi-mme e a e a e

Quand des membres dun objet apparaissent dans une expression crite dans une fonction du mme objet e e on dit que ce dernier fait un acc`s ` ses propres membres. e a On a droit dans ce cas ` une notation simplie : on crit le membre tout seul, sans expliciter lobjet en a e e question. Cest ce que nous avons fait dans les fonctions de la classe Point : class Point { ... void afficher() { cout << ( << x << , << y << ); } ... }; Dans la fonction afficher, les membres x et y dont il question sont ceux de lobjet ` travers lequel on aura a appel cette fonction. Autrement dit, lors dun appel comme e unPoint.afficher(); le corps de cette fonction sera quivalent ` e a cout << ( << unPoint.x << , << unPoint.y << ); ` ` Acces a soi-meme. Il arrive que dans une fonction membre dun objet on doive faire rfrence ` lobjet ee a (tout entier) ` travers lequel on a appel la fonction. Il faut savoir que dans une fonction membre9 on dispose a e de la pseudo variable this qui reprsente un pointeur vers lobjet en question. e Par exemple, la fonction acher peut scrire de mani`re quivalente, mais cela na aucun intrt : e e e e e void afficher() { cout << ( << this->x << , << this->y << ); } Pour voir un exemple plus utile dutilisation de this imaginons quon nous demande dajouter ` la classe a Point deux fonctions boolennes, une pour dire si deux points sont gaux, une autre pour dire si deux points e e sont le mme objet. Dans les deux cas le deuxi`me point est donn par un pointeur : e e e class Point { ... bool pointEgal(Point *pt) { return pt->x == x && pt->y == y; } bool memePoint(Point *pt) { return pt == this; } ... };

2.2.3

Membres publics et privs e

Par dfaut, les membres des classes sont privs. Les mots cls public et private permettent de modier les e e e droits dacc`s des membres : e class nom { les membres dclars ici sont privs e e e public: les membres dclars ici sont publics e e private: les membres dclars ici sont privs e e e etc. };
9 Sauf

dans le cas dune fonction membre statique, voir la section 2.8.

10

Les expressions public: et private: peuvent appara tre un nombre quelconque de fois dans une classe. Les membres dclars apr`s private: (resp. public:) sont privs (resp. publics) jusqu` la n de la classe, ou e e e e a jusqu` la rencontre dune expression public: (resp. private:). a Un membre public dune classe peut tre accd partout o` il est visible ; un membre priv ne peut tre e e e u e e accd que depuis une fonction membre de la classe (les notions de membre protg, cf. section 4.2.1, et de e e e e classes et fonctions amies, cf. section 2.9, nuanceront cette armation). Si p est une expression de type Point : dans une fonction qui nest pas membre ou amie de la classe Point, les expressions p.x ou p.y pourtant syntaxiquement correctes et sans ambigu e, constituent des acc`s illgaux aux membres privs x et y de t e e e la classe Point, e e les expressions p.afficher() ou p.placer(u, v) sont des acc`s lgaux aux membres publics afficher et placer, qui se rsolvent en des acc`s parfaitement lgaux aux membres p.x et p.y. e e e 2.2.4 Encapsulation au niveau de la classe

Les fonctions membres dune classe ont le droit daccder ` tous les membres de la classe : deux objets e a de la mme classe ne peuvent rien se cacher. Par exemple, le programme suivant montre notre classe Point e augmente dune fonction pour calculer la distance dun point ` un autre : e a class Point { public: void afficher() { cout << ( << x << , << y << ); } void placer(int a, int b) { validation des valeurs de a et b; x = a; y = b; } double distance(Point autrePoint) { int dx = x - autrePoint.x; int dy = y - autrePoint.y; return sqrt(dx * dx + dy * dy); } private: int x, y; }; Lors dun appel tel que p.distance(q) lobjet p acc`de aux membres privs x et y de lobjet q. On dit que e e C++ pratique lencapsulation au niveau de la classe, non au niveau de lobjet. On notera au passage que, contrairement ` dautres langages orients objets, en C++ encapsuler nest pas a e cacher mais interdire. Les usagers dune classe voient les membres privs de cette derni`re, mais ne peuvent pas e e les utiliser. 2.2.5 Structures

Une structure est la mme chose quune classe mais, par dfaut, les membres y sont publics. Sauf pour ce qui e e touche cette question, tout ce qui sera dit dans la suite ` propos des classes sappliquera donc aux structures : a struct nom { les membres dclars ici sont publics e e private: les membres dclars ici sont privs e e e public: les membres dclars ici sont publics e e etc. };

2.3
2.3.1

Dnition des classes e


Dnition spare et oprateur de rsolution de porte e e e e e e

Tous les membres dune classe doivent tre au moins dclars ` lintrieur de la formule classnom{...} ; e e e a e qui constitue la dclaration de la classe. e 11

Cependant, dans le cas des fonctions, aussi bien publiques que prives, on peut se limiter ` ncrire que leur e a e en-tte ` lintrieur de la classe et dnir le corps ailleurs, plus loin dans le mme chier ou bien dans un autre e a e e e chier. Il faut alors un moyen pour indiquer quune dnition de fonction, crite en dehors de toute classe, est en e e ralit la dnition dune fonction membre dune classe. Ce moyen est loprateur de rsolution de porte, dont e e e e e e la syntaxe est NomDeClasse:: Par exemple, voici notre classe Point avec la fonction distance dnie sparment : e e e class Point { public: ... double distance(Point autrePoint); ... } Il faut alors, plus loin dans le mme chier ou bien dans un autre chier, donner la dnition de la fonction e e  promise  dans la classe Point. Cela scrit : e double Point::distance(Point autrePoint) { int dx = x - autrePoint.x; int dy = y - autrePoint.y; return sqrt(dx * dx + dy * dy); }; Dnir les fonctions membres ` lextrieur de la classe all`ge la dnition de cette derni`re et la rend plus e a e e e e compacte. Mais la question nest pas questhtique, il y a une dirence de taille : les fonctions dnies ` e e e a lintrieur dune classe sont implicitement qualies  en ligne  (cf. section 1.4). e e Consquence : la plupart des fonctions membres seront dnies sparment. Seules les fonctions courtes, e e e e rapides et frquemment appeles mriteront dtre dnies dans la classe. e e e e e 2.3.2 Fichier den-tte et chier dimplmentation e e

En programmation oriente objets,  programmer  cest dnir des classes. Le plus souvent ces classes sont e e destines ` tre utilises dans plusieurs programmes, prsents et ` venir10 . Se pose alors la question : comment e ae e e a disposer le code dune classe pour faciliter son utilisation ? Voici comment on proc`de gnralement : e e e les dnitions des classes se trouvent dans des chiers en-tte (chiers  .h ,  .hpp , etc.), e e chacun des ces chiers en-tte contient la dnition dune seule classe ou dun groupe de classes intimement e e lies ; par exemple, la dnition de notre classe Point pourrait constituer un chier Point.h e e les dnitions des fonctions membres qui ne sont pas dnies ` lintrieur de leurs classes sont crites dans e e a e e des chiers sources (chiers  .cpp  ou  .cp ), aux programmeurs utilisateurs de ces classes sont distribus : e les chiers  .h  le chiers objets rsultant de la compilation des chiers  .cpp  e Par exemple, voici les chiers correspondants ` notre classe Point (toujours tr`s modeste) : a e Fichier Point.h : class Point { public: void placer(int a, int b) { validation de a et b x = a; y = b; } double distance(Point autrePoint); private: int x, y; };
10 La

rutilisabilit du code est une des motivations de la mthodologie oriente objets. e e e e

12

Fichier Point.cpp : #include "Point.h" #include <math.h> double Point::distance(Point autrePoint) { int dx = x - autrePoint.x; int dy = y - autrePoint.y; return sqrt(dx * dx + dy * dy); } La compilation du chier Point.cpp produira un chier objet (nomm gnralement Point.o ou Point.obj). e e e Dans ces conditions, la  distribution  de la classe Point sera compose des deux chiers Point.h et Point.obj, e ce dernier ayant ventuellement t transform en un chier biblioth`que (nomm alors Point.lib ou quelque e ee e e e chose comme a). Bien entendu, tout programme utilisateur de la classe Point devra comporter la directive c #include "Point.h" et devra, une fois compil, tre reli au chier Point.obj ou Point.lib. e e e

2.4
2.4.1

Constructeurs
Dnition de constructeurs e

Un constructeur dune classe est une fonction membre spciale qui : e a le mme nom que la classe, e nindique pas de type de retour, ne contient pas dinstruction return. Le rle dun constructeur est dinitialiser un objet, notamment en donnant des valeurs ` ses donnes membres. o a e Le constructeur na pas ` soccuper de trouver lespace pour lobjet ; il est appel (immdiatement) apr`s que a e e e cet espace ait t obtenu, et cela quelle que soit la sorte dallocation qui a t faite : statique, automatique ou ee ee dynamique, cela ne regarde pas le constructeur. Exemple : class Point { public: Point(int a, int b) { validation des valeurs de a et b x = a; y = b; } ... autres fonctions membres ... private: int x, y; }; Un constructeur de la classe est toujours appel, explicitement (voir ci-dessous) ou implicitement, lorsquun e objet de cette classe est cr, et en particulier chaque fois quune variable ayant cette classe pour type est dnie. ee e Cest le couple dnition de la variable + appel du constructeur qui constitue la ralisation en C++ du e e concept  cration dun objet . Lintrt pour le programmeur est vident : garantir que, d`s leur introduction e ee e e dans un programme, tous les objets sont garnis et cohrents, cest-`-dire viter les variables indnies, au contenu e a e e incertain. Une classe peut possder plusieurs constructeurs, qui doivent alors avoir des signatures direntes : e e

13

class Point { public: Point(int a, int b) { validation de a et b x = a; y = b; } Point(int a) { validation de a x = a; y = 0; } Point() { x = y = 0; } ... private: int x, y; }; Lemploi de param`tres avec des valeurs par dfaut permet de grouper des constructeurs. La classe suivante e e poss`de les mmes constructeurs que la prcdente : e e e e class Point { public: Point(int a = 0, int b = 0) { validation de a et b x = a; y = b; } ... private: int x, y; }; Comme les autres fonctions membres, les constructeurs peuvent tre dclars dans la classe et dnis ailleurs. e e e e Ainsi, la classe prcdente pourrait scrire galement e e e e class Point { public: Point(int a = 0, int b = 0); ... private: int x, y; }; et, ailleurs : Point::Point(int a, int b) { validation de a et b x = a; y = b; } Deux remarques generales. 1. Comme lexemple ci-dessus le montre, lorsquune fonction fait lobjet dune dclaration et dune dnition spares, comme le constructeur Point, les ventuelles valeurs par dfaut e e e e e e des argument concernent la dclaration, non la dnition. e e 2. Lorsquune fonction fait lobjet dune dclaration et dune dnition spares, les noms des arguments ne e e e e sont utiles que pour la dnition. Ainsi, la dclaration du constructeur Point ci-dessus peut scrire galement : e e e e class Point { ... Point(int = 0, int = 0); ... };

14

2.4.2

Appel des constructeurs

Un constructeur est toujours appel lorsquun objet est cre, soit explicitement, soit implicitement. Les e e appels explicites peuvent tre crits sous deux formes : e e Point a(3, 4); Point b = Point(5, 6); Dans le cas dun constructeur avec un seul param`tre, on peut aussi adopter une forme qui rappelle linitiae lisation des variables de types primitifs (` ce propos voir aussi la section 3.3.1) : a Point e = 7; // quivaut ` : Point e = Point(7) e a Un objet allou dynamiquement est lui aussi toujours initialis, au mois implicitement. Dans beaucoup de e e cas il peut, ou doit, tre initialis explicitement. Cela scrit : e e e Point *pt; ... pt = new Point(1, 2); Les constructeurs peuvent aussi tre utiliss pour initialiser des objets temporaires, anonymes. En fait, e e chaque fois quun constructeur est appel, un objet nouveau est cre, mme si cela ne se passe pas ` loccasion e e e a de la dnition dune variable. Par exemple, deux objets sans nom, reprsentant les points (0,0) et (3,4), sont e e crs dans linstruction suivante11 : ee cout << Point(0, 0).distance(Point(3, 4)) << "\n"; Note. Lappel dun constructeur dans une expression comportant un signe = peut prter ` confusion, ` e a a cause de sa ressemblance avec une aectation. Or, en C++, linitialisation et laectation sont deux oprations e distinctes, du moins lorsquelles concernent des variables dun type classe : linitialisation consiste ` donner une a premi`re valeur ` une variable au moment o` elle commence ` exister ; laectation consiste ` remplacer la valeur e a u a a courante dune variable par une autre valeur ; les oprations mises en uvre par le compilateur, constructeur e dans un cas, oprateur daectation dans lautre, ne sont pas les mmes. e e Comment distinguer le  =  dune aectation de celui dune initialisation ? Grossi`rement, lorsque lexprese sion commence par un type, il sagit dune dnition et le signe = correspond ` une initialisation. Exemple : e a Point a = Point(1, 2); // Initialisation de a Cette expression cre la variable a et linitialise en rangeant dans a.x et a.y les valeurs 1 et 2. En revanche, e lorsque lexpression ne commence pas par un type, il sagit dune aectation. Exemple : Point a; ... a = Point(1, 2);

// Affectation de a

Lexpression ci-dessus est une aectation ; elle cre un point anonyme de coordonnes (1,2) et le recopie e e sur la variable a en remplacement de la valeur courante de cette variable, construite peu avant. On arrive au mme rsultat que prcdemment, mais au prix de deux initialisations et une aectation ` la place dune seule e e e e a initialisation. 2.4.3 Constructeur par dfaut e

Le constructeur par dfaut est un constructeur qui peut tre appel sans param`tres : ou bien il nen a pas, e e e e ou bien tous ses param`tres ont des valeurs par dfaut. Il joue un rle remarquable, car il est appel chaque fois e e o e quun objet est cr sans quil y ait appel explicite dun constructeur, soit que le programmeur ne le juge pas ee utile, soit quil nen a pas la possibilit : e Point Point Point Point x; t[10]; *p = new Point; *q = new Point[10]; // // // // quivaut ` : Point x = Point() e a produit 10 appels de Point() quivaut ` : p = new Point() e a produit 10 appels de Point()

` Synthese dun constructeur par defaut. Puisque tout objet doit tre initialis lors de sa cration, si e e e le programmeur crit une classe sans aucun constructeur, alors le compilateur synthtise un constructeur par e e dfaut comme ceci : e
11 Ces objets anonymes ne pouvant servir ` rien dautre dans ce programme, ils seront dtruits lorsque cette instruction aura t a e e e excute. e e

15

si la classe na ni objet membre, ni fonction virtuelle, ni classe de base (cest le cas de notre classe Point), alors le constructeur synthtis est le constructeur trivial, qui consiste ` ne rien faire. Les donnes e e a e membres seront cres comme elles lauraient t en C, cest-`-dire initialises par zro sil sagit dune ee ee a e e variable globale, laisses indtermines sil sagit dune variable locale ou dynamique, e e e e e si la classe a des objets membres ou des classes de base, alors le constructeur synthtis produit lappel du constructeur par dfaut de chaque objet membre et de chaque classe de base. e Attention. Si au moins un constructeur est dni pour une classe, alors aucun constructeur par dfaut e e nest synthtis par le compilateur. Par consquent, ou bien lun des constructeurs explicitement dnis est un e e e e constructeur par dfaut, ou bien toute cration dun objet devra expliciter des valeurs dinitialisation. e e 2.4.4 Constructeur par copie (clonage)

Le constructeur par copie dune classe C est un constructeur dont le premier param`tre est de type  C e &  (rfrence sur un C ) ou  const C &  (rfrence sur un C constant) et dont les autres param`tres, sils ee ee e existent, ont des valeurs par dfaut. Ce constructeur est appel lorsquun objet est initialis en copiant un objet e e e existant. Cela arrive parfois explicitement, mais souvent implicitement, notamment chaque fois quun objet est pass comme param`tre par valeur ` une fonction ou rendu comme rsultat par valeur (c.-`-d. autre quune e e a e a rfrence) dune fonction. ee Si le programmeur na pas dni de constructeur de copie pour une classe, le compilateur synthtise un e e constructeur par copie consistant en la recopie de chaque membre dun objet dans le membre correspondant de lautre objet. Si ces membres sont de types primitifs ou des pointeurs, cela revient ` faire la copie  bit ` bit  a a dun objet sur lautre. A titre dexemple le programme suivant introduit une nouvelle varit de point ; ` chacun est associe une ee a e tiquette qui est une cha de caract`res : e ne e class PointNomme { public: PointNomme(int a, int b, char *s = "") { x = a; y = b; label = new char[strlen(s) + 1]; strcpy(label, s); } ... private: int x, y; char *label; };

x y label

Un texte

Fig. 1 Un point avec tiquette e La gure 1 reprsente la structure de ces objets. Lorsquun objet comporte des pointeurs, comme ici, line formation quil reprsente (appelons-la l objet logique ) ne se trouve pas enti`rement incluse dans lespace e e contigu que le compilateur conna (l objet technique ), car des morceaux dinformation (dans notre exemple t le texte de ltiquette) se trouvent ` dautres endroits de la mmoire. Ainsi, la copie bit ` bit que fait le e a e a compilateur peut tre inadapte ` de tels objets. e e a La gure 2 montre le rsultat de la copie bit ` bit dun objet Point telle que la produirait, avec lactuelle e a dnition de la classe Point, la construction dun objet b telle que e Point b = a; (a est une variable de type Point pralablement construite). Bien entendu, la copie du pointeur na pas dupliqu e e la cha pointe : les deux objets, loriginal et la copie, partagent la mme cha ne e e ne. Tr`s souvent, ce partage e nest pas souhaitable, car dicile ` grer et dangereux : toute modication de ltiquette dun des deux points a e e se rpercutera immdiatement sur lautre. e e Pour rsoudre ce probl`me il faut quiper notre classe dun constructeur par copie : e e e

16

x y label

a 10 20 Un texte

x y label

b 10 20

Fig. 2 Copie  supercielle  dun objet

class PointNomme { ... PointNomme(PointNomme &p) { x = p.x; y = p.y; label = new char[strlen(p.label) + 1]; strcpy(label, p.label); } ... }; Maintenant, la copie dun point entra nera la duplication eective de la cha de caract`res pointe, comme ne e e sur la gure 3.

x y label

a 10 20 Un texte

x y label

b 10 20 Un texte

Fig. 3 Copie  profonde 

2.5

Construction des objets membres

Lorsque des membres dune classe sont ` leur tour dun type classe on dit que la classe a des objets membres. a Linitialisation dun objet de la classe ncessite alors linitialisation de ces objets membres. Il en est toujours e ainsi, indpendamment du fait que lon emploie ou non un constructeur explicite, et qu` son tour ce constructeur e a appelle ou non explicitement des constructeurs des objets membres. Lorsque les objets membres nont pas de constructeurs par dfaut, une syntaxe spciale12 permet de prciser e e e les arguments des constructeurs des membres : NomDeLaClasse(param`tres) e : membre(param`tres), ... membre(param`tres) { e e corps du constructeur } A titre dexemple, imaginons que notre classe Point ne poss`de pas de constructeur sans arguments, et e quon doive dnir une classe Segment ayant deux points pour membres (un segment est dtermin par deux e e e points). Voici comment on devra crire son constructeur : e class Segment { Point origine, extremite; int epaisseur; public: Segment(int ox, int oy, int ex, int ey, int ep) : origine(ox, oy), extremite(ex, ey) { epaisseur = ep; } ... }; Note. Si la classe Point avait eu un constructeur sans arguments, le mauvais constructeur suivant aurait quand-mme fonctionn e e
12 A

titre dexercice on vriera que, sans cette syntaxe spciale, la construction des objets membres serait impossible. e e

17

class Segment { ... Segment(int ox, int oy, int ex, int ey, int ep) { origine = Point(ox, oy); // Version errone e extremite = Point(ex, ey); epaisseur = ep; } ... }; mais il faut comprendre que cette version est tr`s maladroite, car faite de deux aectations (les deux lignes e qui forment le corps du constructeur ne sont pas des dclarations). Ainsi, au lieu de se limiter ` initialiser les e a membres origine et extrmit, on proc`de successivement ` e e e a la construction de origine et extrmit en utilisant le constructeur sans arguments de la classe Point, e e la construction de deux points anonymes, initialiss avec les valeurs de ox, oy, ex et ey, e lcrasement des valeurs initiales de origine et extrmit par les deux points ainsi construits. e e e Note. La syntaxe spciale pour linitialisation des objets membres peut tre utilise aussi pour initialiser e e e les donnes membres de types primitifs. Par exemple, le constructeur de Segment prcdent peut aussi scrire : e e e e class Segment { ... Segment(int ox, int oy, int ex, int ey, int ep) : origine(ox, oy), extremite(ex, ey), epaisseur(ep) { } ... };

2.6

Destructeurs

De la mme mani`re quil y a des choses ` faire pour initialiser un objet qui commence ` exister, il y a e e a a parfois des dispositions ` prendre lorsquun objet va dispara a tre. Un destructeur est une fonction membre spciale. Il a le mme nom que la classe, prcd du caract`re ~. Il e e e e e e na pas de param`tre, ni de type de retour. Il y a donc au plus un destructeur par classe. e Le destructeur dune classe est appel lorsquun objet de la classe est dtruit, juste avant que la mmoire e e e occupe par lobjet soit rcupre par le syst`me. e e ee e Par exemple, voici le destructeur quil faut ajouter ` notre classe PointNomm. Sans ce destructeur, la a e destruction dun point nentra nerait pas la libration de lespace allou pour son tiquette : e e e class PointNomme { ... ~PointNomme() { delete [] label; } ... }; Notez que le destructeur na pas ` sinquiter de restituer lespace occup par l objet technique  lui-mme, a e e e form, ici, par les variables x, y et label (le pointeur, non la cha pointe). Cette restitution dpend du type e ne e e de mmoire que lobjet occupe, statique, automatique ou dynamique, et ne regarde pas le destructeur, de la e mme mani`re que son allocation ne regardait pas le constructeur qui a initialis lobjet. e e e ` Synthese du destructeur. Si le programmeur na pas crit de destructeur pour une classe, le compilateur e en synthtise un, de la mani`re suivante : e e si la classe na ni objets membres ni classes de base (cf. section 4), alors il sagit du destructeur trivial qui consiste ` ne rien faire, a si la classe a des classes de base ou des objets membres, le destructeur synthtis consiste ` appeler les e e a destructeurs des donnes membres et des classes de base, dans lordre inverse de lappel des constructeurs e correspondants.

18

2.7
2.7.1

Membres constants
Donnes membres constantes e

Une donne membre dune classe peut tre qualie const. Il est alors obligatoire de linitialiser lors de la e e e construction dun objet, et sa valeur ne pourra par la suite plus tre modie. e e A titre dexemple voici une nouvelle version de la classe Segment, dans laquelle chaque objet reoit, lors de c sa cration, un  numro de srie  qui ne doit plus changer au cours de la vie de lobjet : e e e class Segment { Point origine, extremite; int epaisseur; const int numeroDeSerie; public: Segment(int x1, int y1, int x2, int y2, int ep, int num); }; Constructeur, version errone : e Segment::Segment(int x1, int y1, int x2, int y2, int ep, int num) : origine(x1, y1), extremite(x2, y2) { epaisseur = ep; numeroDeSerie = num; // ERREUR : tentative de modication dune constante } Constructeur, version correcte, en utilisant la syntaxe de linitialisation des objets membres : Segment::Segment(int x1, int y1, int x2, int y2, int ep, int num) : origine(x1, y1), extremite(x2, y2), numeroDeSerie(num) { epaisseur = ep; } 2.7.2 Fonctions membres constantes

Le mot const plac ` la n de len-tte dune fonction membre indique que ltat de lobjet ` travers lequel la ea e e a fonction est appele nest pas chang du fait de lappel. Cest une mani`re de dclarer quil sagit dune fonction e e e e de consultation de lobjet, non dune fonction de modication : class Point { ... void placer(int a, int b); float distance(Point p) const; ... };

// modie lobjet // ne modie pas lobjet

A lintrieur dune fonction const dune classe C le pointeur this est de type  const C * const  (pointeur e constant vers un C constant) : lobjet point par this ne pourra pas tre modi. Cela permet au compilateur e e e dautoriser certains acc`s qui, sans cela, auraient t interdits. Par exemple, examinons la situation suivante : e ee void uneFonction(const Point a) { Point b; ... double d = a.distance(b); ... } la qualication const de la fonction distance est indispensable pour que lexpression prcdente soit accepte e e e par le compilateur. Cest elle seule, en eet, qui garantit que le point a, contraint ` rester constant, ne sera pas a modi par lappel de distance. e Il est conseill de qualier const toute fonction qui peu ltre : comme lexemple prcdent le montre, cela e e e e largit son champ dapplication. e Coexistence des fonctions constantes et non constantes. La qualication const dune fonction membre fait partie de sa signature. Ainsi, on peut surcharger une fonction membre non constante par une fonction membre constante ayant, ` part cela, le mme en-tte. La fonction non constante sera appele sur les a e e e objets non constants, la fonction constante sur les objets constants.

19

On peut utiliser cette proprit pour crire des fonctions qui neectuent pas le mme traitement ou qui ee e e ne rendent pas le mme type de rsultat lorsquelles sont appeles sur un objet constant et lorsquelles sont e e e appeles sur un objet non constant. Exemple (se rappeler que le rsultat rendu par une fonction ne fait pas e e partie de sa signature) : class Point { int x, y; public: int X() const { return x; } int Y() const { return y; } int& X() { return x; } int& Y() { return y; } ... }; Avec la dclaration prcdente, les fonctions X et Y sont scurises : sur un objet constant elles ne permettent e e e e e que la consultation, sur un objet non constant elles permettent la consultation et la modication : const Point a(2, 3); Point b(4,5); int r; ... r = a.X(); // Oui a.X() = r; // ERREUR ( a.X() rend une valeur) r = b.X(); // Oui b.X() = r; // Oui ( b.X() rend une rfrence) ee

2.8

Membres statiques

Chaque objet dune classe poss`de son propre exemplaire de chaque membre ordinaire (bientt nous dirons e o membre non statique) de la classe : pour les donnes membres, cela signie que de la mmoire nouvelle est alloue lors de la cration de chaque e e e e objet ; pour les fonctions membres, cela veut dire quelles ne peuvent tre appeles quen association avec un e e objet (on nappelle pas  la fonction f  mais  la fonction f sur lobjet x ). A loppos de cela, les membres statiques, signals par la qualication static prcdant leur dclaration, e e e e e sont partags par tous les objets de la classe. De chacun il nexiste quun seul exemplaire par classe, quel que e soit le nombre dobjets de la classe. Les donnes et fonctions membres non statiques sont donc ce que dans dautres langages orients objets on e e appelle variables dinstance et mthodes dinstance, tandis que les donnes et fonctions statiques sont appeles e e e dans ces langages variables de classe et mthodes de classe. e La visibilit et les droits dacc`s des membres statiques sont rgis par les mmes r`gles que les membres e e e e e ordinaires. 2.8.1 Donnes membres statiques e class Point { int x, y; public: static int nombreDePoints; Point(int a, int b) { x = a; y = b; nbrPoints++; } }; Chaque objet Point poss`de ses propres exemplaires des membres x et y mais, quel que soit le nombre de e points existants ` un moment donn, il existe un seul exemplaire du membre nombreDePoints. a e Initialisation. La ligne mentionnant nombreDePoints dans la classe Point est une simple  annonce , comme une dclaration extern du langage C. Il faut encore crer et initialiser cette donne membre (ce qui, e e e pour une donne membre non statique, est fait par le constructeur lors de la cration de chaque objet). Cela e e

20

se fait par une formule analogue ` une dnition de variable, crite dans la porte globale, mme sil sagit de a e e e e membres privs : e int Point::nombreDePoints = 0; (la ligne ci-dessus doit tre crite dans un chier  .cpp , non dans un chier  .h ) Lacc`s ` un membre e e e a statique depuis une fonction membre de la mme classe scrit comme lacc`s ` un membre ordinaire (voyez e e e a lacc`s ` nombreDePoints fait dans le constructeur Point ci-dessus). e a Lacc`s ` un membre statique depuis une fonction non membre peut se faire ` travers un objet, nimporte e a a lequel, de la classe : Point a, b, c; ... cout << a.nombreDePoints << "\n"; Mais, puisquil y a un seul exemplaire de chaque membre statique, lacc`s peut scrire aussi indpendamment e e e de tout objet, par une expression qui met bien en vidence laspect  variable de classe  des donnes membres e e statiques : cout << Point::nombreDePoints << "\n"; 2.8.2 Fonctions membres statiques

Une fonction membre statique nest pas attache ` un objet. Par consquent : e a e elle ne dispose pas du pointeur this, de sa classe, elle ne peut rfrencer que les fonctions et les membres statiques. ee Par exemple, voici la classe Point prcdente, dans laquelle le membre nombreDePoints a t rendu priv e e ee e pour en empcher toute modication intempestive. Il faut donc fournir une fonction pour en consulter la valeur, e nous lavons appele combien : e class Point { int x, y; static int nombreDePoints; public: static int combien() { return nombreDePoints; } Point(int a, int b) { x = a; y = b; nbrPoints++; } }; Pour acher le nombre de points existants on devra maintenant crire une expression comme (a tant de e e type Point) : cout << a.combien() << "\n"; ou, encore mieux, une expression qui ne fait pas intervenir de point particulier : cout << Point::combien() << "\n";

2.9
2.9.1

Amis
Fonctions amies

Une fonction amie dune classe C est une fonction qui, sans tre membre de cette classe, a le droit daccder e e a ` tous ses membres, aussi bien publics que privs. e Une fonction amie doit tre dclare ou dnie dans la classe qui accorde le droit dacc`s, prcde du e e e e e e e e mot rserv friend. Cette dclaration doit tre crite indiremment parmi les membres publics ou parmi les e e e e e e membres privs : e

21

class Tableau { int *tab, nbr; friend void afficher(const Tableau &); public: Tableau(int nbrElements); ... }; et, plus loin, ou bien dans un autre chier : void afficher(const Tableau &t) { cout << [; for (int i = 0; i < t.nbr; i++) cout << << t.tab[i]; cout << ] ; } Note. Notez cet eet de la qualication friend : bien que dclare ` lintrieur de la classe Tableau, la e e a e fonction afficher nest pas membre de cette classe ; en particulier, elle nest pas attache ` un objet, et le e a pointeur this ny est pas dni. e Les exemples prcdents ne montrent pas lutilit des fonctions amies, ce nest pas une chose vidente. En e e e e eet, dans la plupart des cas, une fonction amie peut avantageusement tre remplace par une fonction membre : e e class Tableau { int *tab, nbr; public: void afficher() const; //maintenant cest une fonction membre ... }; avec la dnition : e void Tableau::afficher() { cout << [; for (int i = 0; i < nbr; i++) cout << << tab[i]; cout << ] ; } Il y a cependant des cas de gure o` une fonction doit tre ncessairement crite comme une amie dune u e e e classe et non comme un membre ; un de ces cas est celui o` la fonction doit, pour des raisons diverses, tre u e membre dune autre classe. Imaginons, par exemple, que la fonction afficher doive crire les lments dun e ee objet Tableau dans un certain objet Fen^tre : e class Tableau; // ceci  promet  la classe Tableau

class Fenetre { ostream &fluxAssocie; public: void afficher(const Tableau &); ... }; class Tableau { int *tab, nbr; public: friend void Fenetre::afficher(const Tableau&); ... }; Maintenant, la fonction afficher est membre de la classe Fenetre et amie de la classe Tableau : elle a tous les droits sur les membres privs de ces deux classes, et son criture sen trouve facilite : e e e void Fenetre::afficher(const Tableau &t) { for (int i = 0; i < t.nbr; i++) fluxAssocie << t.tab[i]; } 22

2.9.2

Classes amies

Une classe amie dune classe C est une classe qui a le droit daccder ` tous les membres de C. Une telle e a classe doit tre dclare dans la classe C (la classe qui accorde le droit dacc`s), prcde du mot rserv friend, e e e e e e e e e indiremment parmi les membres privs ou parmi les membres publics de C. e e Exemple : les deux classes Maillon et Pile suivantes implmentent la structure de donnes pile (structure e e  dernier entr premier sorti ) dentiers : e class Maillon { int info; Maillon *suivant; Maillon(int i, Maillon *s) { info = i; suivant = s; } friend class Pile; }; class Pile { Maillon *top; public: Pile() { top = 0; } bool vide() { return top == 0; } void empiler(int x) { top = new Maillon(x, top); } int sommet() { return top->info; } void depiler() { Maillon *w = top; top = top->suivant; delete w; } }; On notera la particularit de la classe Maillon ci-dessus : tous ses membres sont privs, et la classe Pile e e est son amie (on dit que Maillon est une classe  esclave  de la classe Pile). Autrement dit, seules les piles ont le droit de crer et de manipuler des maillons ; le reste du syst`me nutilise que les piles et leurs oprations e e e publiques, et na mme pas ` conna lexistence des maillons. e a tre Exemple dutilisation : Pile p; int x; cout << "? "; cin >> x; while (x >= 0) { p.empiler(x); cin >> x; } while (! p.vide()) { cout << p.sommet() << ; p.depiler(); } La relation damiti nest pas transitive,  les amis de mes amis ne sont pas mes amis . A lvidence, la e e notion damiti est une entorse aux r`gles qui rgissent les droits dacc`s ; elle doit tre employe avec une grande e e e e e e modration, et uniquement pour permettre lcriture de composants intimement associs dun programme, e e e comme les classes Maillon et Pile de notre exemple.

23

3
3.1

Surcharge des oprateurs e


Principe

En C++ on peut rednir la smantique des oprateurs du langage, soit pour les tendre ` des objets, alors e e e e a qui ntaient initialement dnis que sur des types primitifs, soit pour changer leet doprateurs prdnis sur e e e e e des objets. Cela sappelle surcharger des oprateurs. e Il nest pas possible dinventer de nouveaux oprateurs ; seuls des oprateurs dj` connus du compilateur e e ea peuvent tre surchargs. Tous les oprateurs de C++ peuvent tre surchargs, sauf les cinq suivants : e e e e e . .* :: ? : sizeof Il nest pas possible de surcharger un oprateur appliqu uniquement ` des donnes de type standard : un e e a e oprande au moins doit tre dun type classe. e e Une fois surchargs, les oprateurs gardent leur pluralit, leur priorit et leur associativit initiales. En e e e e e revanche, ils perdent leur ventuelle commutativit et les ventuels liens smantiques avec dautres oprateurs. e e e e e Par exemple, la smantique dune surcharge de ++ ou <= na pas ` tre lie avec celle de + ou <. e ae e Surcharger un oprateur revient ` dnir une fonction ; tout ce qui a t dit ` propos de la surcharge des e a e ee a fonctions (cf. section 1.6) sapplique donc ` la surcharge des oprateurs. a e Plus prcisment, pour surcharger un oprateur (ce signe reprsente un oprateur quelconque) il faut e e e e e dnir une fonction nomme operator. Ce peut tre une fonction membre dune classe ou bien une fonction e e e indpendante. Si elle nest pas membre dune classe, alors elle doit avoir au moins un param`tre dun type classe. e e 3.1.1 Surcharge dun oprateur par une fonction membre e

Si la fonction operator est membre dune classe, elle doit comporter un param`tre de moins que la pluralit e e de loprateur : le premier oprande sera lobjet ` travers lequel la fonction a t appele. Ainsi, sauf quelques e e a ee e exceptions :  obj  ou  obj  quivalent `  obj .operator()  e a  obj1 obj2  quivaut `  obj1 .operator(obj2 )  e a Exemple : class Point { int x, y; public: Point(int = 0, int = 0); int X() const { return x; } int Y() const { return y; } Point operator+(const Point) const; ... };

//surcharge de + par une fonction membre

Point Point::operator+(const Point q) const { return Point(x + q.x, y + q.y); } Emploi : Point p, q, r; ... r = p + q;

// compris comme : r = p.operator+(q);

3.1.2

Surcharge dun oprateur par une fonction non membre e

Si la fonction operator nest pas membre dune classe, alors elle doit avoir un nombre de param`tres gal e e a ` la pluralit de loprateur. Dans ce cas : e e e a  obj  ou  obj  quivalent `  operator(obj )   obj1 obj2  quivaut `  operator(obj1 , obj2 )  e a Exemple : Point operator+(const Point p, const Point q) { return Point(p.X() + q.X(), p.Y() + q.Y()); } 24 // surcharge de + par une // fonction non membre

Emploi : Point p, q, r; ... r = p + q;

// compris maintenant comme : r = operator+(p, q);

Note 1. A cause des conversions implicites (voir la section 3.3.1), la surcharge dun oprateur binaire e symtrique par une fonction non membre, comme la prcdente, est en gnral prfrable, car les deux oprandes e e e e e ee e y sont traits symtriquement. Exemple : e e Point p, q, r; int x, y; Surcharge de loprateur + par une fonction membre : e r = p + q; r = p + y; r = x + q; r = p + q; r = p + y; r = x + q; // Oui : r = p.operator+(q); // Oui : r = p.operator+(Point(y)); // Erreur : x nest pas un objet // Oui : r = operator+(p, q); // Oui : r = operator+(p, Point(y)); // Oui : r = operator+(Point(p), q);

Surcharge de loprateur + par une fonction non membre : e

Note 2. Lorsque la surcharge dun oprateur est une fonction non membre, on a souvent intrt, ou ncessit, e ee e e a ` en faire une fonction amie. Par exemple, si la classe Point navait pas possd les  accesseurs  publics X() e e et Y(), on aurait d surcharger laddition par une fonction amie : u class Point { int x, y; public: Point(int = 0, int = 0); friend Point operator+(const Point, const Point); ... }; Point operator+(const Point p, Point q) { return Point(p.x + q.x, p.y + q.y); } Note 3. Les deux surcharges de + comme celles montres ci-dessus, par une fonction membre et par une e fonction non membre, ne peuvent pas tre dnies en mme temps dans un mme programme ; si tel tait e e e e e le cas, une expression comme p + q serait trouve ambigu par le compilateur. On notera que cela est une e e particularit de la surcharge des oprateurs, un tel probl`me ne se pose pas pour les fonctions ordinaires (une e e e fonction membre nest jamais en comptition avec une fonction non membre). e Note 4. La surcharge dun oprateur binaire par une fonction non membre est carrment obligatoire lorsque e e le premier oprande est dun type standard ou dun type classe dni par ailleurs, que le programmeur ne peut e e plus tendre (pour un exemple, voyez la section 3.2.1. e

3.2
3.2.1

Quelques exemples
Injection et extraction de donnes dans les ux e

Loprateur <<13 peut tre utilis pour  injecter  des donnes dans un ux de sortie, ou ostream (cf. section e e e e 1.8). Dune part, lauteur de la classe ostream a dni des surcharges de <<, probablement par des fonctions e membres, pour les types connus lors du dveloppement de la biblioth`que standard : e e
13 Appliqu ` des donnes de types primitifs, loprateur << exprime, en C++ comme en C, lopration de dcalage de bits vers ea e e e e la gauche.

25

class ostream { ... public: ... ostream& operator<<(int); ostream& operator<<(unsigned int); ostream& operator<<(long); ostream& operator<<(unsigned long); ostream& operator<<(double); ostream& operator<<(long double); etc. ... }; Dautre part, le programmeur qui souhaite tendre << aux objets dune classe quil est en train de dvelopper e e ne peut plus ajouter des membres ` la classe ostream. Il doit donc crire une fonction non membre : a e ostream& operator<<(ostream& o, const Point p) { return o << ( << p.X() << , << p.Y() << ); } Parfois (ce nest pas le cas ici) lcriture de loprateur non membre est plus simple si on en fait une fonction e e amie des classes des oprandes : e class Point { ... friend ostream& operator<<(ostream&, const Point); }; ostream& operator<<(ostream &o, const Point p) { return o << ( << p.x << , << p.y << ); } Avec cette surcharge de << nos points scrivent sur un ux de sortie comme les donnes primitives : e e Point p; ... cout << "le point trouv est : " << p << "\n"; e On peut de mani`re analogue surcharger loprateur >> an dobtenir une moyen simple pour lire des points. e e Par exemple, si on impose que les points soient donns sous la forme (x, y), cest-`-dire par deux nombres spars e a e e par une virgule et encadrs par des parenth`ses : e e class Point { ... friend ostream& operator<<(ostream&, const Point); friend istream& operator>>(istream&, Point&); }; istream& operator>>(istream& i, Point& p) { char c; i >> c; if (c == () { cin >> p.x >> c; if (c == ,) { cin >> p.y >> c; if (c == )) return i; } } cerr << "Erreur de lecture. Programme avort\n"; e exit(-1); }

26

Exemple dutilisation : void main() { Point p; cout << "donne un point : "; cin >> p; cout << "le point donn est : " << p << "\n"; e } Essai de ce programme : donne un point : ( 2 , 3 ) le point donn est : (2,3) e 3.2.2 Aectation

Laectation entre objets est une opration prdnie qui peut tre surcharge. Si le programmeur ne le fait e e e e e pas, tout se passe comme si le compilateur avait synthtis une opration daectation consistant en la recopie e e e membre ` membre des objets. Sil sagit dune classe sans objets membres, sans classe de base et sans fonction a virtuelle, cela donne loprateur daectation trivial, consistant en la copie bit ` bit dune portion de mmoire e a e sur une autre, comme pour la copie des structures du langage C. Les raisons qui poussent ` surcharger loprateur daectation pour une classe sont les mmes que celles a e e qui poussent ` crire un constructeur par copie (cf. section 2.4.4). Tr`s souvent, cest que la classe poss`de a e e e des  bouts dehors , cest-`-dire des membres de type pointeur, et quon ne peut pas se contenter dune copie a supercielle. Loprateur daectation doit tre surcharg par une fonction membre (en eet, dans une aectation on ne e e e souhaite pas que les oprandes jouent des rles symtriques). e o e Reprenons les points munis dtiquettes qui nous ont dj` servi dexemple : e ea class PointNomme { public: PointNomme(int = 0, int = 0, char * = ""); PointNomme(const PointNomme&); ~PointNomme(); private: int x, y; char *label; }; PointNomme::PointNomme(int a, int b, char *s) { x = a; y = b; label = new char[strlen(s) + 1]; strcpy(label, s); } PointNomme::PointNomme(const PointNomme& p) { cout << "PointNomme(const PointNomme&)\n"; x = p.x; y = p.y; label = new char[strlen(p.label) + 1]; strcpy(label, p.label); } PointNomme::~PointNomme() { cout << "~PointNomme()\n"; delete [] label; } Avec une classe PointNomme dnie comme ci-dessus, un programme aussi  inoensif  que le suivant est e erron (et explosif) : e void main() { PointNomme p(0, 0, "Origine"), q; q = p; }

27

En eet, laectation nayant pas t surcharge, linstruction  q = p ;  ne fait quune recopie bit ` bit de ee e a lobjet p dans lobjet q, cest ` dire une copie supercielle (voyez la gure 2 ` la page 17). A la n du programme, a a les objets p et q sont dtruits, lun apr`s lautre. Or, la destruction dun de ces objets lib`re la cha label et e e e ne rend lautre objet incohrent, ce qui provoque une erreur fatale lors de la restitution du second objet. e Voici la surcharge de loprateur daectation qui rsout ce probl`me. Comme il fallait sy attendre, cela se e e e rsume ` une destruction de la valeur courante (sauf dans le cas vicieux o` on essaierait daecter un objet par e a u lui mme) suivie dune copie : e class PointNomme { public: PointNomme(int = 0, int = 0, char * = ""); PointNomme(const PointNomme&); PointNomme& operator=(const PointNomme&); ~PointNomme(); ... }; PointNomme& PointNomme::operator=(const PointNomme& p) { if (&p != this) { delete [] label; x = p.x; y = p.y; label = new char[strlen(p.label) + 1]; strcpy(label, p.label); } return *this; }

3.3
3.3.1

Oprateurs de conversion e
Conversion vers un type classe

Pour convertir une donne de nimporte quel type, primitif ou classe, vers un type classe il sut demployer un e constructeur de conversion. Un tel constructeur nest pas une notion nouvelle, mais simplement un constructeur qui peut tre appel avec un seul argument. Exemple : e e class Point { ... public: Point(int a) { x = y = a; } ... }; Le programmeur peut appeler explicitement ce constructeur de trois mani`res direntes : e e Point p = Point(2); Point q(3); Point r = 4; // compris comme Point q = Point(3); // compris comme Point r = Point(4);

La troisi`me expression ci-dessus fait bien appara e tre laspect  conversion  de ce procd. Il faut savoir e e quun tel constructeur sera aussi appel implicitement, car le compilateur sen servira ` chaque endroit o`, un e a u Point tant requis, il trouvera un nombre. Exemple (vu ` la section 3.1.2) : e a Point p; ... r = p + 5;

// compris comme : r = operator+(p, Point(5));

Note. Si on les trouve trop dangereuses, on peut empcher que le compilateur fasse de telles utilisations e implicites dun constructeur ` un argument. Il sut pour cela de le qualier explicit : a

28

class Point { ... public: explicit Point(int a) { x = y = a; } ... }; Nouvel essai : Point p = Point(1); Point q = 2; 3.3.2 // Ok // ERREUR

Conversion dun objet vers un type primitif

C tant une classe et T un type, primitif ou non, on dnit la conversion de C vers T par une fonction e e membre C ::operatorT (). Par exemple, les classes Point et Segment suivantes sont munies de conversions int Point et Point Segment : struct Point { int x, y; ... Point(int a) { x = a; y = 0; } operator int() { return abs(x) + abs(y); } };

// conversion int Point

// conversion Point int // par exemple...

struct Segment { Point orig, extr; ... Segment(Point p) // conversion Point Segment : orig(p), extr(p) { } operator Point() { // conversion Segment Point return Point((orig.x + extr.x) / 2, (orig.y + extr.y) / 2); } };

4
4.1

Hritage e
Classes de base et classes drives e e

Le mcanisme de lhritage consiste en la dnition dune classe par runion des membres dune ou plusieurs e e e e classes prexistantes, dites classes de base directes, et dun ensemble de membres spciques de la classe nouvelle, e e appele alors classe drive. La syntaxe est : e e e class classe : drivation classe, drivation classe, ... drivation classe { e e e dclarations et dnitions des membres spciques de la nouvelle classe e e e } o` drivation est un des mots-cls private, protected ou public (cf. section 4.2.2). u e e Par exemple, voici une classe Tableau (tableau  amlior , en ce sens que la valeur de lindice est contrle e e oe lors de chaque acc`s) et une classe Pile qui ajoute ` la classe Tableau une donne membre exprimant le niveau e a e de remplissage de la pile et trois fonctions membres qui encapsulent le comportement particulier ( dernier entr e premier sorti ) des piles :

29

class Tableau { int *tab; int maxTab; public: int &operator[](int i) { contrle de la valeur de i o return tab[i]; } int taille() { return maxTab; } ... }; class Pile : private Tableau { int niveau; public: void empiler(int) { (*this)[niveau++] = x; } int depiler() { return (*this)[--niveau]; } int taille() { return niveau; } ... };

// Une Pile est un Tableau // avec des choses en plus

Etant donne une classe C, une classe de base de C est soit une classe de base directe de C, soit une classe e de base directe dune classe de base de C. Lhritage est appel simple sil y a une seule classe de base directe, il est dit multiple sinon. En C++, e e lhritage peut tre multiple. e e Encombrement. Dans la notion dhritage il y a celle de runion de membres. Ainsi, du point de vue de e e loccupation de la mmoire, chaque objet de la classe drive contient un objet de la classe de base : e e e

tab maxTab

tab maxTab niveau

Fig. 4 Un objet Tableau et un objet Pile Pour parler de lensemble des membres hrits (par exemple, tab et maxTab) dune classe de base B qui se e e trouvent dans une classe drive D on dit souvent le sous-objet B de lobjet D. e e Visibilite. Pour la visibilit des membres (qui nest pas laccessibilit, explique dans les sections suivantes), e e e il faut savoir que la classe drive dtermine une porte imbrique dans la porte de la classe de base. Ainsi, les e e e e e e noms des membres de la classe drive masquent les noms des membres de la classe de base. e e Ainsi, si unePile est un objet Pile, dans un appel comme unePile.taille() // la valeur de unePile.niveau la fonction taille() de la classe Tableau et celle de la classe Pile (cest-`-dire les fonctions Tableau::taille() a et Pile::taille()) ne sont pas en comptition pour la surcharge, car la deuxi`me rend tout simplement la e e premi`re invisible. Loprateur de rsolution de porte permet de remdier ` ce masquage (sous rserve quon e e e e e a e ait le droit dacc`s au membre taille de la classe Tableau) : e unePile.Tableau::taille() // la valeur de unePile.nbr

4.2
4.2.1

Hritage et accessibilit des membres e e


Membres protgs e e

En plus des membres publics et privs, une classe C peut avoir des membres protgs. Annoncs par le mot e e e e cl protected, ils reprsentent une accessibilit intermdiaire car ils sont accessibles par les fonctions membres e e e e et amies de C et aussi par les fonctions membres et amies des classes directement drives de C. e e Les membres protgs sont donc des membres qui ne font pas partie de linterface de la classe, mais dont on e e a jug que le droit dacc`s serait ncessaire ou utile aux concepteurs des classes drives. e e e e e

30

Imaginons, par exemple, quon veuille comptabiliser le nombre dacc`s faits aux objets de nos classes Tableau e et Pile. Il faudra leur ajouter un compteur, qui naura pas ` tre public (ces dcomptes ne regardent pas les ae e utilisateurs de ces classes) mais qui devra tre accessible aux membres de la classe Pile, si on veut que les acc`s e e aux piles qui ne mettent en uvre aucun membre des tableaux, comme dans vide(), soient bien compts. e class Tableau { int *tab; int maxTab; protected: int nbrAcces; public: Tableau(int t) { nbrAcces = 0; tab = new int[maxTab = t]; } int &operator[](int i) { contrle de la valeur de i o nbrAcces++; return tab[i]; } }; class Pile : private Tableau { int niveau; public: Pile(int t) : Tableau(t), niveau(0) { } bool vide() { nbrAcces++; return niveau == 0; } void empiler(int x) { (*this)[niveau++] = x; } int depiler() { return (*this)[--niveau]; } }; 4.2.2 Hritage priv, protg, public e e e e

En choisissant quel mot-cl indique la drivation, parmi private, protected ou public, le programmeur e e dtermine laccessibilit dans la classe drive des membres de la classe de base. On notera que : e e e e cela concerne laccessibilit des membres, non leur prsence (les objets de la classe drive contiennent e e e e toujours tous les membres de toutes les classes de base), la drivation ne peut jamais servir ` augmenter laccessibilit dun membre. e a e Consquence de ces deux points, les objets des classes drives ont gnralement des membres inaccessibles : e e e e e les membres privs de la classe de base sont prsents dans les objets de la classe drive, mais il ny a aucun e e e e moyen dy faire rfrence. ee Heritage prive. Syntaxe : class classeDrive : privateoptionnel classeDeBase { ... } e e Le mot cl private est optionnel car lhritage priv est lhritage par dfaut. Cest la forme la plus restrictive e e e e e dhritage : voyez la gure 5. e Dans lhritage priv, linterface de la classe de base dispara (lensemble des membres publics cessent dtre e e t e publics). Autrement dit, on utilise la classe de base pour raliser limplmentation de la classe drive, mais on e e e e soblige ` crire une nouvelle interface pour la classe drive. ae e e Cela se voit dans lexemple dj` donn des classes Tableau et Pile (cf. ). Il sagit dhritage priv, car les ea e e e tableaux ne fournissent que limplmentation des piles, non leur comportement. e

31

publics membres de la classe de base protgs privs inaccessibles


Fig. 5 Hritage priv e e

privs inaccessibles

statut dans la classe drive

Exemple demploi dun tableau : Tableau t(taille max souhaite); e ... t[i] = x; etc. Emploi dune pile : Pile p(taille max souhaite); e p[i] = x; // ERREUR : loprateur [] est inaccessible e p.empiler(x); // Oui ... cout << t[i]; // ERREUR : loprateur [] est inaccessible e cout << p.depiler(); // Oui etc. A retenir : si D drive de mani`re prive de B alors un objet D est une sorte de B, mais les utilisateurs e e e de ces classes nont pas ` le savoir. Ou encore : ce quon peut demander ` un B, on ne peut pas forcment le a a e demander ` un D. a Heritage protege. Syntaxe : class classeDrive : protected classeDeBase { ... } e e Cette forme dhritage, moins souvent utilise que les deux autres, est similaire ` lhritage priv (la classe e e a e e de base fournit limplmentation de la classe drive, non son interface) mais on consid`re ici que les dtails e e e e e de limplmentation, c.-`-d. les membres publics et protgs de la classe de base, doivent rester accessibles aux e a e e concepteurs dventuelles classes drives de la classe drive. e e e e e

publics membres de la classe de base protgs privs inaccessibles


Fig. 6 Hritage protg e e e Heritage public. Syntaxe : class classeDrive : public classeDeBase { ... } e e Dans lhritage public (voyez la gure 7) linterface est conserv : tous les lments du comportement e e ee public de la classe de base font partie du comportement de la classe drive. Cest la forme dhritage la plus e e e frquemment utilise, car la plus utile et la plus facile ` comprendre : dire que D drive publiquement de B e e a e cest dire que, vu de lextrieur, tout D est une sorte de B, ou encore que tout ce quon peut demander ` un B e a on peut aussi le demander ` un D. a La plupart des exemples dhritage que nous examinerons seront des cas de drivation publique. e e

protgs inaccessibles

statut dans la classe drive

4.3

Rednition des fonctions membres e

Lorsquun membre spcique dune classe drive a le mme nom quun membre dune classe de base, le e e e e premier masque le second, et cela quels que soient les rles syntaxiques (constante, type, variable, fonction, etc.) o de ces membres. 32

publics membres de la classe de base protgs privs inaccessibles


Fig. 7 Hritage public e

publics protgs inaccessibles

statut dans la classe drive

La signication de ce masquage dpend de la situation : e il peut sagir des noms de deux fonctions membres de mme signature, e e il peut sagir de fonctions de signatures direntes, ou de membres dont lun nest pas une fonction. Sil ne sagit pas de deux fonctions de mme signature, on a une situation maladroite, gnratrice de confusion, e e e dont on ne retire en gnral aucun bnce. En particulier, le masquage dune donne membre par une autre e e e e e donne membre ne fait pas conomiser la mmoire, puisque les deux membres existent dans chaque objet de la e e e classe drive. e e En revanche, le masquage dune fonction membre de la classe de base par une fonction membre de mme e signature est une dmarche utile et justie. On appelle cela une rednition de la fonction membre. e e e La justication est la suivante : si la classe D drive publiquement de la classe B, tout D peut tre vu e e comme une sorte de B, cest-`-dire un B  amlior  (augment des membres dautres classes de base, ou de a e e e membres spciques) ; il est donc naturel quun D rponde aux requtes quon peut soumettre ` un B, et quil e e e a y rponde de faon amliore. Do` lintrt de la rednition : la classe drive donne des versions analogues e c e e u ee e e e mais enrichies des fonctions de la classe de base. Souvent, cet enrichissement concerne le fait que les fonctions rednies acc`dent ` ce que la classe drive e e a e e a de plus que la classe de base. Exemple : un Pixel est un point amlior (c.-`-d. augment dun membre e e a e supplmentaire, sa couleur). Lachage dun pixel consiste ` lacher en tant que point, avec des informations e a additionnelles. Le code suivant re`te tout cela : e class Point { int x, y; public: Point(int, int); void afficher() { cout << ( << x << , << y << ); } ... }; class Pixel : public Point { char *couleur; public: Pixel(int, int, char *); void afficher() { cout << [; Point::afficher(); cout << ; << couleur << ]; } ... }; Utilisation : Point pt(2, 3); Pixel px(4, 5, "rouge"); ... pt.afficher(); px.afficher();

// achage dun Point

// achage dun Pixel // achage en tant que Point

// achage obtenu : (2,3) // achage obtenu : [(4,5) ;rouge]

33

4.4

Cration et destruction des objets drivs e e e

Lors de la construction dun objet, ses sous-objets hrits sont initialiss selon les constructeurs des classes e e e de base14 correspondantes. Sil ny a pas dautre indication, il sagit des constructeurs par dfaut. e Si des arguments sont requis, il faut les signaler dans le constructeur de lobjet selon une syntaxe voisine de celle qui sert ` linitialisation des objets membres (cf. section 2.5) : a classe(param`tres) e : classeDeBase(param`tres), ... classeDeBase(param`tres) { e e corps du constructeur } De la mme mani`re, lors de la destruction dun objet, les destructeurs des classes de base directes sont e e toujours appels. Il ny a rien ` crire, cet appel est toujours implicite. Exemple : e ae class Tableau { int *table; int nombre; public: Tableau(int n) { table = new int[nombre = n]; } ~Tableau() { delete [] table; } ... }; class Pile : private Tableau { int niveau; public: Pile(int max) : Tableau(max) { niveau = 0; } ~Pile() { if (niveau > 0) erreur("Destruction dune pile non vide"); } // ceci est suivi dun appel de ~Tableau() ... };

4.5

Rcapitulation sur la cration et destruction des objets e e

Ayant examin le cas de lhritage nous pouvons donner ici, sous une forme rsume, la totalit de ce quil e e e e e y a ` dire ` propos de la construction et le destruction des objets dans le cas le plus gnral. a a e e 4.5.1 Construction

1. Sauf pour les objets qui sont les valeurs de variables globales, le constructeur dun objet est appel e immdiatement apr`s lobtention de lespace pour lobjet. e e On peut considrer que lespace pour les variables globales existe d`s que le programme est charg. Les e e e constructeurs de ces variables sont appels, dans lordre des dclarations de ces variables, juste avant e e lactivation de la fonction principale du programme (en principe main). 2. Lactivation dun constructeur commence par les appels des constructeurs de chacune de ses classes de base directes, dans lordre de dclaration de ces classes de base directes. e 3. Sont appels ensuite les constructeurs de chacun des objets membres, dans lordre de dclaration de ces e e objets membres. e e 4. Enn, les instructions qui composent le corps du constructeur sont excutes.
sagit ici, tout dabord, des classes de base directes ; mais les constructeurs de ces classes de base directes appelleront ` leur a tour les constructeurs de leurs classes de base directes, et ainsi de suite. En dnitive, les constructeurs de toutes les classes de base e auront t appels. e e e
14 Il

34

Sauf indication contraire, les constructeurs dont il est question aux points 2 et 3 sont les constructeurs sans argument des classes de base et des classes des donnes membres. Si des arguments doivent tre prciss (par e e e e exemple parce que certaines de ces classes nont pas de constructeur sans argument) cela se fait selon la syntaxe : classe(parametres) : nomDeMembreOuDeClasseDeBase(param`tres), e ... nomDeMembreOuDeClasseDeBase(param`tres) { e corps du constructeur } Attention. Notez bien que, lorsque les constructeurs des classes de base et des objets membres sont explicitement appels dans le constructeur de la classe drive, ces appels ne sont pas eectus dans lordre o` e e e e u le programmeur les a crits : comme il a t dit aux points 2 et 3 ci-dessus, les appels de ces constructeurs sont e ee toujours faits dans lordre des dclarations des classes de base et des objets membres. e 4.5.2 Destruction

Grosso modo, le principe gnral est celui-ci : les objets  contemporains  (attachs ` un mme contexte, e e e a e cres par une mme dclaration, etc.) sont dtruits dans lordre inverse de leur cration. Par consquent e e e e e e 1. Lexcution du destructeur dun objet commence par lexcution des instructions qui en composent le e e corps. 2. Elle continue par les appels des destructeurs des classes des objets membres, dans lordre inverse de leurs dclarations. e 3. Sont appels ensuite les destructeurs des classes de base directes, dans lordre inverse des dclarations de e e ces classes de base. 4. Enn, lespace occup par lobjet est libr. e ee A quel moment les objets sont-ils detruits ? Les objets qui sont les valeurs de variables globales sont dtruits immdiatement apr`s la terminaison de la fonction principale (en principe main), dans lordre inverse e e e de la dclaration des variables globales. e Les objets qui ont t allous dynamiquement ne sont dtruits que lors dun appel de delete les concernant. ee e e Les objets qui sont des valeurs de variables automatiques (locales) sont dtruits lorsque le contrle quitte e o le bloc auquel ces variables sont rattaches ou, au plus tard, lorsque la fonction contenant leur dnition se e e termine. Objets temporaires. Les objets temporaires ont les vies les plus courtes possibles. En particulier, les objets temporaires crs lors de lvaluation dune expression sont dtruits avant lexcution de linstruction ee e e e suivant celle qui contient lexpression. Les objets temporaires crs pour initialiser une rfrence persistent jusqu` la destruction de cette derni`re. ee ee a e Exemple : { Point &r = Point(1, 2); // lobjet Point temporaire cr ici vit autant que r, ee // cest-`-dire au moins jusqu` la n du bloc a a

... } Attention, les pointeurs ne sont pas traits avec autant de soin. Il ne faut jamais initialiser un pointeur avec e ladresse dun objet temporaire (loprateur new est fait pour cela) : e { Point *p = & Point(3, 4); Point *q = new Point(5, 6); delete q; delete p; } // ERREUR // Oui // Oui // ERREUR (pour plusieurs raisons)

ici, le pointeur p est invalide : le point de coordonnes (3,4) a dj` t dtruit e eaee e

4.6
4.6.1

Polymorphisme
Conversion standard vers une classe de base

Si la classe D drive publiquement de la classe B alors les membres de B sont membres de D. Autrement e dit, les membres publics de B peuvent tre atteints ` travers un objet D. Ou encore : tous les services oerts e a 35

par un B sont oerts par un D. Par consquent, l` o` un B est prvu, on doit pouvoir mettre un D. Cest la raison pour laquelle la conversion e a u e (explicite ou implicite) dun objet de type D vers le type B, une classe de base accessible de D, est dnie, et a e le statut de conversion standard. Exemple : class Point { int x, y; public: Point(int, int); ... }; class Pixel : public Point { char *couleur; public: Pixel(int, int, char *); ... }; Utilisation : Pixel px(1, 2, "rouge"); Point pt = px; // un pixel a t mis l` o` un point tait ee a u e // attendu : il y a conversion implicite // point  gomtrique  e e

// pixel = point color e

De mani`re interne, la conversion dun D vers un B est traite comme lappel dune fonction membre de D e e qui serait publique, protge ou prive selon le mode (ici : public) dont D drive de B. e e e e Ainsi, la conversion dune classe drive vers une classe de base prive ou protge existe mais nest pas e e e e e utilisable ailleurs que depuis lintrieur de la classe drive. Bien entendu, le cas le plus intressant est celui de e e e e la drivation publique. Sauf mention contraire, dans la suite de cette section nous supposerons tre dans ce cas. e e Selon quelle sapplique ` des objets ou ` des pointeurs (ou des rfrences) sur des objets, la conversion dun a a ee objet de la classe drive vers la classe de base recouvre deux ralits tr`s direntes : e e e e e e 1. Convertir un objet D vers le type B cest lui enlever tous les membres qui ne font pas partie de B (les membres spciques et ceux hrits dautres classes). Dans cette conversion il y a perte eective dinformation : e e e voyez la gure 8.

oD

B D

(B)oD

Fig. 8 Conversion de oD, un objet D, en un objet B 2. Au contraire, convertir un D* en un B * (cest-`-dire un pointeur sur D en un pointeur sur B ) ne fait a perdre aucune information. Point ` travers une expression de type B *, lobjet nore que les services de la e a classe B, mais il ne cesse pas dtre un D avec tous ses membres : voyez la gure 9. e

pD

(B*)pD

B D
Fig. 9 Conversion de pD, un pointeur sur un D, en un pointeur sur un B Par exemple, supposons possder deux fonctions de prototypes : e void fon1(Point pt); void fon2(Point *pt); 36

et supposons quelles sont appeles de la mani`re suivante : e e Pixel pix = Pixel(2, 3, "rouge") ... fon1(pix); fon2(&pix); Le statut de lobjet pix en tant que param`tre de ces deux fonctions est tout ` fait dirent. Lobjet pass e a e e a ` fon1 comme argument est une version tronque de pix, alors que le pointeur pass ` fon2 est ladresse de e ea lobjet pix tout entier : void fon1(Point pt) { La valeur de pt est un Point ; il na pas de couleur. Il y avait peut-tre e ` lorigine un Pixel mais, ici, aucune conversion (autre que dnie a e par lutilisateur) ne peut faire un Pixel ` partir de pt. a } void fon2(Point *pt) { Il nest rien arriv ` lobjet dont ladresse a servi ` initialiser pt lors de ea a lappel de cette fonction. Si on peut garantir que cet objet est un Pixel, lexpression suivante (place sous la responsabilit du programmeur) a e e un sens : ((Pixel *) pt)->couleur ... } Pour ce qui nous occupe ici, les rfrences (pointeurs grs de mani`re interne par le compilateur) doivent ee ee e tre considres comme des pointeurs. Nous pouvons donc ajouter un troisi`me cas : e ee e void fon3(Point &rf) { Il nest rien arriv ` lobjet qui a servi ` initialiser rf lors de lappel de ea a cette fonction. Si on peut garantir que cet objet est un Pixel, lexpression suivante (place sous la responsabilit du programmeur) a un sens : e e ((Pixel &) rf).couleur ... } 4.6.2 Type statique, type dynamique, gnralisation e e

Considrons la situation suivante : e class B { ... }; class D : public B { ... }; D unD; ... B unB = unD; B *ptrB = &unD; B &refB = unD; Les trois aectations ci-dessus sont lgitimes ; elles font jouer la conversion standard dune classe vers une e classe de base. Le type de unB ne soul`ve aucune question (cest un B), mais les choses sont moins claires pour e les types de ptrB et refB. Ainsi ptrB, par exemple, pointe-t-il un B ou un D ? on dit que le type statique de *ptrB (ce que ptB pointe) et de refB est B, car cest ainsi que ptB et refB ont t dclares ; ee e e on dit que le type dynamique de *ptrB et de refB est D, car tel est le type des valeurs eectives de ces variables. Le type statique dune expression dcoule de lanalyse du texte du programme ; il est connu ` la compilation. e a Le type dynamique, au contraire, est dtermin par la valeur courante de lexpression, il peut changer durant e e lexcution du programme. e La notion de type dynamique va nous amener assez loin. Pour commencer, remarquons ceci : grce aux a

37

conversions implicites de pointeurs et rfrences vers des pointeurs et rfrences sur des classes de base, tout objet ee ee peut tre momentanment tenu pour plus gnral quil nest. Cela permet les traitements gnriques, comme e e e e e e dans lexemple suivant, qui prsente un ensemble de classes conues pour grer le stock dun supermarch15 : e c e e struct Article { char *denom; int quant; double prix; Article(char *d, int q, double p) : denom(d), quant(q), prix(p) { } ... };

// denomination // quantit disponible e // prix HT

struct Perissable : public Article { Date limite; Perissable(char *d, int q, double p, Date l) : Article(d, q, p), limite(l) { } ... }; struct Habillement : public Article { int taille; Habillement(char *d, int q, double p, int t) : Article(d, q, p), taille(t) { } ... }; struct Informatique : public Article { char garantie; Informatique (char *d, int q, double p, char g) : Article(d, q, p), garantie(g) { } ... }; Introduisons quelques structures de donnes (des tableaux de pointeurs) pour mmoriser les diverses classes e e darticles prsents dans le stock : e Perissable *aliment[maxAliment]; Habillement *vetemts[maxVetemts]; Informatique *info[maxInfo]; etc. Dnissons galement une structure pour mmoriser tous les articles du stock : e e e Article *stock[maxStock]; Initialisons tout cela (notez que chaque adresse fournie par un appel de new est dabord copie dans un e tableau spcique et ensuite gnralise et copie dans le tableau gnrique) : e e e e e e e nStock = nAliment = nVetemts = nInfo = 0; stock[nStock++] = aliment[nAliment++] = new Perissable("Foie gras Shell 1Kg", 1000, 450.0, Date(15,1,2000)); stock[nStock++] = vetemts[nVetemts] = new Habillement("Tenue gendarme fluo rose", 250, 200.0, 52); stock[nStock++] = info[nInfo++] = new Informatique("Pentium III 800 Mhz Diesel HDI", 500, 1000.0, C); etc. En tant qulments du tableau stock, les objets des classes Perissable, Habillement, etc., sont tenus ee pour plus gnraux quils ne sont, ce qui est tr`s commode pour faire sur chacun une opration gnrique. Par e e e e e e exemple, le calcul de la valeur totale du stock na pas besoin des spcicits de chaque article : e e
15 Nous dclarons ici des structures (une structure est une classe dont les membres sont par dfaut publiques) pour ne pas nous e e encombrer avec des probl`mes de droits dacc`s. e e

38

double valeurStock = 0; for (int i = 0; i < nStock; i++) valeurStock += stock[i]->quant * stock[i]->prix; Dans cet exemple nous avons utilis la gnralisation des pointeurs pour accder ` des membres dont les e e e e a objets hritent sans les modier : les notions de quantit disponible et de prix unitaire sont les mmes pour e e e toutes les sortes darticles du stock. La question devient beaucoup plus intressante lorsquon acc`de ` des membres de la classe de base qui sont e e a rednis dans les classes drives. Voici un autre exemple, classique : la classe Figure est destine ` reprsenter e e e e a e les lments communs ` tout un ensemble de gures gomtriques, telles que des triangles, des ellipses, des ee a e e rectangles, etc. : class Figure { ... }; class Triangle : public Figure { ... }; class Ellipse : public Figure { ... }; Figure *image[maxFigure]; int n = 0; image[n++] = new Triangle(...); image[n++] = new Figure(...); image[n++] = new Ellipse(...); ... for (int i = 0; i < n; i++) image[i]->seDessiner(); Si on suppose que la fonction seDessiner (qui doit ncessairement tre dclare dans la classe Figure, sinon e e e e la ligne ci-dessus serait trouve incorrecte ` la compilation) est rednie dans les classes Triangle, Ellipse, e a e etc., il est intressant de se demander quelle est la fonction qui sera eectivement appele par lexpression e e image[i]->seDessiner() crite ci-dessus. e Par dfaut, la dtermination du membre accd ` travers une expression comme la prcdente se fait durant e e e ea e e la compilation, cest-`-dire dapr`s le type statique du pointeur ou de la rfrence. Ainsi, dans lexpression a e ee image[i]->seDessiner(); cest la fonction seDessiner de la classe Figure (probablement une fonction bouche-trou) qui est appele. Cette e ralit dcevante obit ` des considrations decacit et de compatibilit avec le langage C ; elle semble limiter e e e e a e e e considrablement lintrt de la gnralisation des pointeurs... e ee e e ...heureusement, la section suivante prsente un comportement beaucoup plus intressant ! e e

4.7

Fonctions virtuelles

Soit f une fonction membre dune classe C. Si les conditions suivantes sont runies : e e e e f est rednie dans des classes drives (directement ou indirectement) de C, f est souvent appele ` travers des pointeurs ou des rfrences sur des objets de C ou de classes drives e a ee e e de C, alors f mrite dtre une fonction virtuelle. On exprime cela en faisant prcder sa dclaration du mot rserv e e e e e e e virtual. Limportant service obtenu est celui-ci : si f est appele ` travers un pointeur ou une rfrence sur un e a ee objet de C, le choix de la fonction eectivement active, parmi les diverses rednitions de f, se fera dapr`s le e e e type dynamique de cet objet. Une classe possdant des fonctions virtuelles est dite classe polymorphe. La qualication virtual devant e la rednition dune fonction virtuelle est facultative : les rednitions dune fonction virtuelle sont virtuelles e e doce. Exemple :

39

class Figure { public: virtual void seDessiner() { fonction passe-partout, probablement sans grand intrt e e } ... }; class Triangle : public Figure { public: void seDessiner() { implmentation du trac dun triangle e e } ... }; class Ellipse : public Figure { public: void seDessiner() { implmentation du trac dune ellipse e e ... }; Figure *image[maxFigure]; int n = 0; image[n++] = new Triangle(...); image[n++] = new Figure(...); image[n++] = new Ellipse(...); ... for (int i = 0; i < N ; i++) image[i]->seDessiner(); Maintenant, lappel image[i]->seDessiner() active la fonction rednie dans la classe de lobjet eectie vement point par image[i]. Cela constitue, bien entendu, le comportement intressant, celui quon recherche e e presque toujours. On notera que cet appel est lgal parce que la fonction seDessiner a t dclare une premi`re e ee e e e fois dans la classe qui correspond au type statique de image[i]. En r`gle gnrale, les diverses rednitions dune fonction peuvent tre vues comme des versions de plus en e e e e e plus spcialises dun traitement spci de mani`re gnrique dans la classe de base C. Dans ces conditions, e e e e e e e appeler une fonction virtuelle f ` travers un pointeur p sur un objet de C cest demander lexcution de la a e version de f la plus spcialise qui sapplique ` ce que p pointe en fait ` linstant o` lappel est excut. e e a a u e e Contraintes En concurrence avec le mcanisme du masquage, la rednition des fonctions virtuelles subit e e des contraintes fortes, sur les types des param`tres et le type du rsultat : e e 1. Tout dabord, la signature (liste des types des arguments, sans le type du rsultat) de la rednition e e doit tre strictement la mme que celle de la premi`re dnition, sinon la deuxi`me dnition nest pas une e e e e e e rednition mais un masquage pur et simple de la premi`re. e e 2. La contrainte sur le rsultat garantit que lappel dune fonction virtuelle est correct et sans perte dinfore mation quoi quil arrive. Supposons que f soit une fonction virtuelle dune classe C rendant un rsultat de type e T1 , et quune rednition de f dans une classe drive de C rende un rsultat de type T2 . Durant la compilation, e e e e une expression telle que (p est de type C*) : p->f() est vue comme de type T1 ; le compilateur vriera que ce type correspond ` ce que le contexte de cette expression e a requiert. Or, ` lexcution, cette expression peut renvoyer un objet de type T2 . Pour que cela soit cohrent et a e e ecace, il faut que le type T2 puisse tre converti dans le type T1 , et cela sans avoir ` modier (tronquer) la e a valeur renvoye. Il faut donc : e soit T1 = T2 , ee soit T1 est un pointeur [resp. une rfrence] sur un objet dune classe C1 , T2 est un pointeur [resp. une rfrence] sur un objet dune classe C2 et C1 est une classe de base accessible de C2 . ee Autrement dit, si une fonction virtuelle f rend ladresse dun T , toute rednition de f doit rendre ladresse e dune sorte de T ; si f rend une rfrence sur un T , toute rednition de f doit rendre une rfrence sur une ee e ee

40

sorte de T ; enn, si f ne rend ni un pointeur ni une rfrence, toute rednition de f doit rendre la mme chose ee e e que f . Exemple : class Figure { public: virtual Figure *symetrique(Droite *d); ... }; class Triangle : public Figure { public: Triangle *symetrique(Droite *d); ... }; Exemple dutilisation (notez que la contrainte sur le rsultat assure la correction de linitialisation de f2, e quel que soit le type dynamique de f1) : Figure *f1 = new nomDuneClasseDriveDeFigure(...); e e Figure *f2 = f1->symetrique(axe); Les deux contraintes indiques sont examines une apr`s lautre : si la contrainte sur la signature (ici (Droite e e e *)) est satisfaite, le compilateur dtecte un cas de rednition de fonction virtuelle et, alors seulement, il vrie e e e la contrainte sur le rsultat (ici la contrainte est satisfaite car le rsultat est un pointeur dans les deux cas et e e Figure est une classe de base accessible de Triangle). Si cette derni`re nest pas satisfaite, il annonce une e erreur.

4.8

Classes abstraites

Les fonctions virtuelles sont souvent introduites dans des classes places ` des niveaux si levs de la hirarchie e a e e e (dhritage) quon ne peut pas leur donner une implmentation utile ni mme vraisemblable. e e e Une premi`re solution consiste ` en faire des fonctions vides : e a class Figure { public: virtual void seDessiner() { } // sera dnie dans les classes drives e e e ... }; En fait, si la fonction seDessiner peut tre vide cest parce que : e la classe Figure ne doit pas avoir dobjets, la classe Figure doit avoir des classes drives (qui, elles, auront des objets), e e la fonction seDessiner doit tre rednie dans les classes drives. e e e e Bref, la classe Figure est une abstraction. Un programme ne crera pas dobjets Figure, mais des objets e Rectangle, Ellipse, etc., de classes drives de Figure. Le rle de la classe Figure nest pas de produire des e e o objets, mais de reprsenter ce que les objets rectangles, ellipses, etc., ont en commun ; or, sil y a une chose que e ces objets ont en commun, cest bien la facult de se dessiner. Nous dirons que Figure est une classe abstraite. e On remarque que la fonction seDessiner introduite au niveau de la classe Figure nest pas un service rendu aux programmeurs des futures classes drives de Figure, mais une contrainte : son rle nest pas de dire ce e e o quest le dessin dune gure, mais dobliger les futures classes drives de Figure ` le dire. On peut traduire e e a cet aspect de la chose en faisant que cette fonction dclenche une erreur. En eet, si elle est appele, cest que e e ou bien on a cr un objet de la classe Figure, ou bien on a cr un objet dune classe drive de Figure dans ee ee e e laquelle on na pas redni la fonction seDessiner : e

41

class Figure { public: virtual void seDessiner() { erreur("on a oubli de dfinir la fonction seDessiner"); e e } ... }; Cette mani`re de faire est peu pratique, car lerreur ne sera dclenche qu` lexcution (cest le programmeur e e e a e qui a commis loubli mais cest lutilisateur qui se fera rprimander !). En C++ on a une meilleure solution : e dnir une fonction virtuelle pure, par la syntaxe : e class Figure { public: virtual void seDessiner() = 0; ... };

// Une fonction virtuelle pure

Dune part, cela  ocialise  le fait que la fonction ne peut pas, ` ce niveau, possder une implmentation. a e e Dautre part, cela cre, pour le programmeur, lobligation de dnir cette implmentation dans une classe drive e e e e e ultrieure, et permet de vrier pendant la compilation que cela a t fait. Une fonction virtuelle pure reste e e ee virtuelle pure dans les classes drives, aussi longtemps quelle ne fait pas lobjet dune rednition (autre que e e e  = 0 ). Pour le compilateur, une classe abstraite est une classe qui a des fonctions virtuelles pures. Tenter de crer e des objets dune classe abstraite est une erreur, quil signale : class Figure { public: virtual void seDessiner() = 0; ... }; class Rectangle : public Figure { int x1, y1, x2, y2; public: void seDessiner() { trait(x1, y1, x2, y1); trait(x2, y1, x2, y2); trait(x2, y2, x1, y2); trait(x1, y2, x1, y1); } ... }; Utilisation : Figure f; Figure *pt; pt = new Figure; Rectangle r; pt = new Rectangle; // // // // // // // ERREUR : Cration dune instance de la e classe abstraite Figure Oui : cest un pointeur ERREUR : Cration dune instance de la e classe abstraite Figure Oui : la classe Rectangle nest pas abstraite Oui

4.9

Identication dynamique du type

Le cot du polymorphisme, en espace mmoire, est dun pointeur par objet, quel que soit le nombre de u e fonctions virtuelles de la classe16 . Chaque objet dune classe polymorphe comporte un membre de plus que ceux que le programmeur voit : un pointeur vers une table qui donne les adresses quont les fonctions virtuelles pour les objets de la classe en question.
16 En temps, le co t du polymorphisme est celui dune indirection supplmentaire pour chaque appel dune fonction virtuelle, u e puisquil faut aller chercher dans une table ladresse eective de la fonction.

42

Do` lide de proter de lexistence de ce pointeur pour ajouter au langage, sans cot supplmentaire, une u e u e gestion des types dynamiques quil faudrait sinon crire au coup par coup (probablement ` laide de fonctions e a virtuelles). Fondamentalement, ce mcanisme se compose des deux oprateurs dynamic cast et typeid. e e 4.9.1 Loprateur dynamic cast e

Syntaxe dynamic_cast<type>(expression) Il sagit deectuer la conversion dune expression dun type pointeur vers un autre type pointeur, les types points tant deux classes polymorphes dont lune, B, est une classe de base accessible de lautre, D. e e Sil sagit de gnralisation (conversion dans le sens D* B *), cet oprateur ne fait rien de plus que la e e e conversion standard. Le cas intressant est B * D*, conversion qui na de sens que si lobjet point par lexpression de type B * e e est en ralit un objet de la classe D ou dune classe drive de D. e e e e Loprateur dynamic cast fait ce travail de mani`re sre et portable, et rend ladresse de lobjet lorsque e e u cest possible, 0 dans le cas contraire : class Animal { ... virtual void uneFonction() { ... } }; class Mammifere : public Animal { ... }; class Chien : public Mammifere { ... }; class Caniche : public Chien { ... }; class Chat : public Mammifere { ... }; class Reverbere { ... }; Chien medor; Animal *ptr = &medor; ... Mammifere *p0 = ptr; // ERREUR (` la compilation) : un Animal nest pas forcment un Mammif`re a e e Mammifere *p1 = dynamic_cast<Mammifere *>(ptr); // OK : p1 reoit une bonne adresse, car Mdor est un mammif`re c e e Caniche *p2 = dynamic_cast<Caniche *>(ptr); // OK, mais p2 reoit 0, car Mdor nest pas un caniche c e Chat *p3 = dynamic_cast<Chat *>(ptr); // OK, mais p3 reoit 0, car Mdor nest pas un chat non plus c e Reverbere *p4 = dynamic_cast<Reverbere *>(ptr); // OK, mais p4 reoit 0, car Mdor nest pas un reverb`re c e e Loprateur dynamic_cast sapplique galement aux rfrences. Lexplication est la mme, sauf quen cas e e ee e dimpossibilit deectuer la conversion, cet oprateur lance lexception bad cast : e e

// il faut au moins une fonction virtuelle // (car il faut des classes polymorphes)

43

Animal &ref = medor; Mammifere &r1 = dynamic_cast<Mammifere &>(ref); Caniche &r2 = dynamic_cast<Caniche &>(ref);

// Oui // exception bad cast lance ; e // non attrape, elle est fatale e

4.9.2

Loprateur typeid e

Syntaxes : typeid(type) ou typeid(expression) Le rsultat est une valeur de type const type_info&, o` type_info est une classe de la biblioth`que e u e standard comportant au moins les membres suivants : class type_info { public: const char *name() const; int operator==(const type_info&) const; int operator!=(const type_info&) const; int before(const type_info&) const; ... }; On a donc la possibilit de comparer les types dynamiques et den obtenir lcriture des noms. Exemple : e e Animal *ptr = new Caniche; cout << typeid(ptr).name() << \n; cout << typeid(*ptr).name() << \n; cout << "Lanimal point par ptr " e << (typeid(*ptr) == typeid(Chien) ? "est" : "nest pas") << " un chien\n"; cout << "Lanimal point par ptr est un " e << typeid(*ptr).name() << "\n"; achage obtenu : Animal * Chien Lanimal point par ptr nest pas un chien e Lanimal point par ptr est un Caniche e

Mod`les (templates) e

Un mod`le dnit une famille de fonctions ou de classes paramtre par une liste didenticateurs qui e e e e reprsentent des valeurs et des types. Les valeurs sont indiques par leurs types respectifs, les types par le mot e e rserv class. Le tout est prx par le mot rserv template. Exemple : e e e e e e template<class T, class Q, int N> ici gure la dclaration ou la dnition dune fonction ou dune classe e e dans laquelle T et Q apparaissent comme types et N comme constante Le mot class ne signie pas que T et Q sont ncessairement des classes, mais des types. Depuis la norme e ISO on peut galement utiliser le mot rserv typename, qui est certainement un terme plus heureux. e e e La production eective dun lment de la famille dnie par un mod`le sappelle linstanciation 17 du mod`le. ee e e e Lors de cette opration, les param`tres qui reprsentent des types doivent recevoir pour valeur des types ; les e e e autres param`tres doivent recevoir des expressions dont la valeur est connue durant la compilation. e
17 Le choix du mot nest pas tr`s heureux. Dans tous les autres langages ` objets, linstanciation est lopration de cration dun e a e e objet, et on dit quun objet est instance de sa classe. En C++ le mot instanciation ne sutilise quen rapport avec les mod`les. e

44

5.1

Mod`les de fonctions e

Il ny a pas en C++ un oprateur ou un mot rserv expressment destin ` produire des instances des e e e e e a mod`les. Linstanciation dun mod`le de fonction e e template<param 1 , ... param k > type nom ( argfor 1 , ... argfor n )... est commande implicitement par lappel dune des fonctions que le mod`le dnit. Un tel appel peut tre crit e e e e e sous la forme : nom <arg 1 , ... arg k > (arge 1 , ... arge n ) dans laquelle les derniers arguments du mod`le, arg i ... arg k , ventuellement tous, peuvent tre omis si leurs e e e valeurs peuvent tre dduites sans ambigu e des arguments eectifs de la fonction, arge i , ... arge n . e e t Exemple : dclaration du mod`le  recherche de la valeur minimale dun tableau (ayant au moins un e e lment)  : ee template<class T> T min(T tab[], int n) { T min = tab[0]; for (int i = 1; i < n; i++) if (tab[i] < min) min = tab[i]; return min; } Voici un programme qui produit et utilise deux instances de ce mod`le : e int t[] = { 10, 5, 8, 14, 20, 3, 19, 7 }; ... cout << min<int>(t, 8); cout << min<char>("BKEFYFFLKRNFAJDQKXJD", 20); cout << min(t, 8); cout << min("BKEFYFFLKRNFAJDQKXJD", 20);

// T = int // T = char // T = int // T = char

Puisque dans les deux cas largument du mod`le peut se dduire de lappel, ce programme scrit aussi : e e e

Un mod`le de fonction peut coexister avec une ou plusieurs spcialisations. Par exemple, la fonction e e suivante est une spcialisation du mod`le min quon ne peut pas obtenir par instanciation de ce mod`le, car ` e e e a la place de loprateur < gure un appel de la fonction strcmp : e char *min(char *tab[], int n) { char *min = tab[0]; for (int i = 1; i < n; i++) if (strcmp(tab[i], min) < 0) min = tab[i]; return min; } Pour le mcanisme de la surcharge des fonctions, les instances de mod`les sont souvent en concurrence e e avec dautres instances ou des fonctions ordinaires. Il faut savoir que les fonctions ordinaires sont prfres aux eee instances de mod`les et les instances de mod`les plus spcialiss sont prfres aux instances de mod`les moins e e e e eee e spcialiss. e e Par exemple, donnons-nous un programme un peu plus compliqu que le prcdent, en crivant une fonction e e e e qui recherche lindice du minimum dun tableau, avec un deuxi`me tableau pour  crit`re secondaire . Nous e e dnissons : e 1. Un mod`le avec deux param`tres-types : e e template<class T1, class T2> int imin(T1 ta[], T2 tb[], int n) { cout << "imin(T1 [], T2 [], int) : "; int m = 0; for (int i = 1; i < n; i++) if (ta[i] < ta[m] || ta[i] == ta[m] && tb[i] < tb[m]) m = i; return m; } 2. Un mod`le qui en est une spcialisation partielle (le premier tableau est un tableau de cha e e nes, il ny a donc plus quun param`tre-type) : e 45

template<class T> int imin(char *ta[], T tb[], int n) { cout << "imin(char *[], T [], int) : "; int m = 0, r; for (int i = 1; i < n; i++) { r = strcmp(ta[i], ta[m]); if (r < 0 || r == 0 && tb[i] < tb[m]) m = i; } return m; } 3. Une fonction qui est une spcialisation compl`te du mod`le (deux tableaux de cha e e e nes, plus aucun param`tre-type) : e int imin(char *ta[], char *tb[], int n) { cout << "imin(char *[], char *[], int) : "; int m = 0, r; for (int i = 1; i < n; i++) { r = strcmp(ta[i], ta[m]); if (r < 0 || r == 0 && strcmp(tb[i], tb[m]) < 0) m = i; } return m; } Essayons tout cela : int ti1[5] = { 10, 5, 15, 5, 18 }; int ti2[5] = { 20, 8, 20, 10, 20 }; char *ts1[5] = { "Andre", "Denis", "Charles", "Andre", "Bernard" }; char *ts2[5] = { "Duchamp", "Dubois", "Dumont", "Dupont", "Durand" }; cout << imin(ti1, ti2, 5) << \n; cout << imin(ts1, ti2, 5) << \n; cout << imin(ts1, ts2, 5) << \n; Lachage obtenu : imin(T1 [], T2 [], int) : 1 imin(char *[], T [], int) : 3 imin(char *[], char *[], int) : 0 conrme que les mod`les les plus spcialiss sont prfrs aux moins spcialiss. e e e eee e e

5.2

Mod`les de classes e

Un mod`le de classe est un type paramtr, par dautres types et par des valeurs connues lors de la compie e e lation. Par exemple, dnissons un mod`le pour reprsenter des tableaux dans lesquels lindice est vri lors de e e e e e chaque acc`s. Le type des lments de ces tables nest pas connu, cest un param`tre du mod`le : e ee e e template<class TElement> class Table { TElement *tab; int nbr; public: Table(int n) { tab = new TElement[nbr = n]; } ~Table() { delete [] tab; } TElement &operator[](int i) { contrle de la validit de i o e return tab[i]; }

46

TElement operator[](int i) const { contrle de la validit de i o e return tab[i]; } }; On obtient linstanciation dun mod`le de classe en en crant des objets. Ici, contrairement ` ce qui se passe e e a pour les fonctions, les arguments du mod`le doivent tre toujours explicits : e e e Table<char> message(80); Table<Point> ligne(100); Une fois dclars, ces tableaux sutilisent comme les tableaux du langage (avec en plus la certitude que si e e lindice dborde on en sera prvenu tout de suite) : e e message[0] = ; ligne[i] = Point(j, k); Tout se passe comme si la dnition de lobjet message crite ci-dessus produisait le texte obtenu en substie e tuant TElement par char dans le mod`le Table et le prsentait au compilateur ; un peu plus tard, la dnition e e e de ligne produit le texte obtenu en substituant TElement par Point et le prsente galement au compilateur. e e Cest un traitement apparent au dveloppement des macros18 mais bien plus complexe, aussi bien sur le plan e e formel (les arguments dun mod`le peuvent tre des instances dautres mod`les, les modalits de linstanciation e e e e dun mod`le de fonction peuvent tre automatiquement dduites de lappel de la fonction, etc.) que sur le plan e e e pratique (ncessit de ne pas instancier un mod`le qui la dj` t dans la mme unit de compilation, ncessit e e e eaee e e e e de reconna que des instances du mme mod`le produites et compiles dans des units de compilation spares tre e e e e e e sont la mme entit, etc.). e e 5.2.1 Fonctions membres dun mod`le e

Les fonctions membres dun mod`le de classe sont des mod`les de fonctions avec les mmes param`tres que e e e e le mod`le de la classe. Cela est implicite pour les dclarations et dnitions crites ` lintrieur du mod`le de e e e e a e e classe, mais doit tre explicit pour les dnitions crites ` lextrieur. e e e e a e Par exemple, voici notre mod`le de classe Table avec des fonctions membres spares : e e e template<class TElement> class Table { TElement *tab; int nbr; public: explicit Table(int); ~Table(); TElement &operator[](int); TElement operator[](int) const; }; template<class TElement> Table<TElement>::Table(int n) { tab = new TElement[nbr = n]; } template<class TElement> Table<TElement>::~Table() { delete [] tab; } template<class TElement> TElement &Table<TElement>::operator[](int i) { contrle de la validit de i o e return tab[i]; } template<class TElement> TElement Table<TElement>::operator[](int i) const { contrle de la validit de i o e return tab[i]; }
18 Dans

les premi`res versions de C++ les mod`les taient dnis comme des macros et pris en charge par le prprocesseur. e e e e e

47

Les param`tres des mod`les de classes peuvent avoir des valeurs par dfaut. Voici, par exemple, une autre e e e version de nos tables, dans laquelle lallocation du tableau nest pas dynamique ; par dfaut, ce sont des tables e de 10 entiers : template<class TElement = int, int N = 10> class TableFixe { TElement tab[N]; public: TElement & operator[](int i) { IndiceValide(i, N); return tab[i]; } TElement operator[](int i) const { IndiceValide(i, N); return tab[i]; } }; TableFixe<float, 10> t1; TableFixe<float> t2; TableFixe<> t3; TableFixe t4; // // // // Oui, table de 10 float Oui, table de 100 float Oui, table de 100 int ERREUR

Comme une classe, un mod`le peut tre  annonc , cest-`-dire, dclar et non dni. Il pourra alors tre e e e a e e e e utilis dans toute situation ne ncessitant pas de conna sa taille ou ses dtails internes : e e tre e template<class TElement> class Table; Table<float> table1; // ERREUR Table<float> *ptr; // Oui ptr = new Table<float>; // ERREUR extern Table<float> table2; // Oui

6
6.1

Exceptions
Principe et syntaxe

Il y a parfois un probl`me de communication au sein des grands logiciels. En eet, le concepteur dune e biblioth`que de bas niveau peut programmer la dtection dune situation anormale produite durant lexcution e e e dune de ses fonctions, mais ne sait pas quelle conduite doit adopter alors la fonction de haut niveau qui la appele. Lauteur des fonctions de haut niveau conna la mani`re de ragir aux anomalies, mais ne peut pas e t e e les dtecter. e Le mcanisme des exceptions est destin ` permettre aux fonctions profondes dune biblioth`que de notier e ea e la survenue dune erreur aux fonctions hautes qui utilisent la biblioth`que. e Les points-cls de ce mcanisme sont les suivants : e e la fonction qui dtecte un vnement exceptionnel construit une exception et la  lance  (throw ) vers la e e e fonction qui la appele ; e ne e e lexception est un nombre, une cha ou, mieux, un objet dune classe spcialement dnie dans ce but (souvent une classe drive de la classe exception) comportant diverses informations utiles ` la e e a caractrisation de lvnement ` signaler ; e e e a e e une fois lance, lexception traverse la fonction qui la lance, sa fonction appelante, la fonction appelante de la fonction appelante, etc., jusqu` atteindre une fonction active (c.-`-d. une fonction commence et a a e non encore termine) qui a prvu d attraper  (catch) ce type dexception ; e e e lors du lancement dune exception, la fonction qui la lance et les fonctions que lexception traverse sont immdiatement termines : les instructions qui restaient ` excuter dans chacune de ces fonctions sont e e a e abandonnes ; malgr son caract`re prmatur, cette terminaison prend le temps de dtruire les objets e e e e e e locaux de chacune des fonctions ainsi avortes ; e a e si une exception arrive ` traverser toutes les fonctions actives, car aucune de ces fonctions na prvu de lattraper, alors elle produit la terminaison du programme. Une fonction indique quelle sintresse aux exceptions qui peuvent survenir durant lexcution dune certaine e e squence dinstructions par une expression qui dune part dlimite cette squence et qui dautre part associe un e e e

48

traitement spcique aux divers types dexception que la fonction intercepte. Cela prend la forme dun  bloc e try  suivi dun ensemble de gestionnaires dinterception ou  gestionnaires catch  : try { instructions susceptibles de provoquer, soit directement soit dans des fonctions appeles, le lancement dune exception e } catch(dclarationParam`tre 1 ) { e e instructions pour traiter les exceptions correspondant au type de param`tre 1 e } catch(dclarationParam`tre 2 ) { e e instructions pour traiter les exceptions, non attrapes par le e gestionnaire prcdent, correspondant au type de param`tre 2 e e e } etc. catch(...) { instructions pour traiter toutes les exceptions non attrapes par les gestionnaires prcdents e e e } Un bloc try doit tre immdiatement suivi par au moins un gestionnaire catch. Un gestionnaire catch doit e e se trouver immdiatement apr`s un bloc try ou immdiatement apr`s un autre gestionnaire catch. e e e e Chaque gestionnaire catch comporte un en-tte avec la dclaration dun argument formel qui sera initialis, e e e a ` la mani`re dun argument dune fonction, avec lexception ayant activ le gestionnaire. Exemple : e e catch(bad_alloc e) { cerr << "ERREUR : " << e.what() << \n; } Si le corps du gestionnaire ne rfrence pas lexception en question, len-tte peut se limiter ` spcier le ee e a e type de lexception : catch(bad_alloc) { cout << "ERREUR : Allocation impossible"; } Un programme lance une exception par linstruction throw expression; La valeur de expression est suppose dcrire lexception produite. Elle est propage aux fonctions actives, e e e jusqu` trouver un gestionnaire catch dont le param`tre indique un type compatible avec celui de lexpression. a e Les fonctions sont explores de la plus rcemment appele vers la plus anciennement appele (c.-`-d. du sommet e e e e a vers la base de la pile dexcution) ; ` lintrieur dune fonction, les gestionnaires catch sont explors dans lordre e a e e o` ils sont crits. Le gestionnaire19 u e catch(...) attrape toutes les exceptions. Il nest donc pas toujours prsent et, quand il lest, il est le dernier de son groupe. e D`s lentre dans un gestionnaire catch, lexception est considre traite (il ny a plus de  balle en lair ). e e ee e A la n de lexcution dun gestionnaire, le contrle est pass ` linstruction qui suit le dernier gestionnaire de e o ea son groupe. Les instructions restant ` excuter dans la fonction qui a lanc lexception et dans les fonctions a e e entre celle-l` et celle qui contient le gestionnaire qui a attrap lexception sont abandonnes. Il est important de a e e noter que les objets locaux attachs aux blocs brusquement abandonns ` cause du lancement dune exception e e a sont proprement dtruits. e Exemple. A plusieurs reprises nous avons vu des classes reprsentant des tableaux dans lesquels les indices e taieznt contrles. Voici comment cela pourrait scrire20 : e o e void IndiceValide(int i, int n) { if (i < 0 || i >= n) throw "indice hors bornes"; }
que, pour une fois, les trois points ... font partie de la syntaxe du langage. une version na ve. Lancer, comme ici, une exception de type cha de caract`res est vite fait, mais lancer une exception ne e objet dune classe, drive de la classe exception, expressment dnie dans ce but, serait plus puissant et utile. e e e e
20 Cest 19 Notez

49

class Vecteur { double *tab; int nbr; public: explicit Vecteur(int n) { tab = new double[nbr = n]; } double &operator[](int i) { IndiceValide(i, nbr); return tab[i]; } ... }; Utilisation : try { cout << "n ? "; cin >> n; Vecteur t(n); ... cout << "i, t[i] ? "; cin >> i >> x; t[i] = x; cout << "t[" << i << "] = " << x << \n; ... } catch (char *message) { // origine : IndiceValide cout << "Probl`me: " << message << \n; e } catch (bad_alloc) { // origine : new cout << "Probl`me dallocation de mmoire\n"; e e } catch (...) { // toutes les autres exceptions cout << "Probl`me indetermin\n"; e e } cout << " - Termin\n"; e Excution : e i, t[i] ? 5 0.125 t[5] = 0.125 ... i, t[i] ? 25 0.250 Probl`me: indice hors bornes e Termin e

6.2

Attraper une exception

Lorsque lexcution dune instruction lance une exception, un gestionnaire catch ayant un argument compae tible avec lexception est recherch dans les fonctions actives (commences et non encore termines), de la plus e e e rcemment appele vers la plus anciennement appele. e e e Un argument dun gestionnaire catch de type T1 , const T1 , T1 & ou const T1 & est compatible avec une exception de type T2 si et seulement si : e T1 et T2 sont le mme type, ou T1 est une classe de base accessible de T2 , ou T1 et T2 sont de la forme B* et D* respectivement, et B est une classe de base accessible de D. Notez que les conversions standard sur des types qui ne sont pas des pointeurs ne sont pas eectues. Ainsi, e par exemple, un gestionnaire catch(float) nattrape pas des exceptions int. Note. Lorsque le contrle entre dans un gestionnaire catch, son argument est initialis (par lexception o e eectivement lance) selon un mcanisme analogue ` celui de linitialisation des arguments dun appel de e e a fonction. Cest donc la mme sorte de considrations que celles que lon fait pour un appel de fonction qui permettent e e de choisir le mode (valeur, adresse ou rfrence) de largument du gestionnaire. On notera en particulier que ee 50

la dclaration comme pointeur ou rfrence est indispensable si on souhaite faire jouer le polymorphisme. Par e ee exemple, le programme suivant est maladroit : catch(exception e) { // maladroit cout << "Probl`me: " << e.what(); e } car on y appelle la fonction what de la classe exception, et lachage obtenu sera trop gnral, dans le e e genre :  Probl`me : Unknown exception . En principe, lexception eectivement lance appartient ` une e e a classe drive de exception, dans laquelle la fonction virtuelle what a t rednie pour donner un message e e ee e intressant. Pour obtenir lachage de ce message il faut crire : e e catch(exception &e) { cout << "Exception: " << e.what(); } ou bien (mais dans ce cas il faut que linstruction throw lance lexception par adresse) : catch(exception *e) { cout << "Exception: " << e->what(); } Relance dune exception. A lintrieur dun gestionnaire catch on peut crire linstruction e e throw; elle indique que lexception, qui tait tenue pour traite en entrant dans le gestionnaire, doit tre relance comme e e e e si elle navait pas encore t attrape. ee e

6.3

Dclaration des exceptions quune fonction laisse chapper e e

Lorsquune exception est lance ` lintrieur dune fonction dans laquelle elle nest pas attrape on dit que e a e e la fonction  laisse chapper  lexception en question. e Une fonction peut indiquer les types des exceptions quelle laisse chapper. Cela dnit avec prcision, ` e e e a lintention des utilisateurs de la fonction, les risques entra es par un appel de cette fonction. La syntaxe est : n en-tte de la fonction throw(type 1 , type 2 , ... type k ) e Exemple : void IndiceValide(int i, int n) throw(char *) { if (i < 0 || i >= n) throw "indice hors bornes"; } class Vecteur { ... double &operator[](int i) throw(char *) { IndiceValide(i, nbr); return tab[i]; } .... }; Plus gnralement, dune fonction dont len-tte se prsente ainsi e e e e en-tte de la fonction throw(T1 , T2 *) e ne peuvent provenir que des exceptions dont le type est T1 ou un type ayant T1 pour classe de base accessible ou T2 * ou un type pointeur vers une classe ayant T2 pour classe de base accessible. Lexpression en-tte de la fonction throw() e indique quun appel dune telle fonction ne peut lancer aucune exception. En outre, on consid`re quune fonction dont len-tte ne comporte pas de clause throw laisse chapper toutes e e e les exceptions. De telles dclarations ne font pas partie de la signature : deux fonctions dont les en-ttes ne dirent quen e e e cela ne sont pas assez direntes pour faire jouer le mcanisme de la surcharge. e e Pour les exceptions explicitement lances par la fonction, cette indication permet une vrication d`s la e e e compilation, avec lmission dventuels messages de mise en garde. e e

51

Quil y ait eu une mise en garde durant la compilation ou pas, lorsquune exception non attrape se produit e dans une fonction qui ne la laisse pas chapper, la fonction prdnie unexpected() est appele. Par dfaut la e e e e e fonction unexpected() se rduit ` lappel de la fonction terminate(). e a

6.4

La classe exception

Une exception est une valeur quelconque, dun type prdni ou bien dun type classe dni ` cet eet. e e e a Cependant, les exceptions lances par les fonctions de la biblioth`que standard sont toutes des objets de classes e e drives dune classe dnie spcialement ` cet eet, la classe exception, qui comporte au minimum les membres e e e e a suivants : class exception { public: exception() throw(); exception(const exception &e) throw(); exception &operator=(const exception &e) throw(); virtual ~exception() throw(); virtual const char *what() const throw(); }; Les quatre premiers membres correspondent aux besoins internes de limplmentation des exceptions. La e fonction what renvoie une cha de caract`res qui est une description informelle de lexception. ne e On notera que, comme le montre la dclaration de cette classe, aucune exception ne peut tre lance durant e e e lexcution dun membre de la classe exception. e Bien que la question concerne plus la biblioth`que standard que le langage lui-mme, voici les principales e e classes drives directement ou indirectement de exception : e e exception bad exception : cette exception, en rapport avec la dnition par lutilisateur de la fonce tion unexpected, signale lmission par une fonction dune exception qui nest pas e dclare dans sa clause throw. e e bad cast : cette exception signale lexcution dune expression dynamic cast invalide. e bad typeid : indique la prsence dun pointeur p nul dans une expression typeid(*p) e logic error : ces exceptions signalent des erreurs provenant de la structure logique interne du programme (en thorie, on aurait pu les prvoir en tudiant le programme) : e e e domain error : erreur de domaine, invalid argument : argument invalide, length error : tentative de cration dun objet de taille suprieure ` la taille e e a maximum autorise (dont le sens prcis dpend du contexte), e e e out of range : arguments en dehors des bornes. bad alloc : cette exception correspond ` lchec dune allocation de mmoire. a e e runtime error : exceptions signalant des erreurs, autres que des erreurs dallocation de mmoire, qui ne peuvent tre dtectes que durant lexcution du programme : e e e e e range error : erreur de rang, overflow error : dbordement arithmtique (par le haut), e e underflow error : dbordement arithmtique (par le bas). e e Remarque. Les classes prcdentes sont surtout des emplacements rservs pour des exceptions que les e e e e fonctions de la biblioth`que standard (actuelle ou future) peuvent ventuellement lancer ; rien ne dit que cest e e actuellement le cas.

Rfrences ee
B. Stroustrup ` Le langage C++ (3eme edition) CampusPress, 1999

52

H. Garreta ` Le langage et la bibliotheque C++ Norme ISO Ellipses, 2000 J. Charbonnel Langage C++, la proposition de standard ANSI/ISO expliquee Masson, 1996 A. Gron, F. Tawbi e Pour mieux developper avec C++ InterEditions, 1999

53

Vous aimerez peut-être aussi