Vous êtes sur la page 1sur 53

Facult e des Sciences de Luminy

D epartement dInformatique

Le langage C++
Henri Garreta

Table des mati` eres


1 El ements pr ealables 1.1 Placement des d eclarations de variables . . . . 1.2 Bool eens . . . . . . . . . . . . . . . . . . . . . . 1.3 R ef erences . . . . . . . . . . . . . . . . . . . . . 1.3.1 Notion . . . . . . . . . . . . . . . . . . . 1.3.2 R ef erences param` etres des fonctions . . 1.3.3 Fonctions renvoyant une r ef erence . . . 1.3.4 R ef erences sur des donn ees constantes . 1.3.5 R ef erences ou pointeurs ? . . . . . . . . 1.4 Fonctions en ligne . . . . . . . . . . . . . . . . 1.5 Valeurs par d efaut des arguments des fonctions 1.6 Surcharge des noms de fonctions . . . . . . . . 1.7 Appel et d enition de fonctions ecrites en C . . 1.8 Entr ees-sorties simples . . . . . . . . . . . . . . 1.9 Allocation dynamique de m emoire . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 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` es aux membres . . . . . . . . . . . . . . . . . . . . 2.2.1 Acc` es aux membres dun objet . . . . . . . . . 2.2.2 Acc` es ` a ses propres membres, acc` es ` a soi-m eme 2.2.3 Membres publics et priv es . . . . . . . . . . . . 2.2.4 Encapsulation au niveau de la classe . . . . . . 2.2.5 Structures . . . . . . . . . . . . . . . . . . . . . 2.3 D enition des classes . . . . . . . . . . . . . . . . . . . 2.3.1 D enition s epar ee et op erateur de r esolution de 2.3.2 Fichier den-t ete et chier dimpl ementation . . 2.4 Constructeurs . . . . . . . . . . . . . . . . . . . . . . . 2.4.1 D enition de constructeurs . . . . . . . . . . . 2.4.2 Appel des constructeurs . . . . . . . . . . . . . 2.4.3 Constructeur par d efaut . . . . . . . . . . . . . 2.4.4 Constructeur par copie (clonage) . . . . . . . . 2.5 Construction des objets membres . . . . . . . . . . . . 2.6 Destructeurs . . . . . . . . . . . . . . . . . . . . . . . 2.7 Membres constants . . . . . . . . . . . . . . . . . . . . 2.7.1 Donn ees membres constantes . . . . . . . . . . 2.7.2 Fonctions membres constantes . . . . . . . . . 2.8 Membres statiques . . . . . . . . . . . . . . . . . . . . 2.8.1 Donn ees membres statiques . . . . . . . . . . . 2.8.2 Fonctions membres statiques . . . . . . . . . . 2.9 Amis . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2.9.1 Fonctions amies . . . . . . . . . . . . . . . . . . 2.9.2 Classes amies . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . port ee . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

3 Surcharge des op erateurs 3.1 Principe . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3.1.1 Surcharge dun op erateur par une fonction membre . . . 3.1.2 Surcharge dun op erateur par une fonction non membre 3.2 Quelques exemples . . . . . . . . . . . . . . . . . . . . . . . . . 3.2.1 Injection et extraction de donn ees dans les ux . . . . . 3.2.2 Aectation . . . . . . . . . . . . . . . . . . . . . . . . . 3.3 Op erateurs de conversion . . . . . . . . . . . . . . . . . . . . . 3.3.1 Conversion vers un type classe . . . . . . . . . . . . . . 3.3.2 Conversion dun objet vers un type primitif . . . . . . . 4 H eritage 4.1 Classes de base et classes d eriv ees . . . . . . . . . . . . 4.2 H eritage et accessibilit e des membres . . . . . . . . . . 4.2.1 Membres prot eg es . . . . . . . . . . . . . . . . 4.2.2 H eritage priv e, prot eg e, public . . . . . . . . . 4.3 Red enition des fonctions membres . . . . . . . . . . . 4.4 Cr eation et destruction des objets d eriv es . . . . . . . 4.5 R ecapitulation sur la cr eation et destruction des objets 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, g en eralisation 4.7 Fonctions virtuelles . . . . . . . . . . . . . . . . . . . . 4.8 Classes abstraites . . . . . . . . . . . . . . . . . . . . . 4.9 Identication dynamique du type . . . . . . . . . . . . 4.9.1 Lop erateur dynamic cast . . . . . . . . . . . . 4.9.2 Lop erateur typeid . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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` eles (templates ) 44 5.1 Mod` eles de fonctions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 45 5.2 Mod` eles de classes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 46 5.2.1 Fonctions membres dun mod` ele . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 47 6 Exceptions 6.1 Principe et syntaxe . . . . 6.2 Attraper une exception . . 6.3 D eclaration des exceptions 6.4 La classe exception . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . quune fonction laisse echapper . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 48 48 50 51 52

19 mars 2002 henri.garreta@luminy.univ-mrs.fr

El ements pr ealables

Ce document est le support du cours sur le langage C++, consid er e comme une extension de C (tel que normalis e par lISO en 1990), langage pr esum e bien connu. Attention, la pr esentation faite ici est d es equilibr ee : des concepts importants ne sont pas expliqu es, pour la raison quils sont r ealis es en C++ comme en C, donc suppos es acquis. En revanche, nous insistons sur les di erences entre C et C++ et notamment sur tous les el ements  orient es objets  que C++ ajoute ` a C. Cette premi` ere section expose un certain nombre de notions qui, sans etre directement li es ` a la m ethodologie objets, font d ej` a appara tre C++ comme une am elioration notable de C.

1.1

Placement des d eclarations de variables

En C les d eclarations de variables doivent appara tre au d ebut dun bloc. En C++, au contraire, on peut mettre une d eclaration de variable partout o` u on peut mettre une instruction1 . Cette di erence para t mineure, mais elle est importante par son esprit. Elle permet de repousser la d eclaration dune variable jusqu` a lendroit du programme o` u lon dispose dassez el ements pour linitialiser. On lutte aussi contre les variables  d ej` a d eclar ees mais non encore initialis ees  qui sont un important vivier de bugs dans les programmes. Exemple, lemploi dun chier. Version C : { FILE *fic; ... obtention de nomFic, le nom du chier ` a ouvrir Danger ! Tout emploi de fic ici est erron e ... fic = fopen(nom, "w"); ... ici, lemploi de fic est l egitime ... } Version C++ : { ... obtention de nomFic, le nom du chier ` a ouvrir ici pas de danger dutiliser fic ` a tort ... FILE *fic = fopen(nom, "w"); ... ici, lemploi de fic est l egitime ... }

1.2

Bool eens

En plus des types d enis par lutilisateur (ou classes, une des notions fondamentales de la programmation orient ee objets) C++ poss` ede quelques types qui manquaient ` a C, notamment le type bool een et les types r ef erences. Le type bool (pour bool een) comporte deux valeurs : false et true. Contrairement ` aC: le r esultat dune op eration logique (&&, ||, etc.) est de type bool een, a o` u une condition est attendue on doit mettre une expression de type bool een et l` il est d econseill e de prendre les entiers pour des bool eens et r eciproquement. Cons equence pratique : n ecrivez pas  if (x) ...  au lieu de  if (x != 0) ... 

1.3
1.3.1

R ef erences
Notion

A c ot e des pointeurs, les r ef erences sont une autre mani` ere de manipuler les adresses des objets plac es dans la m emoire. Une r ef erence est un pointeur g er e de mani` ere interne par la machine. Si T est un type donn e, le
1 Bien

entendu, une r` egle absolue reste en vigueur : une variable ne peut etre utilis ee quapr` es quelle ait et e d eclar ee.

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

Une valeur de type r ef erence est une adresse mais, hormis lors de son initialisation, toute op eration eectu ee sur la r ef erence agit sur lobjet r ef erenc e, non sur ladresse. Il en d ecoule quil est obligatoire dinitialiser une r ef erence lors de sa cr eation ; apr` es, cest trop tard : r = j; // ceci ne transforme pas r en une r ef erence // sur j mais copie la valeur de j dans i

1.3.2

R ef erences param` etres des fonctions

Lutilit e principale des r ef erences est de permettre de donner aux fonctions des param` etres modiables, sans 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` etres formels a et b sont initialis es avec les adresses des param` etres eectifs t[i] et u, mais cette utilisation des adresses reste cach ee, le programmeur na pas ` a sen occuper. Autrement dit, ` a loccasion dun tel appel, a et b ne sont pas des variables locales de la fonction recevant des copies des valeurs des param` etres, mais dauthentiques synonymes des variables t[i] et u. Il en r esulte que lappel ci-dessus permute eectivement les valeurs des variables t[i] et u2 . 1.3.3 Fonctions renvoyant une r ef erence

Il est possible d ecrire des fonctions qui renvoient une r ef erence comme r esultat. Cela leur permet d etre le membre gauche dune aectation, ce qui donne lieu ` a des expressions el egantes et ecaces. Par exemple, voici comment une fonction permet de simuler un tableau index e par des cha nes de caract` eres3 : 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 l ecriture dexpressions qui ressemblent ` a des acc` es ` a un tableau dont les indices seraient des cha nes : age("Am elie") = 20; ou encore age("Benjamin")++; 1.3.4 R ef erences sur des donn ees constantes

Lors de l ecriture dune fonction il est parfois souhaitable dassocier lecacit e des arguments r ef erences (puisque dans le cas dune r ef erence il ny a pas recopie de la valeur de largument) et la s ecurit e des arguments par valeur (qui ne peuvent en aucun cas etre modi es par la fonction). Cela peut se faire en d eclarant des arguments comme des r ef erences sur des objets immodiables, ou constants, ` a laide du qualieur const :
2 Notez quune telle chose est impossible en C, o` u une fonction appel ee par lexpression permuter(t[i], u) ne peut recevoir que des copies des valeurs de de t[i] et u. 3 Cest un exemple simpliste, pour faire court nous ny avons pas trait e le cas de labsence de la cha ne cherch ee.

void uneFonction(const unType & arg) { ... ici arg nest pas une recopie de largument eectif (puisque r ef erence) mais il ne peut pas etre modi e par la fonction (puisque const) ... } 1.3.5 R ef erences ou pointeurs ?

Il existe quelques situations, nous les verrons plus loin, o` u les r ef erences se r ev` elent indispensables. Cependant, la plupart du temps elles font double emploi avec les pointeurs et il nexiste pas de crit` eres simples pour choisir des r ef erences plut ot que des pointeurs ou le contraire. Par exemple, la fonction permuter montr ee ` a la page pr ec edente peut s ecrire aussi : void permuter(int *a, int *b) { int w = *a; *a = *b; *b = w; } son appel s ecrit alors permuter(&t[i], &u); Attention On notera que lop erateur & (` a un argument) a une signication tr` es di erente selon le contexte dans lequel il appara t : employ e dans une d eclaration, comme dans int &r = x; il sert ` a indiquer un type r ef erence :  r est une r ef erence sur un int  employ e ailleurs que dans une d eclaration il indique lop eration  obtention de ladresse , comme dans lexpression suivante qui signie  aecter ladresse de x ` ap: p = &x; enn, on doit se souvenir quil y a en C++, comme en C, un op erateur & binaire (` a deux arguments) qui exprime la conjonction bit-` a-bit de deux mots-machine.

1.4

Fonctions en ligne

Normalement, un appel de fonction est une rupture de s equence : ` a lendroit o` u un appel gure, la machine cesse dex ecuter s equentiellement les instructions en cours ; les arguments de lappel sont dispos es sur la pile dex ecution, et lex ecution continue ailleurs, l` a o` u se trouve le code de la fonction. Une fonction en ligne est le contraire de cela : l` a o` u lappel dune telle fonction appara t il ny a pas de rupture de s equence. Au lieu de cela, le compilateur remplace lappel de la fonction par le corps de celle-ci, en mettant les arguments aectifs ` a la place des arguments formels. Cela se fait dans un but decacit e : puisquil ny a pas dappel, pas de pr eparation des arguments, pas de rupture de s equence, pas de retour, etc. Mais il est clair quun tel traitement ne peut convenir qu` a des fonctions fr equemment appel ees (si une fonction ne sert pas souvent, ` a quoi bon la rendre plus rapide ?) de petite taille (sinon le code compil e risque de devenir d emesur ement volumineux) et rapides (si une fonction eectue une op eration lente, le gain de temps obtenu en supprimant lappel est n egligeable). En C++ on indique quune fonction doit etre trait ee en ligne en faisant pr ec eder sa d eclaration par le mot inline : inline int abs(int x) { return x >= 0 ? x : -x; } Les fonctions en ligne de C++ rendent le m eme service que les macros (avec arguments) de C, mais on notera que les fonctions en ligne sont plus ables car, contrairement ` a ce qui se passe pour les macros, le compilateur peut y eectuer tous les contr oles syntaxiques et s emantiques quil fait sur les fonctions ordinaires. La port ee dune fonction en ligne est r eduite au chier o` u la fonction est d enie. Par cons equent, de telles fonctions sont g en eralement ecrites dans des chiers en-t ete (chiers  .h ), qui doivent etre inclus dans tous les chiers comportant des appels de ces fonctions.

1.5

Valeurs par d efaut des arguments des fonctions


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

Les param` etres formels dune fonction peuvent avoir des valeurs par d efaut. Exemple : Lors de lappel dune telle fonction, les param` etres eectifs correspondants peuvent alors etre omis (ainsi que les virgules correspondantes), les param` etres formels seront initialis es avec les valeurs par d efaut. Exemples : 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 el ements suppl ementaires, que nous verrons plus loin). Le type du r esultat rendu par la fonction ne fait pas partie de sa signature. La surcharge des noms des fonctions consiste en ceci : en C++ des fonctions di erentes peuvent avoir le m eme nom, ` a la condition que leurs signatures soient assez di erentes pour que, lors de chaque appel, le nombre et les types des arguments eectifs permettent de choisir sans ambigu t e la fonction ` a appeler. Exemple : 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 lint er et de la surcharge des noms des fonctions : la m eme notion abstraite  x ` la puissance n  se traduit par des algorithmes tr` a es di erents selon que n est entier (xn = x.x...x) ou r eel (xn = en log x ) ; de plus, pour n entier, si on veut que xn soit entier lorsque x est entier, on doit ecrire deux fonctions distinctes, une pour x entier et une pour x r eel. Or le programmeur naura quun nom ` a conna tre, puissance. Il ecrira dans tous les cas c = puissance(a, b); le compilateur se chargeant de choisir la fonction la plus adapt ee, selon les types de a et b. Notes. 1. Le type du r esultat rendu par la fonction ne fait pas partie de la signature. Par cons equent, on ne peut pas donner le m eme nom ` a deux fonctions qui ne di erent que par le type du r esultat quelles rendent. 2. Lors de lappel dune fonction surcharg ee, la fonction eectivement appel ee est celle dont la signature correspond avec les types des arguments eectifs de lappel. Sil y a une correspondance exacte, pas de probl` eme. Sil ny a pas de correspondance exacte, alors des r` egles complexes (trop complexes pur les expliquer ici) sappliquent, pour d eterminer la fonction ` a appeler. Malgr e ces r` egles, il existe de nombreux cas de gure ambigus, que le compilateur ne peut pas r esoudre. Par exemple, si x est ottant et n entier, lappel puissance(n, x) est erron e, alors quil aurait et e correct sil y avait eu une seule d enition de la fonction puissance (les conversions entier ottant n ecessaires auraient alors et e faites).

1.7

Appel et d enition de fonctions ecrites en C

Pour que la compilation et l edition de liens dun programme avec des fonctions surcharg ees soient possibles, le compilateur C++ doit fabriquer pour chaque fonction un nom long comprenant le nom de la fonction et une repr esentation cod ee de sa signature ; cest ce nom long qui est communiqu e` a l editeur de liens. Or, les fonctions produites par un compilateur C nont pas de tels noms longs ; si on ne prend pas de disposition particuli` ere, il sera donc impossible dappeler dans un programme C++ une fonction ecrite en C, ou r eciproquement. On rem edie ` a cette impossibilit e par lutilisation de la d eclaration  extern C  : extern "C" { d eclarations et d enitions d el ements dont le nom est repr esent e` a la mani` ere de C }

1.8

Entr ees-sorties simples

Cette section traite de lutilisation simple des ux standard dentr ee-sortie, cest-` a-dire la mani` ere de faire en C++ les op erations quon fait habituellement en C avec les fonctions printf et scanf. Un programme qui utilise les ux standard dentr ee-sortie doit comporter la directive #include <iostream.h> ou bien, si vous utilisez un compilateur r ecent et que vous suivez de pr` es les recommandations de la norme4 : #include <iostream> using namespace std; Les ux dentr ee-sortie sont repr esent es dans les programmes par les trois variables, pr ed eclar ees et pr einitialis ees, suivantes : cin, le ux standard dentr ee (l equivalent du stdin de C), qui est habituellement associ e au clavier du poste de travail, cout, le ux standard de sortie (l equivalent du stdout de C), qui est habituellement associ e` a l ecran du poste de travail, cerr, le ux standard pour la sortie des messages derreur (l equivalent du stderr de C), egalement associ e a l ` ecran du poste de travail. Les ecritures et lectures sur ces unit es ne se font pas en appelant des fonctions, mais ` a laide des op erateurs <<, appel e op erateur dinjection ( injection  de donn ees dans un ux de sortie), et >>, appel e op erateur dextraction ( extraction  de donn ees dun ux dentr ee). Or, le m ecanisme de la surcharge des op erateurs (voir la section 3) permet la d etection des types des donn ees ` a lire ou ` a ecrire. Ainsi, le programmeur na pas ` a sencombrer avec des sp ecications de format. La syntaxe dun injection de donn ee sur la sortie standard cout est : cout << expression ` a ecrire le r esultat de cette expression est lobjet cout lui-m eme. On peut donc lui injecter une autre donn ee, puis encore une, etc. : ((cout << expression ) << expression ) << expression ce qui, lop erateur << etant associatif ` a gauche, se note aussi, de mani` ere bien plus agr eable : cout << expression << expression << expression Le m eme proc ed e existe avec lextraction depuis cin. Par exemple, le programme suivant est un programme 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 dex ecution de ce programme : Donne x et n : 2 10 2^10 = 1024
4 Cest-` a-dire, si vous utilisez les espaces de noms, ou namespace (tous les el ements de la biblioth` eque standard sont dans lespace de noms std).

1.9

Allocation dynamique de m emoire

Des di erences entre C et C++ existent aussi au niveau de lallocation et de la restitution dynamique de m emoire. Les fonctions malloc et free de la biblioth` eque standard C sont disponibles en C++. Mais il est fortement conseill e de leur pr ef erer les op erateurs new et delete. La raison principale est la suivante : les objets cr e es ` a laide de new sont initialis es ` a laide des constructeurs (cf. section 2.4) correspondants, ce que ne fait pas malloc. De m eme, les objets liber es en utilisant delete sont nalis es en utilisant le destructeur (cf. section 2.6) de la classe correspondante, contrairement ` a ce que fait free. 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-` a-dire  type * . Exemples (on suppose que Machin est un type d eni par ailleurs) : Machin *ptr = new Machin; int *tab = new int[n]; // un objet Machin // un tableau de n int

Si type est une classe poss edant un constructeur par d efaut, celui-ci sera appel e une fois (cas de lallocation dun objet simple) ou n fois (allocation dun tableau dobjets) pour construire lobjet ou les objets allou es. Si type est une classe sans constructeur par d efaut, une erreur sera signal ee par le compilateur. Pour un tableau, la dimension n peut etre donn ee par une variable, cest-` a-dire etre inconnue lors de la compilation, mais la taille des composantes doit etre connue. Il en d ecoule que dans le cas dun tableau ` a plusieurs indices, seule la premi` ere dimension peut etre non constante : 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 etre utilis e comme une matrice ` a n lignes et 10 colonnes. Lop erateur delete restitue la m emoire dynamique. Si la valeur de p a et e obtenue par un appel de new, on ecrit delete p; dans le cas dun objet qui nest pas un tableau, et delete [] p; si ce que p pointe est un tableau. Les cons equences dune utilisation de delete l` a o` u il aurait fallu utiliser delete[], ou inversement, sont impr evisibles.

2
2.1

Classes
Classes et objets

Un objet est constitu e par lassociation dune certaine quantit e de m emoire, organis ee en champs, et dun ensemble de fonctions, destin ees principalement ` a la consultation et la modication des valeurs de ces champs. La d enition dun type objet sappelle une classe. Dun point de vue syntaxique, cela ressemble beaucoup ` a une d enition de structure, sauf que eserv e class remplace5 le mot struct, le mot r certains champs de la classe sont des fonctions. Par exemple, le programme suivant est une premi` ere version dune classe Point destin ee ` a repr esenter les points ach es dans une fen etre graphique :
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 m emoire, compos ee de deux entiers x et y, et de deux fonctions : afficher, qui acc` ede ` a x et y sans les modier, et placer, qui change les valeurs de x et y. Lassociation de membres et fonctions au sein dune classe, avec la possibilit e de rendre priv es certains dentre eux, sappelle lencapsulation des donn ees. Int er et de la d emarche : puisquelles ont et e d eclar ees priv ees, les coordonn ees x et y dun point ne peuvent etre modi ees autrement que par un appel de la fonction placer sur ce point. Or, en prenant les pr ecautions n ecessaires lors de l ecriture de cette fonction (ce que nous avons not e  validation des valeurs de a et b ) le programmeur responsable de la classe Point peut garantir aux utilisateurs de cette classe que tous les objets cr ees auront toujours des coordonn ees correctes. Autrement dit : chaque objet peut prendre soin de sa propre coh erence interne. Autre avantage important : on pourra ` a tout moment changer limpl ementation (i.e. les d etails internes) de la classe tout en ayant la certitude quil ny aura rien ` a changer dans les programmes qui lutilisent. Note. Dans une classe, les d eclarations des membres peuvent se trouver dans un ordre quelconque, m eme lorsque ces membres se r ef erencent mutuellement. Dans lexemple pr ec edent, le membre afficher mentionne les membres x et y, dont la d enition se trouve apr` es celle de afficher. Jargon. On appelle objet une donn ee dun type classe ou structure, fonction membre un membre dune classe qui est une fonction6 , donn ee membre un membre qui est une variable7 .

2.2
2.2.1

Acc` es aux membres


Acc` es aux membres dun objet

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

Si on suppose que le pointeur pt a et e initialis e, par exemple par une expression telle que alors des acc` es analogues aux pr ec edents s ecrivent :

A propos de lacc` es ` a un membre dun objet, deux questions se posent. Il faut comprendre quelles sont tout a fait ind ` ependantes lune de lautre : lacc` es est-il bien ecrit ? cest-` a-dire, d esigne-t-il bien le membre voulu de lobjet voulu ? es est-il l egitime ? cest-` a-dire, dans le contexte o` u il est ecrit, a-t-on le droit dacc` es sur le membre cet acc` en question ? La question des droits dacc` es est trait ee ` a la section 2.2.3.
la plupart des langages orient es objets, les fonctions membres sont appel ees m ethodes. beaucoup de langages orient es objets, les donn ees membres sont appel ees variables dinstance et aussi, sous certaines conditions, propri et es 8 Notez que, une fois la classe d eclar ee, il nest pas obligatoire d ecrire class devant Point pour y faire r ef erence.
7 Dans 6 Dans

2.2.2

Acc` es ` a ses propres membres, acc` es ` a soi-m eme

Quand des membres dun objet apparaissent dans une expression ecrite dans une fonction du m eme objet on dit que ce dernier fait un acc` es ` a ses propres membres. On a droit dans ce cas ` a une notation simpli ee : on ecrit le membre tout seul, sans expliciter lobjet en 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 ` a travers lequel on aura appel e cette fonction. Autrement dit, lors dun appel comme unPoint.afficher(); le corps de cette fonction sera equivalent ` a cout << ( << unPoint.x << , << unPoint.y << ); `s a ` soi-me me. Il arrive que dans une fonction membre dun objet on doive faire r Acce ef erence ` a lobjet (tout entier) ` a travers lequel on a appel e la fonction. Il faut savoir que dans une fonction membre9 on dispose de la pseudo variable this qui repr esente un pointeur vers lobjet en question. Par exemple, la fonction acher peut s ecrire de mani` ere equivalente, mais cela na aucun int er et : void afficher() { cout << ( << this->x << , << this->y << ); } Pour voir un exemple plus utile dutilisation de this imaginons quon nous demande dajouter ` a la classe Point deux fonctions bool eennes, une pour dire si deux points sont egaux, une autre pour dire si deux points sont le m eme objet. Dans les deux cas le deuxi` eme point est donn e par un pointeur : 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 priv es

Par d efaut, les membres des classes sont priv es. Les mots cl es public et private permettent de modier les droits dacc` es des membres : class nom { les membres d eclar es ici sont priv es public: les membres d eclar es ici sont publics private: les membres d eclar es ici sont priv es 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 d eclar es apr` es private: (resp. public:) sont priv es (resp. publics) jusqu` a la n de la classe, ou jusqu` a la rencontre dune expression public: (resp. private:). Un membre public dune classe peut etre acc ed e partout o` u il est visible ; un membre priv e ne peut etre acc ed e que depuis une fonction membre de la classe (les notions de membre prot eg e, cf. section 4.2.1, et de 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 t e, constituent des acc` es ill egaux aux membres priv es x et y de la classe Point, es l egaux aux membres publics afficher les expressions p.afficher() ou p.placer(u, v) sont des acc` et placer, qui se r esolvent en des acc` es parfaitement l egaux aux membres p.x et p.y. 2.2.4 Encapsulation au niveau de la classe

Les fonctions membres dune classe ont le droit dacc eder ` a tous les membres de la classe : deux objets de la m eme classe ne peuvent rien se cacher. Par exemple, le programme suivant montre notre classe Point augment ee dune fonction pour calculer la distance dun point ` a un autre : 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` ede aux membres priv es x et y de lobjet q. On dit que C++ pratique lencapsulation au niveau de la classe, non au niveau de lobjet. On notera au passage que, contrairement ` a dautres langages orient es objets, en C++ encapsuler nest pas cacher mais interdire. Les usagers dune classe voient les membres priv es de cette derni` ere, mais ne peuvent pas les utiliser. 2.2.5 Structures

Une structure est la m eme chose quune classe mais, par d efaut, les membres y sont publics. Sauf pour ce qui touche cette question, tout ce qui sera dit dans la suite ` a propos des classes sappliquera donc aux structures : struct nom { les membres d eclar es ici sont publics private: les membres d eclar es ici sont priv es public: les membres d eclar es ici sont publics etc. };

2.3
2.3.1

D enition des classes


D enition s epar ee et op erateur de r esolution de port ee

Tous les membres dune classe doivent etre au moins d eclar es ` a lint erieur de la formule classnom {...} ; qui constitue la d eclaration de la classe. 11

Cependant, dans le cas des fonctions, aussi bien publiques que priv ees, on peut se limiter ` a n ecrire que leur en-t ete ` a lint erieur de la classe et d enir le corps ailleurs, plus loin dans le m eme chier ou bien dans un autre chier. Il faut alors un moyen pour indiquer quune d enition de fonction, ecrite en dehors de toute classe, est en r ealit e la d enition dune fonction membre dune classe. Ce moyen est lop erateur de r esolution de port ee, dont la syntaxe est NomDeClasse :: Par exemple, voici notre classe Point avec la fonction distance d enie s epar ement : class Point { public: ... double distance(Point autrePoint); ... } Il faut alors, plus loin dans le m eme chier ou bien dans un autre chier, donner la d enition de la fonction  promise  dans la classe Point. Cela s ecrit : double Point::distance(Point autrePoint) { int dx = x - autrePoint.x; int dy = y - autrePoint.y; return sqrt(dx * dx + dy * dy); }; D enir les fonctions membres ` a lext erieur de la classe all` ege la d enition de cette derni` ere et la rend plus compacte. Mais la question nest pas questh etique, il y a une di erence de taille : les fonctions d enies ` a lint erieur dune classe sont implicitement quali ees  en ligne  (cf. section 1.4). Cons equence : la plupart des fonctions membres seront d enies s epar ement. Seules les fonctions courtes, rapides et fr equemment appel ees m eriteront d etre d enies dans la classe. 2.3.2 Fichier den-t ete et chier dimpl ementation

En programmation orient ee objets,  programmer  cest d enir des classes. Le plus souvent ces classes sont destin ees ` a etre utilis ees dans plusieurs programmes, pr esents et ` a venir10 . Se pose alors la question : comment disposer le code dune classe pour faciliter son utilisation ? Voici comment on proc` ede g en eralement : les d enitions des classes se trouvent dans des chiers en-t ete (chiers  .h ,  .hpp , etc.), chacun des ces chiers en-t ete contient la d enition dune seule classe ou dun groupe de classes intimement li ees ; par exemple, la d enition de notre classe Point pourrait constituer un chier Point.h les d enitions des fonctions membres qui ne sont pas d enies ` a lint erieur de leurs classes sont ecrites dans des chiers sources (chiers  .cpp  ou  .cp ), aux programmeurs utilisateurs de ces classes sont distribu es : les chiers  .h  le chiers objets r esultant de la compilation des chiers  .cpp  Par exemple, voici les chiers correspondants ` a notre classe Point (toujours tr` es modeste) : 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

r eutilisabilit e du code est une des motivations de la m ethodologie orient ee objets.

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 e g en eralement Point.o ou Point.obj). Dans ces conditions, la  distribution  de la classe Point sera compos ee des deux chiers Point.h et Point.obj, ce dernier ayant eventuellement et e transform e en un chier biblioth` eque (nomm e alors Point.lib ou quelque chose comme ca). Bien entendu, tout programme utilisateur de la classe Point devra comporter la directive #include "Point.h" et devra, une fois compil e, etre reli e au chier Point.obj ou Point.lib.

2.4
2.4.1

Constructeurs
D enition de constructeurs

Un constructeur dune classe est une fonction membre sp eciale qui : a le m eme nom que la classe, nindique pas de type de retour, ne contient pas dinstruction return. Le r ole dun constructeur est dinitialiser un objet, notamment en donnant des valeurs ` a ses donn ees membres. Le constructeur na pas ` a soccuper de trouver lespace pour lobjet ; il est appel e (imm ediatement) apr` es que cet espace ait et e obtenu, et cela quelle que soit la sorte dallocation qui a et e faite : statique, automatique ou 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 e, explicitement (voir ci-dessous) ou implicitement, lorsquun objet de cette classe est cr e e, et en particulier chaque fois quune variable ayant cette classe pour type est d enie. Cest le couple d enition de la variable + appel du constructeur qui constitue la r ealisation en C++ du concept  cr eation dun objet . Lint er et pour le programmeur est evident : garantir que, d` es leur introduction dans un programme, tous les objets sont garnis et coh erents, cest-` a-dire eviter les variables ind enies, au contenu incertain. Une classe peut poss eder plusieurs constructeurs, qui doivent alors avoir des signatures di erentes :

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` etres avec des valeurs par d efaut permet de grouper des constructeurs. La classe suivante poss` ede les m emes constructeurs que la pr ec edente : 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 etre d eclar es dans la classe et d enis ailleurs. Ainsi, la classe pr ec edente pourrait s ecrire egalement 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; } ne rales. 1. Comme lexemple ci-dessus le montre, lorsquune fonction fait lobjet Deux remarques ge dune d eclaration et dune d enition s epar ees, comme le constructeur Point, les eventuelles valeurs par d efaut des argument concernent la d eclaration, non la d enition. 2. Lorsquune fonction fait lobjet dune d eclaration et dune d enition s epar ees, les noms des arguments ne sont utiles que pour la d enition. Ainsi, la d eclaration du constructeur Point ci-dessus peut s ecrire egalement : class Point { ... Point(int = 0, int = 0); ... };

14

2.4.2

Appel des constructeurs

Un constructeur est toujours appel e lorsquun objet est cr ee, soit explicitement, soit implicitement. Les appels explicites peuvent etre ecrits sous deux formes : Point a(3, 4); Point b = Point(5, 6); Dans le cas dun constructeur avec un seul param` etre, on peut aussi adopter une forme qui rappelle linitialisation des variables de types primitifs (` a ce propos voir aussi la section 3.3.1) : Point e = 7; // equivaut ` a : Point e = Point(7) Un objet allou e dynamiquement est lui aussi toujours initialis e, au mois implicitement. Dans beaucoup de cas il peut, ou doit, etre initialis e explicitement. Cela s ecrit : Point *pt; ... pt = new Point(1, 2); Les constructeurs peuvent aussi etre utilis es pour initialiser des objets temporaires, anonymes. En fait, chaque fois quun constructeur est appel e, un objet nouveau est cr ee, m eme si cela ne se passe pas ` a loccasion de la d enition dune variable. Par exemple, deux objets sans nom, repr esentant les points (0,0) et (3,4), sont cr e es dans linstruction suivante11 : cout << Point(0, 0).distance(Point(3, 4)) << "\n"; Note. Lappel dun constructeur dans une expression comportant un signe = peut pr eter ` a confusion, ` a cause de sa ressemblance avec une aectation. Or, en C++, linitialisation et laectation sont deux op erations distinctes, du moins lorsquelles concernent des variables dun type classe : linitialisation consiste ` a donner une premi` ere valeur ` a une variable au moment o` u elle commence ` a exister ; laectation consiste ` a remplacer la valeur courante dune variable par une autre valeur ; les op erations mises en uvre par le compilateur, constructeur dans un cas, op erateur daectation dans lautre, ne sont pas les m emes. Comment distinguer le  =  dune aectation de celui dune initialisation ? Grossi` erement, lorsque lexpression commence par un type, il sagit dune d enition et le signe = correspond ` a une initialisation. Exemple : Point a = Point(1, 2); // Initialisation de a Cette expression cr ee la variable a et linitialise en rangeant dans a.x et a.y les valeurs 1 et 2. En revanche, 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 cr ee un point anonyme de coordonn ees (1,2) et le recopie sur la variable a en remplacement de la valeur courante de cette variable, construite peu avant. On arrive au m eme r esultat que pr ec edemment, mais au prix de deux initialisations et une aectation ` a la place dune seule initialisation. 2.4.3 Constructeur par d efaut

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

`se dun constructeur par de faut. Puisque tout objet doit Synthe etre initialis e lors de sa cr eation, si le programmeur ecrit une classe sans aucun constructeur, alors le compilateur synth etise un constructeur par d efaut comme ceci :
11 Ces objets anonymes ne pouvant servir ` a rien dautre dans ce programme, ils seront d etruits lorsque cette instruction aura et e ex ecut ee.

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 synth etis e est le constructeur trivial, qui consiste ` a ne rien faire. Les donn ees membres seront cr e ees comme elles lauraient et e en C, cest-` a-dire initialis ees par z ero sil sagit dune variable globale, laiss ees ind etermin ees sil sagit dune variable locale ou dynamique, etis e produit lappel si la classe a des objets membres ou des classes de base, alors le constructeur synth du constructeur par d efaut de chaque objet membre et de chaque classe de base. Attention. Si au moins un constructeur est d eni pour une classe, alors aucun constructeur par d efaut nest synth etis e par le compilateur. Par cons equent, ou bien lun des constructeurs explicitement d enis est un constructeur par d efaut, ou bien toute cr eation dun objet devra expliciter des valeurs dinitialisation. 2.4.4 Constructeur par copie (clonage)

Le constructeur par copie dune classe C est un constructeur dont le premier param` etre est de type  C &  (r ef erence sur un C ) ou  const C &  (r ef erence sur un C constant) et dont les autres param` etres, sils existent, ont des valeurs par d efaut. Ce constructeur est appel e lorsquun objet est initialis e en copiant un objet existant. Cela arrive parfois explicitement, mais souvent implicitement, notamment chaque fois quun objet est pass e comme param` etre par valeur ` a une fonction ou rendu comme r esultat par valeur (c.-` a-d. autre quune r ef erence) dune fonction. Si le programmeur na pas d eni de constructeur de copie pour une classe, le compilateur synth etise un 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 ` a faire la copie  bit ` a bit  dun objet sur lautre. A titre dexemple le programme suivant introduit une nouvelle vari et e de point ; ` a chacun est associ ee une etiquette qui est une cha ne de caract` eres : 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 etiquette La gure 1 repr esente la structure de ces objets. Lorsquun objet comporte des pointeurs, comme ici, linformation quil repr esente (appelons-la l objet logique ) ne se trouve pas enti` erement incluse dans lespace contigu que le compilateur conna t (l objet technique ), car des morceaux dinformation (dans notre exemple le texte de l etiquette) se trouvent ` a dautres endroits de la m emoire. Ainsi, la copie bit ` a bit que fait le compilateur peut etre inadapt ee ` a de tels objets. La gure 2 montre le r esultat de la copie bit ` a bit dun objet Point telle que la produirait, avec lactuelle d enition de la classe, une aectation telle que b = a; (a et b sont des variables de type Point). Bien entendu, la copie du pointeur na pas dupliqu e la cha ne point ee : les deux objets, loriginal et la copie, partagent la m eme cha ne. La plupart du temps ce partage nest pas souhaitable, car dicile ` a g erer et dangereux : toute modication de l etiquette dun des deux points se r epercutera imm ediatement sur lautre. Pour r esoudre ce probl` eme il faut equiper notre classe dun constructeur par copie :

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 ne de caract` eres point ee, comme 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 ` a leur tour dun type classe on dit que la classe a des objets membres. Linitialisation dun objet de la classe n ecessite alors linitialisation de ces objets membres. Il en est toujours ainsi, ind ependamment du fait que lon emploie ou non un constructeur explicite, et qu` a son tour ce constructeur appelle ou non explicitement des constructeurs des objets membres. Lorsque les objets membres nont pas de constructeurs par d efaut, une syntaxe sp eciale12 permet de pr eciser les arguments des constructeurs des membres : NomDeLaClasse (param` etres ) : membre (param` etres ), ... membre (param` etres ) { corps du constructeur } A titre dexemple, imaginons que notre classe Point ne poss` ede pas de constructeur sans arguments, et quon doive d enir une classe Segment ayant deux points pour membres (un segment est d etermin e par deux points). Voici comment on devra ecrire son constructeur : 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-m eme fonctionn e
12 A

titre dexercice on v eriera que, sans cette syntaxe sp eciale, la construction des objets membres serait impossible.

17

class Segment { ... Segment(int ox, int oy, int ex, int ey, int ep) { origine = Point(ox, oy); // Version erron ee extremite = Point(ex, ey); epaisseur = ep; } ... }; mais il faut comprendre que cette version est tr` es maladroite, car faite de deux aectations (les deux lignes qui forment le corps du constructeur ne sont pas des d eclarations). Ainsi, au lieu de se limiter ` a initialiser les membres origine et extr emit e, on proc` ede successivement ` a la construction de origine et extr emit e en utilisant le constructeur sans arguments de la classe Point, la construction de deux points anonymes, initialis es avec les valeurs de ox, oy, ex et ey, l ecrasement des valeurs initiales de origine et extr emit e par les deux points ainsi construits. Note. La syntaxe sp eciale pour linitialisation des objets membres peut etre utilis ee aussi pour initialiser les donn ees membres de types primitifs. Par exemple, le constructeur de Segment pr ec edent peut aussi s ecrire : 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 m eme mani` ere quil y a des choses ` a faire pour initialiser un objet qui commence ` a exister, il y a parfois des dispositions ` a prendre lorsquun objet va dispara tre. Un destructeur est une fonction membre sp eciale. Il a le m eme nom que la classe, pr ec ed e du caract` ere ~. Il na pas de param` etre, ni de type de retour. Il y a donc au plus un destructeur par classe. Le destructeur dune classe est appel e lorsquun objet de la classe est d etruit, juste avant que la m emoire occup ee par lobjet soit r ecup er ee par le syst` eme. Par exemple, voici le destructeur quil faut ajouter ` a notre classe PointNomm e. Sans ce destructeur, la destruction dun point nentra nerait pas la lib eration de lespace allou e pour son etiquette : class PointNomme { ... ~PointNomme() { delete [] label; } ... }; Notez que le destructeur na pas ` a sinqui eter de restituer lespace occup e par l objet technique  lui-m eme, form e, ici, par les variables x, y et label (le pointeur, non la cha ne point ee). Cette restitution d epend du type de m emoire que lobjet occupe, statique, automatique ou dynamique, et ne regarde pas le destructeur, de la m eme mani` ere que son allocation ne regardait pas le constructeur qui a initialis e lobjet. `se du destructeur. Si le programmeur na pas Synthe ecrit de destructeur pour une classe, le compilateur en synth etise un, de la mani` ere suivante : si la classe na ni objets membres ni classes de base (cf. section 4), alors il sagit du destructeur trivial qui consiste ` a ne rien faire, si la classe a des classes de base ou des objets membres, le destructeur synth etis e consiste ` a appeler les destructeurs des donn ees membres et des classes de base, dans lordre inverse de lappel des constructeurs correspondants.

18

2.7
2.7.1

Membres constants
Donn ees membres constantes

Une donn ee membre dune classe peut etre quali ee const. Il est alors obligatoire de linitialiser lors de la construction dun objet, et sa valeur ne pourra par la suite plus etre modi ee. A titre dexemple voici une nouvelle version de la classe Segment, dans laquelle chaque objet re coit, lors de sa cr eation, un  num ero de s erie  qui ne doit plus changer au cours de la vie de lobjet : 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 erron ee : 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 e` a la n de len-t ete dune fonction membre indique que l etat de lobjet ` a travers lequel la fonction est appel ee nest pas chang e du fait de lappel. Cest une mani` ere de d eclarer quil sagit dune fonction 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 lint erieur dune fonction const dune classe C le pointeur this est de type  const C * const  (pointeur constant vers un C constant ) : lobjet point e par this ne pourra pas etre modi e. Cela permet au compilateur dautoriser certains acc` es qui, sans cela, auraient et e interdits. Par exemple, examinons la situation suivante : void uneFonction(const Point a) { Point b; ... double d = a.distance(b); ... } la qualication const de la fonction distance est indispensable pour que lexpression pr ec edente soit accept ee par le compilateur. Cest elle seule, en eet, qui garantit que le point a, contraint ` a rester constant, ne sera pas modi e par lappel de distance. Il est conseill e de qualier const toute fonction qui peu l etre : comme lexemple pr ec edent le montre, cela elargit son champ dapplication. 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, ` a part cela, le m eme en-t ete. La fonction non constante sera appel ee sur les objets non constants, la fonction constante sur les objets constants.

19

On peut utiliser cette propri et e pour ecrire des fonctions qui neectuent pas le m eme traitement ou qui ne rendent pas le m eme type de r esultat lorsquelles sont appel ees sur un objet constant et lorsquelles sont appel ees sur un objet non constant. Exemple (se rappeler que le r esultat rendu par une fonction ne fait pas 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 d eclaration pr ec edente, les fonctions X et Y sont s ecuris ees : sur un objet constant elles ne permettent 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 r ef erence)

2.8

Membres statiques

Chaque objet dune classe poss` ede son propre exemplaire de chaque membre ordinaire (bient ot nous dirons membre non statique) de la classe : pour les donn ees membres, cela signie que de la m emoire nouvelle est allou ee lors de la cr eation de chaque objet ; pour les fonctions membres, cela veut dire quelles ne peuvent etre appel ees quen association avec un objet (on nappelle pas  la fonction f  mais  la fonction f sur lobjet x ). A loppos e de cela, les membres statiques, signal es par la qualication static pr ec edant leur d eclaration, sont partag es par tous les objets de la classe. De chacun il nexiste quun seul exemplaire par classe, quel que soit le nombre dobjets de la classe. Les donn ees et fonctions membres non statiques sont donc ce que dans dautres langages orient es objets on appelle variables dinstance et m ethodes dinstance, tandis que les donn ees et fonctions statiques sont appel ees dans ces langages variables de classe et m ethodes de classe. La visibilit e et les droits dacc` es des membres statiques sont r egis par les m emes r` egles que les membres ordinaires. 2.8.1 Donn ees membres statiques class Point { int x, y; public: static int nombreDePoints; Point(int a, int b) { x = a; y = b; nbrPoints++; } }; Chaque objet Point poss` ede ses propres exemplaires des membres x et y mais, quel que soit le nombre de points existants ` a un moment donn e, il existe un seul exemplaire du membre nombreDePoints. Initialisation. La ligne mentionnant nombreDePoints dans la classe Point est une simple  annonce , comme une d eclaration extern du langage C. Il faut encore cr eer et initialiser cette donn ee membre (ce qui, pour une donn ee membre non statique, est fait par le constructeur lors de la cr eation de chaque objet). Cela

20

se fait par une formule analogue ` a une d enition de variable, ecrite dans la port ee globale, m eme sil sagit de membres priv es : int Point::nombreDePoints = 0; (la ligne ci-dessus doit etre ecrite dans un chier  .cpp , non dans un chier  .h ) Lacc` es ` a un membre statique depuis une fonction membre de la m eme classe s ecrit comme lacc` es ` a un membre ordinaire (voyez lacc` es ` a nombreDePoints fait dans le constructeur Point ci-dessus). Lacc` es ` a un membre statique depuis une fonction non membre peut se faire ` a travers un objet, nimporte lequel, de la classe : Point a, b, c; ... cout << a.nombreDePoints << "\n"; Mais, puisquil y a un seul exemplaire de chaque membre statique, lacc` es peut s ecrire aussi ind ependamment de tout objet, par une expression qui met bien en evidence laspect  variable de classe  des donn ees membres statiques : cout << Point::nombreDePoints << "\n"; 2.8.2 Fonctions membres statiques

Une fonction membre statique nest pas attach ee ` a un objet. Par cons equent : elle ne dispose pas du pointeur this, de sa classe, elle ne peut r ef erencer que les fonctions et les membres statiques. Par exemple, voici la classe Point pr ec edente, dans laquelle le membre nombreDePoints a et e rendu priv e pour en emp echer toute modication intempestive. Il faut donc fournir une fonction pour en consulter la valeur, nous lavons appel ee combien : 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 ecrire une expression comme (a etant de 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 etre membre de cette classe, a le droit dacc eder a tous ses membres, aussi bien publics que priv ` es. Une fonction amie doit etre d eclar ee ou d enie dans la classe qui accorde le droit dacc` es, pr ec ed ee du mot r eserv e friend. Cette d eclaration doit etre ecrite indi eremment parmi les membres publics ou parmi les membres priv es :

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 d eclar ee ` a lint erieur de la classe Tableau, la fonction afficher nest pas membre de cette classe ; en particulier, elle nest pas attach ee ` a un objet, et le pointeur this ny est pas d eni. Les exemples pr ec edents ne montrent pas lutilit e des fonctions amies, ce nest pas une chose evidente. En eet, dans la plupart des cas, une fonction amie peut avantageusement etre remplac ee par une fonction membre : class Tableau { int *tab, nbr; public: void afficher() const; //maintenant cest une fonction membre ... }; avec la d enition : void Tableau::afficher() { cout << [; for (int i = 0; i < nbr; i++) cout << << tab[i]; cout << ] ; } Il y a cependant des cas de gure o` u une fonction doit etre n ecessairement ecrite comme une amie dune classe et non comme un membre ; un de ces cas est celui o` u la fonction doit, pour des raisons diverses, etre membre dune autre classe. Imaginons, par exemple, que la fonction afficher doive ecrire les el ements dun objet Tableau dans un certain objet Fen^ etre : 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 priv es de ces deux classes, et son ecriture sen trouve facilit ee : 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 dacc eder ` a tous les membres de C. Une telle classe doit etre d eclar ee dans la classe C (la classe qui accorde le droit dacc` es), pr ec ed ee du mot r eserv e friend, indi eremment parmi les membres priv es ou parmi les membres publics de C. Exemple : les deux classes Maillon et Pile suivantes impl ementent la structure de donn ees pile (structure  dernier entr e premier sorti ) dentiers : 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 e de la classe Maillon ci-dessus : tous ses membres sont priv es, et la classe Pile est son amie (on dit que Maillon est une classe  esclave  de la classe Pile). Autrement dit, seules les piles ont le droit de cr eer et de manipuler des maillons ; le reste du syst` eme nutilise que les piles et leurs op erations publiques, et na m eme pas ` a conna tre lexistence des maillons. 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 e nest pas transitive,  les amis de mes amis ne sont pas mes amis . A l evidence, la notion damiti e est une entorse aux r` egles qui r egissent les droits dacc` es ; elle doit etre employ ee avec une grande mod eration, et uniquement pour permettre l ecriture de composants intimement associ es dun programme, comme les classes Maillon et Pile de notre exemple.

23

3
3.1

Surcharge des op erateurs


Principe

En C++ on peut red enir la s emantique des op erateurs du langage, soit pour les etendre ` a des objets, alors qui n etaient initialement d enis que sur des types primitifs, soit pour changer leet dop erateurs pr ed enis sur des objets. Cela sappelle surcharger des op erateurs. Il nest pas possible dinventer de nouveaux op erateurs ; seuls des op erateurs d ej` a connus du compilateur peuvent etre surcharg es. Tous les op erateurs de C++ peuvent etre surcharg es, sauf les cinq suivants : . .* :: ? : sizeof Il nest pas possible de surcharger un op erateur appliqu e uniquement ` a des donn ees de type standard : un op erande au moins doit etre dun type classe. Une fois surcharg es, les op erateurs gardent leur pluralit e, leur priorit e et leur associativit e initiales. En revanche, ils perdent leur eventuelle commutativit e et les eventuels liens s emantiques avec dautres op erateurs. Par exemple, la s emantique dune surcharge de ++ ou <= na pas ` a etre li ee avec celle de + ou <. Surcharger un op erateur revient ` a d enir une fonction ; tout ce qui a et e dit ` a propos de la surcharge des fonctions (cf. section 1.6) sapplique donc ` a la surcharge des op erateurs. Plus pr ecis ement, pour surcharger un op erateur (ce signe repr esente un op erateur quelconque) il faut d enir une fonction nomm ee operator. Ce peut etre une fonction membre dune classe ou bien une fonction ind ependante. Si elle nest pas membre dune classe, alors elle doit avoir au moins un param` etre dun type classe. 3.1.1 Surcharge dun op erateur par une fonction membre

Si la fonction operator est membre dune classe, elle doit comporter un param` etre de moins que la pluralit e de lop erateur : le premier op erande sera lobjet ` a travers lequel la fonction a et e appel ee. Ainsi, sauf quelques exceptions :  obj  ou  obj  equivalent ` a  obj .operator()   obj1 obj2  equivaut ` a  obj1 .operator(obj2 )  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 op erateur par une fonction non membre

Si la fonction operator nest pas membre dune classe, alors elle doit avoir un nombre de param` etres egal a la pluralit ` e de lop erateur. Dans ce cas : equivalent ` a  operator(obj )   obj  ou  obj   obj1 obj2  equivaut ` a  operator(obj1 , obj2 )  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 op erateur binaire sym etrique par une fonction non membre, comme la pr ec edente, est en g en eral pr ef erable, car les deux op erandes y sont trait es sym etriquement. Exemple : Point p, q, r; int x, y; Surcharge de lop erateur + par une fonction membre : 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 lop erateur + par une fonction non membre :

Note 2. Lorsque la surcharge dun op erateur est une fonction non membre, on a souvent int er et, ou n ecessit e, ` en faire une fonction amie. Par exemple, si la classe Point navait pas poss a ed e les  accesseurs  publics X() et Y(), on aurait d u surcharger laddition par une fonction amie : 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 montr ees ci-dessus, par une fonction membre et par une fonction non membre, ne peuvent pas etre d enies en m eme temps dans un m eme programme ; si tel etait le cas, une expression comme p + q serait trouv ee ambigu e par le compilateur. On notera que cela est une particularit e de la surcharge des op erateurs, un tel probl` eme ne se pose pas pour les fonctions ordinaires (une fonction membre nest jamais en comp etition avec une fonction non membre). Note 4. La surcharge dun op erateur binaire par une fonction non membre est carr ement obligatoire lorsque le premier op erande est dun type standard ou dun type classe d eni par ailleurs, que le programmeur ne peut plus etendre (pour un exemple, voyez la section 3.2.1.

3.2
3.2.1

Quelques exemples
Injection et extraction de donn ees dans les ux

Lop erateur <<13 peut etre utilis e pour  injecter  des donn ees dans un ux de sortie, ou ostream (cf. section 1.8). Dune part, lauteur de la classe ostream a d eni des surcharges de <<, probablement par des fonctions membres, pour les types connus lors du d eveloppement de la biblioth` eque standard :
13 Appliqu e` a des donn ees de types primitifs, lop erateur << exprime, en C++ comme en C, lop eration de d ecalage de bits vers 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 etendre << aux objets dune classe quil est en train de d evelopper ne peut plus ajouter des membres ` a la classe ostream. Il doit donc ecrire une fonction non membre : ostream& operator<<(ostream& o, const Point p) { return o << ( << p.X() << , << p.Y() << ); } Parfois (ce nest pas le cas ici) l ecriture de lop erateur non membre est plus simple si on en fait une fonction amie des classes des op erandes : 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 s ecrivent sur un ux de sortie comme les donn ees primitives : Point p; ... cout << "le point trouv e est : " << p << "\n"; On peut de mani` ere analogue surcharger lop erateur >> an dobtenir une moyen simple pour lire des points. Par exemple, si on impose que les points soient donn es sous la forme (x, y ), cest-` a-dire par deux nombres s epar es par une virgule et encadr es par des parenth` eses : 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 e\n"; exit(-1); }

26

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

Laectation entre objets est une op eration pr ed enie qui peut etre surcharg ee. Si le programmeur ne le fait pas, tout se passe comme si le compilateur avait synth etis e une op eration daectation consistant en la recopie membre ` a membre des objets. Sil sagit dune classe sans objets membres, sans classe de base et sans fonction virtuelle, cela donne lop erateur daectation trivial, consistant en la copie bit ` a bit dune portion de m emoire sur une autre, comme pour la copie des structures du langage C. Les raisons qui poussent ` a surcharger lop erateur daectation pour une classe sont les m emes que celles qui poussent ` a ecrire un constructeur par copie (cf. section 2.4.4). Tr` es souvent, cest que la classe poss` ede des  bouts dehors , cest-` a-dire des membres de type pointeur, et quon ne peut pas se contenter dune copie supercielle. Lop erateur daectation doit etre surcharg e par une fonction membre (en eet, dans une aectation on ne souhaite pas que les op erandes jouent des r oles sym etriques). Reprenons les points munis d etiquettes qui nous ont d ej` a servi dexemple : 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 d enie comme ci-dessus, un programme aussi  inoensif  que le suivant est erron e (et explosif) : void main() { PointNomme p(0, 0, "Origine"), q; q = p; }

27

En eet, laectation nayant pas et e surcharg ee, linstruction  q = p ;  ne fait quune recopie bit ` a bit de lobjet p dans lobjet q, cest ` a dire une copie supercielle (voyez la gure 2 ` a la page 17). A la n du programme, les objets p et q sont d etruits, lun apr` es lautre. Or, la destruction dun de ces objets lib` ere la cha ne label et rend lautre objet incoh erent, ce qui provoque une erreur fatale lors de la restitution du second objet. Voici la surcharge de lop erateur daectation qui r esout ce probl` eme. Comme il fallait sy attendre, cela se r esume ` a une destruction de la valeur courante (sauf dans le cas vicieux o` u on essaierait daecter un objet par lui m eme) suivie dune copie : 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

Op erateurs de conversion
Conversion vers un type classe

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

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

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

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

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 etant une classe et T un type, primitif ou non, on d enit la conversion de C vers T par une fonction 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

H eritage
Classes de base et classes d eriv ees

Le m ecanisme de lh eritage consiste en la d enition dune classe par r eunion des membres dune ou plusieurs classes pr eexistantes, dites classes de base directes, et dun ensemble de membres sp eciques de la classe nouvelle, appel ee alors classe d eriv ee. La syntaxe est : class classe : d erivation classe , d erivation classe , ... d erivation classe { d eclarations et d enitions des membres sp eciques de la nouvelle classe } o` u d erivation est un des mots-cl es private, protected ou public (cf. section 4.2.2). Par exemple, voici une classe Tableau (tableau  am elior e , en ce sens que la valeur de lindice est contr ol ee lors de chaque acc` es) et une classe Pile qui ajoute ` a la classe Tableau une donn ee membre exprimant le niveau 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) { contr ole de la valeur de i 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 donn ee une classe C, une classe de base de C est soit une classe de base directe de C, soit une classe de base directe dune classe de base de C. Lh eritage est appel e simple sil y a une seule classe de base directe, il est dit multiple sinon. En C++, lh eritage peut etre multiple. Encombrement. Dans la notion dh eritage il y a celle de r eunion de membres. Ainsi, du point de vue de loccupation de la m emoire, chaque objet de la classe d eriv ee contient un objet de la classe de base :

tab maxTab

tab maxTab niveau

Fig. 4 Un objet Tableau et un objet Pile Pour parler de lensemble des membres h erit es (par exemple, tab et maxTab) dune classe de base B qui se trouvent dans une classe d eriv ee D on dit souvent le sous-objet B de lobjet D. . Pour la visibilit Visibilite e des membres (qui nest pas laccessibilit e, expliqu ee dans les sections suivantes), il faut savoir que la classe d eriv ee d etermine une port ee imbriqu ee dans la port ee de la classe de base. Ainsi, les noms des membres de la classe d eriv ee masquent les noms des membres de la classe de base. 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-` a-dire les fonctions Tableau::taille() et Pile::taille()) ne sont pas en comp etition pour la surcharge, car la deuxi` eme rend tout simplement la premi` ere invisible. Lop erateur de r esolution de port ee permet de rem edier ` a ce masquage (sous r eserve quon ait le droit dacc` es au membre taille de la classe Tableau) : unePile.Tableau::taille() // la valeur de unePile.nbr

4.2
4.2.1

H eritage et accessibilit e des membres


Membres prot eg es

En plus des membres publics et priv es, une classe C peut avoir des membres prot eg es. Annonc es par le mot cl e protected, ils repr esentent une accessibilit e interm ediaire car ils sont accessibles par les fonctions membres et amies de C et aussi par les fonctions membres et amies des classes directement d eriv ees de C. Les membres prot eg es sont donc des membres qui ne font pas partie de linterface de la classe, mais dont on a jug e que le droit dacc` es serait n ecessaire ou utile aux concepteurs des classes d eriv ees.

30

Imaginons, par exemple, quon veuille comptabiliser le nombre dacc` es faits aux objets de nos classes Tableau et Pile. Il faudra leur ajouter un compteur, qui naura pas ` a etre public (ces d ecomptes ne regardent pas les utilisateurs de ces classes) mais qui devra etre accessible aux membres de la classe Pile, si on veut que les acc` es aux piles qui ne mettent en uvre aucun membre des tableaux, comme dans vide(), soient bien compt es. class Tableau { int *tab; int maxTab; protected: int nbrAcces; public: Tableau(int t) { nbrAcces = 0; tab = new int[maxTab = t]; } int &operator[](int i) { contr ole de la valeur de i 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 H eritage priv e, prot eg e, public

En choisissant quel mot-cl e indique la d erivation, parmi private, protected ou public, le programmeur d etermine laccessibilit e dans la classe d eriv ee des membres de la classe de base. On notera que : cela concerne laccessibilit e des membres, non leur pr esence (les objets de la classe d eriv ee contiennent toujours tous les membres de toutes les classes de base), la d erivation ne peut jamais servir ` a augmenter laccessibilit e dun membre. Cons equence de ces deux points, les objets des classes d eriv ees ont g en eralement des membres inaccessibles : les membres priv es de la classe de base sont pr esents dans les objets de la classe d eriv ee, mais il ny a aucun moyen dy faire r ef erence. ritage prive . Syntaxe : He class classeD eriv ee : privateoptionnel classeDeBase { ... } Le mot cl e private est optionnel car lh eritage priv e est lh eritage par d efaut. Cest la forme la plus restrictive dh eritage : voyez la gure 5. Dans lh eritage priv e, linterface de la classe de base dispara t (lensemble des membres publics cessent d etre publics). Autrement dit, on utilise la classe de base pour r ealiser limpl ementation de la classe d eriv ee, mais on soblige ` a ecrire une nouvelle interface pour la classe d eriv ee. Cela se voit dans lexemple d ej` a donn e des classes Tableau et Pile (cf. ). Il sagit dh eritage priv e, car les tableaux ne fournissent que limpl ementation des piles, non leur comportement.

31

publics membres de la classe de base protgs privs inaccessibles


Fig. 5 H eritage priv e

statut dans la classe inaccessibles drive privs

Exemple demploi dun tableau : Tableau t(taille max souhait ee ); ... t[i] = x; etc. Emploi dune pile : Pile p(taille max souhait ee ); p[i] = x; // ERREUR : lop erateur [] est inaccessible p.empiler(x); // Oui ... cout << t[i]; // ERREUR : lop erateur [] est inaccessible cout << p.depiler(); // Oui etc. A retenir : si D d erive de mani` ere priv ee de B alors un objet D est une sorte de B, mais les utilisateurs de ces classes nont pas ` a le savoir. Ou encore : ce quon peut demander ` a un B, on ne peut pas forc ement le demander ` a un D. ritage prote ge . Syntaxe : He class classeD eriv ee : protected classeDeBase { ... } Cette forme dh eritage, moins souvent utilis ee que les deux autres, est similaire ` a lh eritage priv e (la classe de base fournit limpl ementation de la classe d eriv ee, non son interface) mais on consid` ere ici que les d etails de limpl ementation, c.-` a-d. les membres publics et prot eg es de la classe de base, doivent rester accessibles aux concepteurs d eventuelles classes d eriv ees de la classe d eriv ee.

publics membres de la classe de base protgs privs inaccessibles


Fig. 6 H eritage prot eg e ritage public. Syntaxe : He class classeD eriv ee : public classeDeBase { ... } Dans lh eritage public (voyez la gure 7) linterface est conserv e : tous les el ements du comportement public de la classe de base font partie du comportement de la classe d eriv ee. Cest la forme dh eritage la plus fr equemment utilis ee, car la plus utile et la plus facile ` a comprendre : dire que D d erive publiquement de B cest dire que, vu de lext erieur, tout D est une sorte de B, ou encore que tout ce quon peut demander ` a un B on peut aussi le demander ` a un D. La plupart des exemples dh eritage que nous examinerons seront des cas de d erivation publique.

statut dans la classe inaccessibles drive protgs

4.3

Red enition des fonctions membres

Lorsquun membre sp ecique dune classe d eriv ee a le m eme nom quun membre dune classe de base, le premier masque le second, et cela quels que soient les r oles syntaxiques (constante, type, variable, fonction, etc.) de ces membres. 32

publics membres de la classe de base protgs privs inaccessibles


Fig. 7 H eritage public

publics protgs inaccessibles statut dans la classe drive

La signication de ce masquage d epend de la situation : il peut sagir des noms de deux fonctions membres de m eme signature, erentes, ou de membres dont lun nest pas une fonction. il peut sagir de fonctions de signatures di Sil ne sagit pas de deux fonctions de m eme signature, on a une situation maladroite, g en eratrice de confusion, dont on ne retire en g en eral aucun b en ece. En particulier, le masquage dune donn ee membre par une autre donn ee membre ne fait pas economiser la m emoire, puisque les deux membres existent dans chaque objet de la classe d eriv ee. En revanche, le masquage dune fonction membre de la classe de base par une fonction membre de m eme signature est une d emarche utile et justi ee. On appelle cela une red enition de la fonction membre. La justication est la suivante : si la classe D d erive publiquement de la classe B, tout D peut etre vu comme une sorte de B, cest-` a-dire un B  am elior e  (augment e des membres dautres classes de base, ou de membres sp eciques) ; il est donc naturel quun D r eponde aux requ etes quon peut soumettre ` a un B, et quil y r eponde de fa con am elior ee. Do` u lint er et de la red enition : la classe d eriv ee donne des versions analogues mais enrichies des fonctions de la classe de base. Souvent, cet enrichissement concerne le fait que les fonctions red enies acc` edent ` a ce que la classe d eriv ee a de plus que la classe de base. Exemple : un Pixel est un point am elior e (c.-` a-d. augment e dun membre suppl ementaire, sa couleur). Lachage dun pixel consiste ` a lacher en tant que point, avec des informations additionnelles. Le code suivant re` ete tout cela : 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

Cr eation et destruction des objets d eriv es

Lors de la construction dun objet, ses sous-objets h erit es sont initialis es selon les constructeurs des classes de base14 correspondantes. Sil ny a pas dautre indication, il sagit des constructeurs par d efaut. Si des arguments sont requis, il faut les signaler dans le constructeur de lobjet selon une syntaxe voisine de celle qui sert ` a linitialisation des objets membres (cf. section 2.5) : classe (param` etres ) : classeDeBase (param` etres ), ... classeDeBase (param` etres ) { corps du constructeur } De la m eme mani` ere, lors de la destruction dun objet, les destructeurs des classes de base directes sont toujours appel es. Il ny a rien ` a ecrire, cet appel est toujours implicite. Exemple : 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

R ecapitulation sur la cr eation et destruction des objets

Ayant examin e le cas de lh eritage nous pouvons donner ici, sous une forme r esum ee, la totalit e de ce quil ya` a dire ` a propos de la construction et le destruction des objets dans le cas le plus g en eral. 4.5.1 Construction

1. Sauf pour les objets qui sont les valeurs de variables globales, le constructeur dun objet est appel e imm ediatement apr` es lobtention de lespace pour lobjet. On peut consid erer que lespace pour les variables globales existe d` es que le programme est charg e. Les constructeurs de ces variables sont appel es, dans lordre des d eclarations de ces variables, juste avant 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 d eclaration de ces classes de base directes. 3. Sont appel es ensuite les constructeurs de chacun des objets membres, dans lordre de d eclaration de ces objets membres. ecut ees. 4. Enn, les instructions qui composent le corps du constructeur sont ex
sagit ici, tout dabord, des classes de base directes ; mais les constructeurs de ces classes de base directes appelleront ` a leur tour les constructeurs de leurs classes de base directes, et ainsi de suite. En d enitive, les constructeurs de toutes les classes de base auront et e appel es.
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 donn ees membres. Si des arguments doivent etre pr ecis es (par exemple parce que certaines de ces classes nont pas de constructeur sans argument) cela se fait selon la syntaxe : classe (parametres ) : nomDeMembreOuDeClasseDeBase (param` etres ), ... nomDeMembreOuDeClasseDeBase (param` etres ) { corps du constructeur } Attention. Notez bien que, lorsque les constructeurs des classes de base et des objets membres sont explicitement appel es dans le constructeur de la classe d eriv ee, ces appels ne sont pas eectu es dans lordre o` u le programmeur les a ecrits : comme il a et e dit aux points 2 et 3 ci-dessus, les appels de ces constructeurs sont toujours faits dans lordre des d eclarations des classes de base et des objets membres. 4.5.2 Destruction

Grosso modo, le principe g en eral est celui-ci : les objets  contemporains  (attach es ` a un m eme contexte, cr ees par une m eme d eclaration, etc.) sont d etruits dans lordre inverse de leur cr eation. Par cons equent 1. Lex ecution du destructeur dun objet commence par lex ecution des instructions qui en composent le corps. 2. Elle continue par les appels des destructeurs des classes des objets membres, dans lordre inverse de leurs d eclarations. 3. Sont appel es ensuite les destructeurs des classes de base directes, dans lordre inverse des d eclarations de ces classes de base. 4. Enn, lespace occup e par lobjet est lib er e. truits ? Les objets qui sont les valeurs de variables globales sont A quel moment les objets sont-ils de d etruits imm ediatement apr` es la terminaison de la fonction principale (en principe main), dans lordre inverse de la d eclaration des variables globales. Les objets qui ont et e allou es dynamiquement ne sont d etruits que lors dun appel de delete les concernant. Les objets qui sont des valeurs de variables automatiques (locales) sont d etruits lorsque le contr ole quitte le bloc auquel ces variables sont rattach ees ou, au plus tard, lorsque la fonction contenant leur d enition se termine. Objets temporaires. Les objets temporaires ont les vies les plus courtes possibles. En particulier, les objets temporaires cr e es lors de l evaluation dune expression sont d etruits avant lex ecution de linstruction suivant celle qui contient lexpression. Les objets temporaires cr e es pour initialiser une r ef erence persistent jusqu` a la destruction de cette derni` ere. Exemple : { Point &r = Point(1, 2); // lobjet Point temporaire cr e e ici vit autant que r, // cest-` a-dire au moins jusqu` a la n du bloc

... } Attention, les pointeurs ne sont pas trait es avec autant de soin. Il ne faut jamais initialiser un pointeur avec ladresse dun objet temporaire (lop erateur new est fait pour cela) : { 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 coordonn ees (3,4) a d ej` a et e d etruit

4.6
4.6.1

Polymorphisme
Conversion standard vers une classe de base

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

par un B sont oerts par un D. Par cons equent, l` a o` u un B est pr evu, on doit pouvoir mettre un D. Cest la raison pour laquelle la conversion (explicite ou implicite) dun objet de type D vers le type B, une classe de base accessible de D, est d enie, et a 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 et e mis l` a o` u un point etait // attendu : il y a conversion implicite // point  g eom etrique 

// pixel = point color e

De mani` ere interne, la conversion dun D vers un B est trait ee comme lappel dune fonction membre de D qui serait publique, prot eg ee ou priv ee selon le mode (ici : public) dont D d erive de B. Ainsi, la conversion dune classe d eriv ee vers une classe de base priv ee ou prot eg ee existe mais nest pas utilisable ailleurs que depuis lint erieur de la classe d eriv ee. Bien entendu, le cas le plus int eressant est celui de la d erivation publique. Sauf mention contraire, dans la suite de cette section nous supposerons etre dans ce cas. Selon quelle sapplique ` a des objets ou ` a des pointeurs (ou des r ef erences) sur des objets, la conversion dun objet de la classe d eriv ee vers la classe de base recouvre deux r ealit es tr` es di erentes : 1. Convertir un objet D vers le type B cest lui enlever tous les membres qui ne font pas partie de B (les membres sp eciques et ceux h erit es dautres classes). Dans cette conversion il y a perte eective dinformation : 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-` a-dire un pointeur sur D en un pointeur sur B ) ne fait perdre aucune information. Point e ` a travers une expression de type B *, lobjet nore que les services de la classe B, mais il ne cesse pas d etre un D avec tous ses membres : voyez la gure 9.

pD

(B*)pD

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

et supposons quelles sont appel ees de la mani` ere suivante : Pixel pix = Pixel(2, 3, "rouge") ... fon1(pix); fon2(&pix); Le statut de lobjet pix en tant que param` etre de ces deux fonctions est tout ` a fait di erent. Lobjet pass e ` fon1 comme argument est une version tronqu a ee de pix, alors que le pointeur pass e` a fon2 est ladresse de lobjet pix tout entier : void fon1(Point pt) { La valeur de pt est un Point ; il na pas de couleur. Il y avait peut- etre ` a lorigine un Pixel mais, ici, aucune conversion (autre que d enie par lutilisateur) ne peut faire un Pixel ` a partir de pt. } void fon2(Point *pt) { Il nest rien arriv e` a lobjet dont ladresse a servi ` a initialiser pt lors de lappel de cette fonction. Si on peut garantir que cet objet est un Pixel, lexpression suivante (plac ee sous la responsabilit e du programmeur) a un sens : ((Pixel *) pt)->couleur ... } Pour ce qui nous occupe ici, les r ef erences (pointeurs g er es de mani` ere interne par le compilateur) doivent etre consid er ees comme des pointeurs. Nous pouvons donc ajouter un troisi` eme cas : void fon3(Point &rf) { Il nest rien arriv e` a lobjet qui a servi ` a initialiser rf lors de lappel de cette fonction. Si on peut garantir que cet objet est un Pixel, lexpression suivante (plac ee sous la responsabilit e du programmeur) a un sens : ((Pixel &) rf).couleur ... } 4.6.2 Type statique, type dynamique, g en eralisation

Consid erons la situation suivante : class B { ... }; class D : public B { ... }; D unD; ... B unB = unD; B *ptrB = &unD; B &refB = unD; Les trois aectations ci-dessus sont l egitimes ; elles font jouer la conversion standard dune classe vers une classe de base. Le type de unB ne soul` eve aucune question (cest un B), mais les choses sont moins claires pour 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 et e d eclar ees ; 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 d ecoule de lanalyse du texte du programme ; il est connu ` a la compilation. Le type dynamique, au contraire, est d etermin e par la valeur courante de lexpression, il peut changer durant lex ecution du programme. La notion de type dynamique va nous amener assez loin. Pour commencer, remarquons ceci : gr ace aux

37

conversions implicites de pointeurs et r ef erences vers des pointeurs et r ef erences sur des classes de base, tout objet peut etre momentan ement tenu pour plus g en eral quil nest. Cela permet les traitements g en eriques, comme dans lexemple suivant, qui pr esente un ensemble de classes con cues pour g erer le stock dun supermarch e15 : struct Article { char *denom; int quant; double prix; Article(char *d, int q, double p) : denom(d), quant(q), prix(p) { } ... };

// denomination // quantit e disponible // 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 donn ees (des tableaux de pointeurs) pour m emoriser les diverses classes darticles pr esents dans le stock : Perissable *aliment[maxAliment]; Habillement *vetemts[maxVetemts]; Informatique *info[maxInfo]; etc. D enissons egalement une structure pour m emoriser tous les articles du stock : Article *stock[maxStock]; Initialisons tout cela (notez que chaque adresse fournie par un appel de new est dabord copi ee dans un tableau sp ecique et ensuite g en eralis ee et copi ee dans le tableau g en erique) : 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 qu el ements du tableau stock, les objets des classes Perissable, Habillement, etc., sont tenus pour plus g en eraux quils ne sont, ce qui est tr` es commode pour faire sur chacun une op eration g en erique. Par exemple, le calcul de la valeur totale du stock na pas besoin des sp ecicit es de chaque article :
15 Nous d eclarons ici des structures (une structure est une classe dont les membres sont par d efaut publiques) pour ne pas nous encombrer avec des probl` emes de droits dacc` es.

38

double valeurStock = 0; for (int i = 0; i < nStock; i++) valeurStock += stock[i]->quant * stock[i]->prix; Dans cet exemple nous avons utilis e la g en eralisation des pointeurs pour acc eder ` a des membres dont les objets h eritent sans les modier : les notions de quantit e disponible et de prix unitaire sont les m emes pour toutes les sortes darticles du stock. La question devient beaucoup plus int eressante lorsquon acc` ede ` a des membres de la classe de base qui sont red enis dans les classes d eriv ees. Voici un autre exemple, classique : la classe Figure est destin ee ` a repr esenter les el ements communs ` a tout un ensemble de gures g eom etriques, telles que des triangles, des ellipses, des 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 n ecessairement etre d eclar ee dans la classe Figure, sinon la ligne ci-dessus serait trouv ee incorrecte ` a la compilation) est red enie dans les classes Triangle, Ellipse, etc., il est int eressant de se demander quelle est la fonction qui sera eectivement appel ee par lexpression image[i]->seDessiner() ecrite ci-dessus. Par d efaut, la d etermination du membre acc ed e` a travers une expression comme la pr ec edente se fait durant la compilation, cest-` a-dire dapr` es le type statique du pointeur ou de la r ef erence. Ainsi, dans lexpression image[i]->seDessiner(); cest la fonction seDessiner de la classe Figure (probablement une fonction bouche-trou) qui est appel ee. Cette r ealit e d ecevante ob eit ` a des consid erations decacit e et de compatibilit e avec le langage C ; elle semble limiter consid erablement lint er et de la g en eralisation des pointeurs... ...heureusement, la section suivante pr esente un comportement beaucoup plus int eressant !

4.7

Fonctions virtuelles

Soit f une fonction membre dune classe C. Si les conditions suivantes sont r eunies : enie dans des classes d eriv ees (directement ou indirectement) de C, f est red f est souvent appel ee ` a travers des pointeurs ou des r ef erences sur des objets de C ou de classes d eriv ees de C, alors f m erite d etre une fonction virtuelle. On exprime cela en faisant pr ec eder sa d eclaration du mot r eserv e virtual. Limportant service obtenu est celui-ci : si f est appel ee ` a travers un pointeur ou une r ef erence sur un objet de C, le choix de la fonction eectivement activ ee, parmi les diverses red enitions de f, se fera dapr` es le type dynamique de cet objet. Une classe poss edant des fonctions virtuelles est dite classe polymorphe. La qualication virtual devant la red enition dune fonction virtuelle est facultative : les red enitions dune fonction virtuelle sont virtuelles doce. Exemple :

39

class Figure { public: virtual void seDessiner() { fonction passe-partout, probablement sans grand int er et } ... }; class Triangle : public Figure { public: void seDessiner() { impl ementation du trac e dun triangle } ... }; class Ellipse : public Figure { public: void seDessiner() { impl ementation du trac e dune ellipse ... }; 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 red enie dans la classe de lobjet eectivement point e par image[i]. Cela constitue, bien entendu, le comportement int eressant, celui quon recherche presque toujours. On notera que cet appel est l egal parce que la fonction seDessiner a et e d eclar ee une premi` ere fois dans la classe qui correspond au type statique de image[i]. En r` egle g en erale, les diverses red enitions dune fonction peuvent etre vues comme des versions de plus en plus sp ecialis ees dun traitement sp eci e de mani` ere g en erique dans la classe de base C. Dans ces conditions, appeler une fonction virtuelle f ` a travers un pointeur p sur un objet de C cest demander lex ecution de la version de f la plus sp ecialis ee qui sapplique ` a ce que p pointe en fait ` a linstant o` u lappel est ex ecut e. Contraintes En concurrence avec le m ecanisme du masquage, la red enition des fonctions virtuelles subit des contraintes fortes, sur les types des param` etres et le type du r esultat : 1. Tout dabord, la signature (liste des types des arguments, sans le type du r esultat) de la red enition doit etre strictement la m eme que celle de la premi` ere d enition, sinon la deuxi` eme d enition nest pas une red enition mais un masquage pur et simple de la premi` ere. 2. La contrainte sur le r esultat garantit que lappel dune fonction virtuelle est correct et sans perte dinformation quoi quil arrive. Supposons que f soit une fonction virtuelle dune classe C rendant un r esultat de type T1 , et quune red enition de f dans une classe d eriv ee de C rende un r esultat de type T2 . Durant la compilation, une expression telle que (p est de type C *) : p->f() est vue comme de type T1 ; le compilateur v eriera que ce type correspond ` a ce que le contexte de cette expression requiert. Or, ` a lex ecution, cette expression peut renvoyer un objet de type T2 . Pour que cela soit coh erent et ecace, il faut que le type T2 puisse etre converti dans le type T1 , et cela sans avoir ` a modier (tronquer) la valeur renvoy ee. Il faut donc : soit T1 = T2 , ef erence] sur un objet dune classe C1 , T2 est un pointeur [resp. une soit T1 est un pointeur [resp. une r r ef erence] sur un objet dune classe C2 et C1 est une classe de base accessible de C2 . Autrement dit, si une fonction virtuelle f rend ladresse dun T , toute red enition de f doit rendre ladresse dune sorte de T ; si f rend une r ef erence sur un T , toute red enition de f doit rendre une r ef erence sur une

40

sorte de T ; enn, si f ne rend ni un pointeur ni une r ef erence, toute red enition de f doit rendre la m eme chose 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 r esultat assure la correction de linitialisation de f2, quel que soit le type dynamique de f1) : Figure *f1 = new nomDuneClasseD eriv eeDeFigure (...); Figure *f2 = f1->symetrique(axe); Les deux contraintes indiqu ees sont examin ees une apr` es lautre : si la contrainte sur la signature (ici (Droite *)) est satisfaite, le compilateur d etecte un cas de red enition de fonction virtuelle et, alors seulement, il v erie la contrainte sur le r esultat (ici la contrainte est satisfaite car le r esultat est un pointeur dans les deux cas et Figure est une classe de base accessible de Triangle). Si cette derni` ere nest pas satisfaite, il annonce une erreur.

4.8

Classes abstraites

Les fonctions virtuelles sont souvent introduites dans des classes plac ees ` a des niveaux si elev es de la hi erarchie (dh eritage) quon ne peut pas leur donner une impl ementation utile ni m eme vraisemblable. Une premi` ere solution consiste ` a en faire des fonctions vides : class Figure { public: virtual void seDessiner() { } // sera d enie dans les classes d eriv ees ... }; En fait, si la fonction seDessiner peut etre vide cest parce que : la classe Figure ne doit pas avoir dobjets, la classe Figure doit avoir des classes d eriv ees (qui, elles, auront des objets), la fonction seDessiner doit etre red enie dans les classes d eriv ees. Bref, la classe Figure est une abstraction. Un programme ne cr eera pas dobjets Figure, mais des objets Rectangle, Ellipse, etc., de classes d eriv ees de Figure. Le r ole de la classe Figure nest pas de produire des objets, mais de repr esenter ce que les objets rectangles, ellipses, etc., ont en commun ; or, sil y a une chose que ces objets ont en commun, cest bien la facult e de se dessiner. Nous dirons que Figure est une classe abstraite. On remarque que la fonction seDessiner introduite au niveau de la classe Figure nest pas un service rendu aux programmeurs des futures classes d eriv ees de Figure, mais une contrainte : son r ole nest pas de dire ce quest le dessin dune gure, mais dobliger les futures classes d eriv ees de Figure ` a le dire. On peut traduire cet aspect de la chose en faisant que cette fonction d eclenche une erreur. En eet, si elle est appel ee, cest que ou bien on a cr e e un objet de la classe Figure, ou bien on a cr e e un objet dune classe d eriv ee de Figure dans laquelle on na pas red eni la fonction seDessiner :

41

class Figure { public: virtual void seDessiner() { erreur("on a oubli e de d efinir la fonction seDessiner"); } ... }; Cette mani` ere de faire est peu pratique, car lerreur ne sera d eclench ee qu` a lex ecution (cest le programmeur qui a commis loubli mais cest lutilisateur qui se fera r eprimander !). En C++ on a une meilleure solution : d enir une fonction virtuelle pure, par la syntaxe : class Figure { public: virtual void seDessiner() = 0; ... };

// Une fonction virtuelle pure

Dune part, cela  ocialise  le fait que la fonction ne peut pas, ` a ce niveau, poss eder une impl ementation. Dautre part, cela cr ee, pour le programmeur, lobligation de d enir cette impl ementation dans une classe d eriv ee ult erieure, et permet de v erier pendant la compilation que cela a et e fait. Une fonction virtuelle pure reste virtuelle pure dans les classes d eriv ees, aussi longtemps quelle ne fait pas lobjet dune red enition (autre que  = 0 ). Pour le compilateur, une classe abstraite est une classe qui a des fonctions virtuelles pures. Tenter de cr eer 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 : Cr eation dune instance de la classe abstraite Figure Oui : cest un pointeur ERREUR : Cr eation dune instance de la classe abstraite Figure Oui : la classe Rectangle nest pas abstrait e Oui

4.9

Identication dynamique du type

Le co ut du polymorphisme, en espace m emoire, est dun pointeur par objet, quel que soit le nombre de 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 ut du polymorphisme est celui dune indirection suppl ementaire pour chaque appel dune fonction virtuelle, puisquil faut aller chercher dans une table ladresse eective de la fonction.

42

Do` u lid ee de proter de lexistence de ce pointeur pour ajouter au langage, sans co ut suppl ementaire, une gestion des types dynamiques quil faudrait sinon ecrire au coup par coup (probablement ` a laide de fonctions virtuelles). Fondamentalement, ce m ecanisme se compose des deux op erateurs dynamic cast et typeid. 4.9.1 Lop erateur dynamic cast

Syntaxe dynamic_cast<type >(expression ) Il sagit deectuer la conversion dune expression dun type pointeur vers un autre type pointeur, les types point es etant deux classes polymorphes dont lune, B, est une classe de base accessible de lautre, D. Sil sagit de g en eralisation (conversion dans le sens D * B *), cet op erateur ne fait rien de plus que la conversion standard. Le cas int eressant est B * D *, conversion qui na de sens que si lobjet point e par lexpression de type B * est en r ealit e un objet de la classe D ou dune classe d eriv ee de D. Lop erateur dynamic cast fait ce travail de mani` ere s ure et portable, et rend ladresse de lobjet lorsque 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 (` a la compilation) : un Animal nest pas forc ement un Mammif` ere Mammifere *p1 = dynamic_cast<Mammifere *>(ptr); // OK : p1 re coit une bonne adresse, car M edor est un mammif` ere Caniche *p2 = dynamic_cast<Caniche *>(ptr); // OK, mais p2 re coit 0, car M edor nest pas un caniche Chat *p3 = dynamic_cast<Chat *>(ptr); // OK, mais p3 re coit 0, car M edor nest pas un chat non plus Reverbere *p4 = dynamic_cast<Reverbere *>(ptr); // OK, mais p4 re coit 0, car M edor nest pas un reverb` ere Lop erateur dynamic_cast sapplique egalement aux r ef erences. Lexplication est la m eme, sauf quen cas dimpossibilit e deectuer la conversion, cet op erateur lance lexception bad cast :

// 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 lanc ee ; // non attrap ee, elle est fatale

4.9.2

Lop erateur typeid

Syntaxes : typeid(type ) ou typeid(expression ) Le r esultat est une valeur de type const type_info&, o` u type_info est une classe de la biblioth` eque 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 e de comparer les types dynamiques et den obtenir l ecriture des noms. Exemple : Animal *ptr = new Caniche; cout << typeid(ptr).name() << \n; cout << typeid(*ptr).name() << \n; cout << "Lanimal point e par ptr " << (typeid(*ptr) == typeid(Chien) ? "est" : "nest pas") << " un chien\n"; cout << "Lanimal point e par ptr est un " << typeid(*ptr).name() << "\n"; achage obtenu : Animal * Chien Lanimal point e par ptr nest pas un chien Lanimal point e par ptr est un Caniche

Mod` eles (templates )

Un mod` ele d enit une famille de fonctions ou de classes param etr ee par une liste didenticateurs qui repr esentent des valeurs et des types. Les valeurs sont indiqu ees par leurs types respectifs, les types par le mot r eserv e class. Le tout est pr ex e par le mot r eserv e template. Exemple : template<class T, class Q, int N> ici gure la d eclaration ou la d enition dune fonction ou dune classe dans laquelle T et Q apparaissent comme types et N comme constante Le mot class ne signie pas que T et Q sont n ecessairement des classes, mais des types. Depuis la norme ISO on peut egalement utiliser le mot r eserv e typename, qui est certainement un terme plus heureux. La production eective dun el ement de la famille d enie par un mod` ele sappelle linstanciation 17 du mod` ele. Lors de cette op eration, les param` etres qui repr esentent des types doivent recevoir pour valeur des types ; les autres param` etres doivent recevoir des expressions dont la valeur est connue durant la compilation.
17 Le choix du mot nest pas tr` es heureux. Dans tous les autres langages ` a objets, linstanciation est lop eration de cr eation dun objet, et on dit quun objet est instance de sa classe. En C++ le mot instanciation ne sutilise quen rapport avec les mod` eles.

44

5.1

Mod` eles de fonctions

Il ny a pas en C++ un op erateur ou un mot r eserv e express ement destin e ` a produire des instances des mod` eles. Linstanciation dun mod` ele de fonction template<param 1 , ... param k > type nom ( argfor 1 , ... argfor n )... est command ee implicitement par lappel dune des fonctions que le mod` ele d enit. Un tel appel peut etre ecrit sous la forme : nom <arg 1 , ... arg k > (arge 1 , ... arge n ) dans laquelle les derniers arguments du mod` ele, arg i ... arg k , eventuellement tous, peuvent etre omis si leurs valeurs peuvent etre d eduites sans ambigu t e des arguments eectifs de la fonction, arge i , ... arge n . Exemple : d eclaration du mod` ele  recherche de la valeur minimale dun tableau (ayant au moins un el ement)  : 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` ele : 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` ele peut se d eduire de lappel, ce programme s ecrit aussi :

Un mod` ele de fonction peut coexister avec une ou plusieurs sp ecialisations. Par exemple, la fonction suivante est une sp ecialisation du mod` ele min quon ne peut pas obtenir par instanciation de ce mod` ele, car ` a la place de lop erateur < gure un appel de la fonction strcmp : 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 m ecanisme de la surcharge des fonctions, les instances de mod` eles sont souvent en concurrence avec dautres instances ou des fonctions ordinaires. Il faut savoir que les fonctions ordinaires sont pr ef er ees aux instances de mod` eles et les instances de mod` eles plus sp ecialis es sont pr ef er ees aux instances de mod` eles moins sp ecialis es. Par exemple, donnons-nous un programme un peu plus compliqu e que le pr ec edent, en ecrivant une fonction qui recherche lindice du minimum dun tableau, avec un deuxi` eme tableau pour  crit` ere secondaire . Nous d enissons : 1. Un mod` ele avec deux param` etres-types : 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` ele qui en est une sp ecialisation partielle (le premier tableau est un tableau de cha nes, il ny a donc plus quun param` etre-type) : 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 sp ecialisation compl` ete du mod` ele (deux tableaux de cha nes, plus aucun param` etre-type) : 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` eles les plus sp ecialis es sont pr ef er es aux moins sp ecialis es.

5.2

Mod` eles de classes

Un mod` ele de classe est un type param etr e, par dautres types et par des valeurs connues lors de la compilation. Par exemple, d enissons un mod` ele pour repr esenter des tableaux dans lesquels lindice est v eri e lors de chaque acc` es. Le type des el ements de ces tables nest pas connu, cest un param` etre du mod` ele : 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) { contr ole de la validit e de i return tab[i]; }

46

TElement operator[](int i) const { contr ole de la validit e de i return tab[i]; } }; On obtient linstanciation dun mod` ele de classe en en cr eant des objets. Ici, contrairement ` a ce qui se passe pour les fonctions, les arguments du mod` ele doivent etre toujours explicit es : Table<char> message(80); Table<Point> ligne(100); Une fois d eclar es, ces tableaux sutilisent comme les tableaux du langage (avec en plus la certitude que si lindice d eborde on en sera pr evenu tout de suite) : message[0] = ; ligne[i] = Point(j, k); Tout se passe comme si la d enition de lobjet message ecrite ci-dessus produisait le texte obtenu en substituant TElement par char dans le mod` ele Table et le pr esentait au compilateur ; un peu plus tard, la d enition de ligne produit le texte obtenu en substituant TElement par Point et le pr esente egalement au compilateur. Cest un traitement apparent e au d eveloppement des macros18 mais bien plus complexe, aussi bien sur le plan formel (les arguments dun mod` ele peuvent etre des instances dautres mod` eles, les modalit es de linstanciation dun mod` ele de fonction peuvent etre automatiquement d eduites de lappel de la fonction, etc.) que sur le plan pratique (n ecessit e de ne pas instancier un mod` ele qui la d ej` a et e dans la m eme unit e de compilation, n ecessit e de reconna tre que des instances du m eme mod` ele produites et compil ees dans des unit es de compilation s epar ees sont la m eme entit e, etc.). 5.2.1 Fonctions membres dun mod` ele

Les fonctions membres dun mod` ele de classe sont des mod` eles de fonctions avec les m emes param` etres que le mod` ele de la classe. Cela est implicite pour les d eclarations et d enitions ecrites ` a lint erieur du mod` ele de classe, mais doit etre explicit e pour les d enitions ecrites ` a lext erieur. Par exemple, voici notre mod` ele de classe Table avec des fonctions membres s epar ees : 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) { contr ole de la validit e de i return tab[i]; } template<class TElement> TElement Table<TElement>::operator[](int i) const { contr ole de la validit e de i return tab[i]; }
18 Dans

les premi` eres versions de C++ les mod` eles etaient d enis comme des macros et pris en charge par le pr eprocesseur.

47

Les param` etres des mod` eles de classes peuvent avoir des valeurs par d efaut. Voici, par exemple, une autre version de nos tables, dans laquelle lallocation du tableau nest pas dynamique ; par d efaut, ce sont des tables 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` ele peut etre  annonc e , cest-` a-dire, d eclar e et non d eni. Il pourra alors etre utilis e dans toute situation ne n ecessitant pas de conna tre sa taille ou ses d etails internes : 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` eme de communication au sein des grands logiciels. En eet, le concepteur dune biblioth` eque de bas niveau peut programmer la d etection dune situation anormale produite durant lex ecution dune de ses fonctions, mais ne sait pas quelle conduite doit adopter alors la fonction de haut niveau qui la appel ee. Lauteur des fonctions de haut niveau conna t la mani` ere de r eagir aux anomalies, mais ne peut pas les d etecter. Le m ecanisme des exceptions est destin e` a permettre aux fonctions profondes dune biblioth` eque de notier la survenue dune erreur aux fonctions hautes qui utilisent la biblioth` eque. Les points-cl es de ce m ecanisme sont les suivants : la fonction qui d etecte un ev enement exceptionnel construit une exception et la  lance  (throw ) vers la fonction qui la appel ee ; ne ou, mieux, un objet dune classe sp ecialement d enie dans ce lexception est un nombre, une cha but (souvent une classe d eriv ee de la classe exception) comportant diverses informations utiles ` a la caract erisation de l ev enement ` a signaler ; ee, lexception traverse la fonction qui la lanc ee, sa fonction appelante, la fonction appelante une fois lanc de la fonction appelante, etc., jusqu` a atteindre une fonction active (c.-` a-d. une fonction commenc ee et non encore termin ee) qui a pr evu d attraper  (catch ) ce type dexception ; ee et les fonctions que lexception traverse sont lors du lancement dune exception, la fonction qui la lanc imm ediatement termin ees : les instructions qui restaient ` a ex ecuter dans chacune de ces fonctions sont abandonn ees ; malgr e son caract` ere pr ematur e, cette terminaison prend le temps de d etruire les objets locaux de chacune des fonctions ainsi avort ees ; a traverser toutes les fonctions actives, car aucune de ces fonctions na pr evu de si une exception arrive ` lattraper, alors elle produit la terminaison du programme. Une fonction indique quelle sint eresse aux exceptions qui peuvent survenir durant lex ecution dune certaine s equence dinstructions par une expression qui dune part d elimite cette s equence et qui dautre part associe un

48

traitement sp ecique aux divers types dexception que la fonction intercepte. Cela prend la forme dun  bloc try  suivi dun ensemble de gestionnaires dinterception ou  gestionnaires catch  : try { instructions susceptibles de provoquer, soit directement soit dans des fonctions appel ees, le lancement dune exception } catch(d eclarationParam` etre 1 ) { instructions pour traiter les exceptions correspondant au type de param` etre 1 } catch(d eclarationParam` etre 2 ) { instructions pour traiter les exceptions, non attrap ees par le gestionnaire pr ec edent, correspondant au type de param` etre 2 } etc. catch(...) { instructions pour traiter toutes les exceptions non attrap ees par les gestionnaires pr ec edents } Un bloc try doit etre imm ediatement suivi par au moins un gestionnaire catch. Un gestionnaire catch doit se trouver imm ediatement apr` es un bloc try ou imm ediatement apr` es un autre gestionnaire catch. Chaque gestionnaire catch comporte un en-t ete avec la d eclaration dun argument formel qui sera initialis e, a la mani` ` ere dun argument dune fonction, avec lexception ayant activ e le gestionnaire. Exemple : catch(bad_alloc e) { cerr << "ERREUR : " << e.what() << \n; } Si le corps du gestionnaire ne r ef erence pas lexception en question, len-t ete peut se limiter ` a sp ecier le type de lexception : catch(bad_alloc) { cout << "ERREUR : Allocation impossible"; } Un programme lance une exception par linstruction throw expression ; La valeur de expression est suppos ee d ecrire lexception produite. Elle est propag ee aux fonctions actives, jusqu` a trouver un gestionnaire catch dont le param` etre indique un type compatible avec celui de lexpression. Les fonctions sont explor ees de la plus r ecemment appel ee vers la plus anciennement appel ee (c.-` a-d. du sommet vers la base de la pile dex ecution) ; ` a lint erieur dune fonction, les gestionnaires catch sont explor es dans lordre o` u ils sont ecrits. Le gestionnaire19 catch(...) attrape toutes les exceptions. Il nest donc pas toujours pr esent et, quand il lest, il est le dernier de son groupe. D` es lentr ee dans un gestionnaire catch, lexception est consid er ee trait ee (il ny a plus de  balle en lair ). A la n de lex ecution dun gestionnaire, le contr ole est pass e` a linstruction qui suit le dernier gestionnaire de son groupe. Les instructions restant ` a ex ecuter dans la fonction qui a lanc e lexception et dans les fonctions entre celle-l` a et celle qui contient le gestionnaire qui a attrap e lexception sont abandonn ees. Il est important de noter que les objets locaux attach es aux blocs brusquement abandonn es ` a cause du lancement dune exception sont proprement d etruits. Exemple. A plusieurs reprises nous avons vu des classes repr esentant des tableaux dans lesquels les indices etaieznt contr oles. Voici comment cela pourrait s ecrire20 : 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 ne de caract` eres est vite fait, mais lancer une exception objet dune classe, d eriv ee de la classe exception, express ement d enie dans ce but, serait plus puissant et utile.
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` eme: " << message << \n; } catch (bad_alloc) { // origine : new cout << "Probl` eme dallocation de m emoire\n"; } catch (...) { // toutes les autres exceptions cout << "Probl` eme indetermin e\n"; } cout << " - Termin e\n"; Ex ecution : i, t[i] ? 5 0.125 t[5] = 0.125 ... i, t[i] ? 25 0.250 Probl` eme: indice hors bornes Termin e

6.2

Attraper une exception

Lorsque lex ecution dune instruction lance une exception, un gestionnaire catch ayant un argument compatible avec lexception est recherch e dans les fonctions actives (commenc ees et non encore termin ees), de la plus r ecemment appel ee vers la plus anciennement appel ee. 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 : eme type, ou T1 et T2 sont le m 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 eectu ees. Ainsi, par exemple, un gestionnaire catch(float) nattrape pas des exceptions int. Note. Lorsque le contr ole entre dans un gestionnaire catch, son argument est initialis e (par lexception eectivement lanc ee) selon un m ecanisme analogue ` a celui de linitialisation des arguments dun appel de fonction. Cest donc la m eme sorte de consid erations que celles que lon fait pour un appel de fonction qui permettent de choisir le mode (valeur, adresse ou r ef erence) de largument du gestionnaire. On notera en particulier que 50

la d eclaration comme pointeur ou r ef erence est indispensable si on souhaite faire jouer le polymorphisme. Par exemple, le programme suivant est maladroit : catch(exception e) { // maladroit cout << "Probl` eme: " << e.what(); } car on y appelle la fonction what de la classe exception, et lachage obtenu sera trop g en eral, dans le genre :  Probl` eme : Unknown exception . En principe, lexception eectivement lanc ee appartient ` a une classe d eriv ee de exception, dans laquelle la fonction virtuelle what a et e red enie pour donner un message int eressant. Pour obtenir lachage de ce message il faut ecrire : 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 lint erieur dun gestionnaire catch on peut ecrire linstruction throw; elle indique que lexception, qui etait tenue pour trait ee en entrant dans le gestionnaire, doit etre relanc ee comme si elle navait pas encore et e attrap ee.

6.3

D eclaration des exceptions quune fonction laisse echapper

Lorsquune exception est lanc ee ` a lint erieur dune fonction dans laquelle elle nest pas attrap ee on dit que la fonction  laisse echapper  lexception en question. Une fonction peut indiquer les types des exceptions quelle laisse echapper. Cela d enit avec pr ecision, ` a lintention des utilisateurs de la fonction, les risques entra n es par un appel de cette fonction. La syntaxe est : en-t ete de la fonction throw(type 1 , type 2 , ... type k ) 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 g en eralement, dune fonction dont len-t ete se pr esente ainsi en-t ete de la fonction throw(T1 , T2 *) 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-t ete de la fonction throw() indique quun appel dune telle fonction ne peut lancer aucune exception. En outre, on consid` ere quune fonction dont len-t ete ne comporte pas de clause throw laisse echapper toutes les exceptions. De telles d eclarations ne font pas partie de la signature : deux fonctions dont les en-t etes ne di erent quen cela ne sont pas assez di erentes pour faire jouer le m ecanisme de la surcharge. Pour les exceptions explicitement lanc ees par la fonction, cette indication permet une v erication d` es la compilation, avec l emission d eventuels messages de mise en garde.

51

Quil y ait eu une mise en garde durant la compilation ou pas, lorsquune exception non attrap ee se produit dans une fonction qui ne la laisse pas echapper, la fonction pr ed enie unexpected() est appel ee. Par d efaut la fonction unexpected() se r eduit ` a lappel de la fonction terminate().

6.4

La classe exception

Une exception est une valeur quelconque, dun type pr ed eni ou bien dun type classe d eni ` a cet eet. Cependant, les exceptions lanc ees par les fonctions de la biblioth` eque standard sont toutes des objets de classes d eriv ees dune classe d enie sp ecialement ` a cet eet, la classe exception, qui comporte au minimum les membres 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 limpl ementation des exceptions. La fonction what renvoie une cha ne de caract` eres qui est une description informelle de lexception. On notera que, comme le montre la d eclaration de cette classe, aucune exception ne peut etre lanc ee durant lex ecution dun membre de la classe exception. Bien que la question concerne plus la biblioth` eque standard que le langage lui-m eme, voici les principales classes d eriv ees directement ou indirectement de exception : exception bad exception : cette exception, en rapport avec la d enition par lutilisateur de la fonction unexpected, signale l emission par une fonction dune exception qui nest pas d eclar ee dans sa clause throw. bad cast : cette exception signale lex ecution dune expression dynamic cast invalide. bad typeid : indique la pr esence dun pointeur p nul dans une expression typeid(*p) logic error : ces exceptions signalent des erreurs provenant de la structure logique interne du programme (en th eorie, on aurait pu les pr evoir en etudiant le programme) : domain error : erreur de domaine, invalid argument : argument invalide, length error : tentative de cr eation dun objet de taille sup erieure ` a la taille maximum autoris ee (dont le sens pr ecis d epend du contexte), out of range : arguments en dehors des bornes. bad alloc : cette exception correspond ` a l echec dune allocation de m emoire. runtime error : exceptions signalant des erreurs, autres que des erreurs dallocation de m emoire, qui ne peuvent etre d etect ees que durant lex ecution du programme : range error : erreur de rang, overflow error : d ebordement arithm etique (par le haut), underflow error : d ebordement arithm etique (par le bas). Remarque. Les classes pr ec edentes sont surtout des emplacements r eserv es pour des exceptions que les fonctions de la biblioth` eque standard (actuelle ou future) peuvent eventuellement lancer ; rien ne dit que cest actuellement le cas.

R ef erences
B. Stroustrup `me e dition) Le langage C++ (3e CampusPress, 1999

52

H. Garreta `que C++ Norme ISO Le langage et la bibliothe Ellipses, 2000 J. Charbonnel e Langage C++, la proposition de standard ANSI/ISO explique Masson, 1996 A. G eron, F. Tawbi velopper avec C++ Pour mieux de InterEditions, 1999

53