Vous êtes sur la page 1sur 23

Réf.

: H3078 V1

Langage C++
Date de publication :
10 août 2003

Cet article est issu de : Technologies de l'information | Technologies logicielles


Architectures des systèmes

par Manuel SERRANO

Résumé Le langage C++ a été développé à partir de C en rajoutant une couche objet.
Par rapport à C, assembleur portable qui couvre un très vaste champ d’applications, C++
est emprunt de méthodologies et de techniques issues du génie logiciel. C++ reste difficile
à maîtriser, mais un programmeur avec une bonne connaissance de ce langage a la
possibilité d'écrire des programmes très efficaces.

Pour toute question :


Service Relation clientèle
Techniques de l’Ingénieur
Immeuble Pleyad 1 Document téléchargé le : 11/10/2016
39, boulevard Ornano
93288 Saint-Denis Cedex Pour le compte : 7200038948 - universite de limoges // karim ZIBANI // 164.81.14.58

Par mail :
infos.clients@teching.com
Par téléphone :
00 33 (0)1 53 35 20 20 © Techniques de l'Ingénieur | tous droits réservés
Ce document a ete delivre pour le compte de 7200038948 - universite de limoges // karim ZIBANI // 164.81.14.58

Langage C++
par Manuel SERRANO
Chargé de recherche à l’Institut national de recherche en informatique et en automatique
(INRIA)

1. Programmes C++...................................................................................... H 3 078 – 2


2. Macroexpanseur CPP.............................................................................. — 2
2.1 Inclusion de fichiers sources ...................................................................... — 2
2.2 Macros et macrofonctions .......................................................................... — 3
2.3 Compilation conditionnelle......................................................................... — 3
2.4 Identificateurs, chaînes de caractères et macros prédéfinies .................. — 4
3. Types et typage ........................................................................................ — 4
3.1 Types de base .............................................................................................. — 4
3.2 Constructeurs de types ............................................................................... — 5
3.3 Conversions de types .................................................................................. — 5
Parution : août 2003 - Ce document a ete delivre pour le compte de 7200038948 - universite de limoges // karim ZIBANI // 164.81.14.58

4. Expressions et instructions................................................................... — 5
4.1 Expressions .................................................................................................. — 5
4.2 Instructions................................................................................................... — 7
5. Fonctions.................................................................................................... — 8
5.1 Déclarations et prototypes.......................................................................... — 8
5.2 Blocs d’activation et récursion ................................................................... — 9
5.3 Fonctions inline............................................................................................ — 9
5.4 Passage des arguments .............................................................................. — 10
5.5 Surcharge ..................................................................................................... — 10
5.6 Fonction templates ...................................................................................... — 11
6. Objets .......................................................................................................... — 11
6.1 Classes.......................................................................................................... — 11
6.2 Surcharge des opérateurs........................................................................... — 12
6.3 Construction et destruction des instances ................................................ — 13
6.4 Héritage ........................................................................................................ — 14
6.5 Fonctions membres virtuelles .................................................................... — 16
6.6 Classes templates ........................................................................................ — 17
7. Exceptions.................................................................................................. — 17
7.1 Lever des exceptions................................................................................... — 17
7.2 Intercepter les exceptions ........................................................................... — 18
8. Interopérabilité avec C et les bibliothèques .................................... — 18
9. Conclusion ................................................................................................. — 19
Références bibliographiques ......................................................................... — 19
Pour en savoir plus........................................................................................... Doc. H 3 078

e langage C++ est apparu au début des années 1980. Il a été conçu et développé
L par Bjarne Stroustrup dans les laboratoires d’AT&T. C++ est bâti au-dessus du
langage C dont il présente un certain nombre d’extensions. La plus spectaculaire est
probablement sa couche objet dont la conception a été fortement influencée par le
langage Simula 67. Plus que les variations techniques, c’est surtout l’« esprit » des
deux langages qui sépare C et C++. C est un assembleur portable qui couvre un très
vaste champ d’applications. On peut écrire en C des programmes de très bas
niveau, comme des contrôleurs de composants matériels spécialisés, mais aussi
des programmes plus généralistes tels que des interfaces graphiques utilisateurs. C
permet un contrôle très fin de la mémoire, ce qui constitue un atout fort du langage.

Toute reproduction sans autorisation du Centre français d’exploitation du droit de copie est strictement interdite.
© Techniques de l’Ingénieur, traité Informatique H 3 078 − 1

tiwekacontentpdf_h3078 v1 Ce document a ete delivre pour le compte de 7200038948 - universite de limoges // karim ZIBANI // 164.81.14.58
Ce document a ete delivre pour le compte de 7200038948 - universite de limoges // karim ZIBANI // 164.81.14.58

LANGAGE C++ _________________________________________________________________________________________________________________________

Cela impose aussi discernement et compétence de la part des programmeurs car


avec une telle capacité, tous les coups sont permis ! En revanche, C++ est emprunt
de méthodologies et de techniques issues du génie logiciel. C++ a été voulu, et donc
conçu, comme une alliance entre l’efficacité de C et la robustesse de langages de
plus haut niveau tels que Ada ou Eiffel. Là où C prône la débrouillardise et un prag-
matisme forcené, C++ met l’accent sur la pérennité et la fiabilité du logiciel. Cela se
ressent, par exemple, dans l’incitation faite aux programmeurs d’utiliser des types
abstraits de données (les classes) mais aussi dans un système de types plus strict
que celui de C. La possibilité, pour un compilateur C++, d’engendrer du code pres-
que aussi efficace que C a fortement influencé la conception du langage. Il en résulte
que C++ est assez difficile à maîtriser, mais, en contrepartie, une bonne connais-
sance de ce langage permet d’écrire des programmes très efficaces. Cette caracté-
ristique le distingue de bien d’autres langages de programmation évolués.
C++ a probablement bâti son succès sur C. Au moment où C++ est apparu, C
était déjà bien implanté et l’engouement pour la programmation par objets
émergeait. En se présentant comme une extension du langage C permettant la
programmation par objets, il a attiré de nombreux programmeurs. Malgré
l’apparition, depuis quelques années, de langages de plus haut niveau qui lui
disputent sa popularité, C++ est encore, en ce début de siècle, un des langages
généralistes les plus utilisés.
Parution : août 2003 - Ce document a ete delivre pour le compte de 7200038948 - universite de limoges // karim ZIBANI // 164.81.14.58

1. Programmes C++ nommé CPP (C pre-processor). Le langage pour manipuler CPP est
assez rudimentaire. Il ne peut être utilisé que pour opérer des trans-
formations simples et ne permet en guise de calcul que la concaté-
nation d’identificateurs et la construction de chaînes de caractères.
C++ [1][2][3] a hérité du langage C [4][5] la structure des program-
Malgré ce dépouillement, CPP joue un rôle absolument central tant
mes. Bien que cela ne fasse pas partie de la spécification du lan-
pour le développement en C qu’en C++. En particulier, comme CPP
gage, le code source d’un programme C++ peut être « éclaté » sur
permet la compilation conditionnelle, il est généralement utilisé
plusieurs fichiers. Il est généralement possible de les compiler sépa-
pour implanter les parties d’un programme qui sont intrinsèque-
rément (c’est-à-dire les uns après les autres) et de les rassembler
ment non portables (par exemple, les parties qui dépendent des
ultérieurement (par une opération qui se nomme l’édition de liens)
spécificités de tel ou tel système d’exploitation ou matériel). Les
pour former un programme exécutable. Tous ces fichiers sources
fonctionnalités de CPP sont les suivantes :
ont la même structure. Ils sont faits de commentaires (notés // …. ou
/✽ … ✽/), de déclarations de types, de variables et de fonctions. Par — inclusion de fichiers sources ;
ailleurs, l’éditeur de liens distingue une fonction particulière, géné- — macroexpansion simple ou fonctionnelle ;
ralement nommée main, qui est le point d’entrée du programme, — compilation conditionnelle ;
c’est-à-dire que cette fonction est appelée automatiquement dès le — calcul d’identificateurs et information sur la compilation.
lancement du programme. Pour l’exemple, voici un premier pro- Il est généralement possible d’inspecter le résultat de la phase de
gramme C++ correctement formé et pouvant être exécuté : macroexpansion soit au moyen d’une option de compilation (par
exemple, –E avec le compilateur g++ ou /E avec le compilateur
VisualC++), soit au moyen d’un outil séparé (par exemple, cpp sous
// Déclaration d’un type Unix). Toutes les déclarations de CPP partagent une syntaxe
typedef char boolean ; commune assez simple. En particulier, elles commencent toutes par
// Déclaration d’une variable le caractère # en colonne 0 suivi facultativement de caractères
boolean init = 0; d’espace ou de tabulation.

// Déclaration de la fonction principale


int main (int argc, char ✽argv[]){
return argc – 1;
2.1 Inclusion de fichiers sources
}
L’inclusion de fichiers sources est la première fonction de CPP.
Les techniques d’évaluation des programmes C++ sont les Son rôle est encore plus fort en C++ qu’en C parce qu’elle est essen-
mêmes que pour la plupart des langages de programmation. Les tielle à la couche objet. En effet, pour s’éviter des copies manuelles
programmes C++ peuvent être interprétés ou compilés. Cette de fragments de code, on place généralement les définitions des
deuxième solution étant largement dominante, nous nous place- classes dans des fichiers inclus. La forme d’inclusion de fichiers est
rons par la suite dans ce contexte. la suivante :

#include <file>
2. Macroexpanseur CPP ou
#include "file"

C++ partage avec le langage de programmation C une phase de


« macroexpansion », c’est-à-dire qu’un programme C++ n’est pas Cette directive a pour effet d’inclure le fichier de nom file. La dif-
évalué ou compilé tel quel ; il subit au préalable une réécriture qui férence entre les deux formes réside dans la liste des répertoires à
est contrôlée par l’utilisateur, au moyen d’un macroexpanseur parcourir lors de la recherche du fichier.

Toute reproduction sans autorisation du Centre français d’exploitation du droit de copie est strictement interdite.
H 3 078 − 2 © Techniques de l’Ingénieur, traité Informatique

tiwekacontentpdf_h3078 v1 Ce document a ete delivre pour le compte de 7200038948 - universite de limoges // karim ZIBANI // 164.81.14.58
Ce document a ete delivre pour le compte de 7200038948 - universite de limoges // karim ZIBANI // 164.81.14.58

________________________________________________________________________________________________________________________ LANGAGE C++

Ainsi, supposons que le fichier exemple.h contienne le texte sui- Le caractère \ placé en fin de ligne a pour effet d’annuler la fin de
vant : ligne qui lui succède. C’est ainsi un moyen de découper les défini-
tions des macros sur plusieurs lignes.
Ceci est un simple La syntaxe des macrofonctions est la suivante :
texte sur trois
lignes
#define <ident> (<ident1>, <ident2>, …, <identn>)\
Alors le fichier source suivant : <expression jusqu’à fin de ligne>

/✽ un exemple d’inclusion ✽/ Par exemple :


#include "exemple.h"
/✽ vraiment trop simple… ✽/
#define getchar() getc(stdin)
sera expansé par CPP en :
#define min(a,b) ((a)<(b) ? (a) : (b))
#define max (a,b) ((a)<(b) ? (b) : (a))
/✽ un exemple d’inclusion ✽/Ceci est un simple while (c = getchar() != EOF)
texte sur trois m = min (m, c) ;
lignes/✽ vraiment trop simple… ✽/

La directive #include permet l’inclusion récursive de fichiers est macroexpansé en :


(c’est-à-dire qu’un fichier inclu peut lui-même contenir des directi-
ves #include). Toutefois, comme CPP ne gère pas les boucles dans
Parution : août 2003 - Ce document a ete delivre pour le compte de 7200038948 - universite de limoges // karim ZIBANI // 164.81.14.58

les inclusions, c’est à l’utilisateur de se prémunir contre les inclu- while (c = getc(stdin) != EOF)
sions cycliques sous peine de voir la phase de macroexpansion bou- m = ((m)< (c) ? (m) : (c)) ;
cler indéfiniment. Nous verrons au paragraphe 2.3 qu’il existe un
moyen pragmatique simple pour éviter cet écueil.
Les macrofonctions de CPP correspondent à des fonctions utili-
sant une sémantique d’appel par nom. Ce mode d’évaluation peut
jouer des tours aux programmateurs insuffisamment vigilants. En
2.2 Macros et macrofonctions particulier, il est recommandé d’abuser de parenthèses dans
l’expression introduite afin d’éviter des erreurs assez classiques de
la macroexpansion mêlant expansions en cascade et « précédence »
Un usage très fréquent de CPP est la définition de macros (pour des opérateurs.
macro-instructions) et macrofonctions. Les premières permettent
Pour conclure sur la définition des macros, notons qu’il est possi-
de substituer un identificateur du programme source par une
ble d’« effacer » la définition d’une macro au moyen de la déclara-
expression quelconque, les secondes permettent une substitution
tion #undef et qu’il est généralement possible au moment de la
paramétrée par des expressions de l’utilisateur. La directive de défi-
compilation de spécifier sur la ligne de commande des définitions
nition des macros est la suivante :
de macros ou des effacements de macros (sous Unix, ces options
sont généralement "–D" et "–U").

#define <ident> <expression jusqu’à fin de ligne>

2.3 Compilation conditionnelle


Par exemple :

Comme il a été mentionné en introduction, la compilation condi-


#define BUFSIZ 8192 tionnelle de CPP est le mécanisme standard des développeurs en C
#define stdout _ _stdfile[0] et en C++ pour traiter les problèmes de portabilité des codes sour-
#define int32 long ces. Grâce aux directives #if, #else et #endif, il est possible d’inclure
ou d’expanser du code en fonction de critères connus lors de la
int foo (int32 x) { compilation. Avec ce mécanisme, il est par exemple possible
char [BUFSIZ] tab; d’écrire des programmes qui opèrent des traitements différents s’ils
sont compilés sous Unix ou sous Windows. La forme générale de
fprintf (stdout, "%s\n", tab);
compilation conditionnelle est :
fprintf (stdout, "sizeof(int32)=%d\n", sizeof (int32));
}

#if <cpp-test>
est macroexpansé en : <un texte quelconque>
#else
<un texte quelconque>
#endif
int foo (long x) {
char [8192] tab;
fprintf (_ _stdfile [0], "%s\n", tab); La partie <cpp-test> est une expression arithmétique ne mettant
fprintf (_ _stdfile [0], "sizeof(int32)=%d\n", sizeof (long)); en œuvre que des constantes ou des tests vérifiant l’existence de
} macros.

Toute reproduction sans autorisation du Centre français d’exploitation du droit de copie est strictement interdite.
© Techniques de l’Ingénieur, traité Informatique H 3 078 − 3

tiwekacontentpdf_h3078 v1 Ce document a ete delivre pour le compte de 7200038948 - universite de limoges // karim ZIBANI // 164.81.14.58
Ce document a ete delivre pour le compte de 7200038948 - universite de limoges // karim ZIBANI // 164.81.14.58

LANGAGE C++ _________________________________________________________________________________________________________________________

Par exemple : est macroexpansé en :

printf("%s = %d\n", "3 + 2 ✽ 4", 3 + 2 ✽ 4);


#define LINUX
int positif_ _sin(double x) {double res = sin (x);
#if !defined (MAXFILE) return res < 0 ? 0 : res;}
# define MAXFILE 32
#endif Rappelons que le caractère \ « prolonge » la ligne courante avec la
ligne suivante. On voit donc sur notre exemple précédent que le
typedef struct { caractère \ est le moyen permettant de définir des macros sur plu-
#if defined(LINUX) || defined(SUN) sieurs lignes.
int _cnst ; unsigned char ✽_ptr;
#else CPP prédéfinit quelques macros pouvant être utilisées comme tou-
unsigned char ✽_ptr; tes les macros déclarées par l’utilisateur. À titre d’exemple, citons :
#endif — _ _LINE_ _ qui indique la ligne courante dans le fichier source ;
unsigned char ✽_base; — _ _FILE_ _ qui indique le nom du fichier source ;
} FILE; — _ _STDC_ _ qui est définie uniquement pour les compilateurs
respectant la norme ISO 9899 (de définition du langage C) ;
#if (MAXFILE < 8) — _ _cplusplus qui est définie uniquement pour les compilateurs
– –> error MAXSIZE must be larger than 1024 C++.
#else
FILE ✽buffer[MAXSIZE]; Les macros _ _LINE_ _ et _ _FILE_ _ sont, par exemple, utilisées
#endif dans la définition de la macro assert qui permet l’introduction
d’assertions dans les programmes C et C++ :

est macroexpansé en : #define assert(cond) \


if(!(cond)) { \
Parution : août 2003 - Ce document a ete delivre pour le compte de 7200038948 - universite de limoges // karim ZIBANI // 164.81.14.58

cerr <<"assert" #cond "failed in file" << _ _FILE_ _ \


typedef struct { << ", line" << _ _LINE_ _ "\n"; \
int _cnst ; unsigned char ✽_ptr; abort(); \
unsigned char ✽_base; }
} FILE;
FILE *buffer[MAXSIZE];
3. Types et typage
Les tests de CPP sont aussi très fréquemment utilisés pour éviter
les cycles infinis dans l’inclusion de fichier. La convention d’usage Au premier abord, le typage de C++ peut sembler très similaire à
est de protéger tout le corps d’un fichier par le test d’existence d’une celui de C. En réalité, même s’ils partagent nombre de points
macro définie dans le fichier. Ainsi les fichiers inclus ressemblent communs, les deux langages reposent sur des systèmes assez diffé-
généralement à : rents. En particulier, le typage de C++ est plus contraint que celui de
C, son algèbre de type est plus étendue et les types sont plus
expressifs (par exemple parce qu’ils permettent d’exprimer la sur-
#if !defined(STDIO_H) charge des fonctions, des relations de sous-typage ou encore des
#define STDIO_H types paramétriques). Nous dressons ici un panorama rapide du
Le corps du fichier typage de C++ qui exclut les traits liés à la couche objet. Ceux-ci
#endif seront présentés ultérieurement (§ 6).
En C, les types servent exclusivement à assurer, lors de la compi-
lation des programmes, une certaine correction des programmes.
Le typage de C est en effet destiné à augmenter le nombre d’erreurs
2.4 Identificateurs, chaînes de caractères que le compilateur peut détecter. L’intention est de simplifier la
et macros prédéfinies phase de développement en notifiant des erreurs logiques, telle une
donnée utilisée dans un contexte incompatible, dès la compilation.
Les types sont des annotations portées sur les déclarations des
CPP permet la construction des chaînes de caractères ainsi que le variables, des paramètres formels et sur les résultats des fonctions.
calcul d’identificateurs. L’opérateur # suivi d’un identificateur déno- Les types jouent en C le même rôle que les unités dans la résolution
tant un argument d’une macrofonction introduit, dans le résultat de d’un exercice de physique. Une vitesse doit par exemple être expri-
l’expansion, une chaîne de caractères formée de la valeur de l’argu- mée en mètres par seconde et si, après un calcul, on trouve une
ment. L’opérateur ## permet de concaténer et de créer des identifi- autre unité, c’est qu’il y a une erreur quelque part…
cateurs. Comme l’opérateur #, il s’applique à des arguments de
macrofonctions. Ainsi : 3.1 Types de base
C++ dispose de plusieurs types prédéfinis qui caractérisent des
#define MY_PRINT (x) printf("%s = %d\n", #x, x)
données primitives manipulées par les ordinateurs :
#define positif(func) \ — il existe plusieurs sortes de nombres : des entiers (char, short,
int positif_ _##func(double x) { \ int et long) et des réels (float et double) ayant différentes précisions.
double res = func(x); \ Les entiers peuvent être signés (signed) ou non signés (unsigned) ;
return res <0 ? 0 : res; \ — dans ses « incarnations » récentes, C a introduit des booléens
} (bool) qui ont été également adoptés par C++ ;
— enfin, il existe un type très particulier nommé void qui dénote
MY_PRINT(3 + 2 ✽ 4); l’absence de valeur. L’existence d’un tel type peut sembler para-
positif(sin) doxale mais nous verrons sa justification au paragraphe 4.

Toute reproduction sans autorisation du Centre français d’exploitation du droit de copie est strictement interdite.
H 3 078 − 4 © Techniques de l’Ingénieur, traité Informatique

tiwekacontentpdf_h3078 v1 Ce document a ete delivre pour le compte de 7200038948 - universite de limoges // karim ZIBANI // 164.81.14.58
Ce document a ete delivre pour le compte de 7200038948 - universite de limoges // karim ZIBANI // 164.81.14.58

________________________________________________________________________________________________________________________ LANGAGE C++

Il convient de noter que C++ dispose de très peu de types de base. quel ordre les sous-expressions constituant une expression doivent
En particulier, il n’existe pas de type dénotant des caractères ni de être évaluées. Par ailleurs, pour forcer un ordre d’évaluation, il est
type dénotant des chaînes de caractères. Ces valeurs sont représen- toujours possible de regrouper les expressions par des paires de
tées respectivement par des nombres et par des pointeurs sur des parenthèses.
paquets de nombres.

4.1 Expressions
3.2 Constructeurs de types
Les expressions sont constituées d’opérations. L’évaluation d’une
C++ permet de construire des types élaborés tels que des enregis- expression produit une valeur. Une opération est un traitement à
trements, des unions, des types énumérés, des pointeurs, des appliquer sur les opérandes. On distingue plusieurs types d’opéra-
tableaux ou des constantes. La syntaxe de ces déclarations étant fas- teurs en fonction du nombre d’opérandes qu’ils admettent (les opé-
tidieuse, nous nous contentons de présenter quelques exemples : rateurs unaires attendent un opérande, les binaires attendent deux
opérandes, etc.). En C++, les opérateurs peuvent être préfixes, c’est-
à-dire placés avant leurs opérandes, ou infixes, c’est-à-dire placés
// une énumération entre deux opérandes.
typedef enum day {mon, tue, wed, thu, fri, sat, sun} day;
// un enregistrement
struct person { 4.1.1 Litéraux et variables
char ✽lastname, ✽firstname;
struct date { Les expressions simples sont constituées de litéraux (chaînes de
enum day day; caractères ou nombres) et de références à des variables. Les identifi-
char month; cateurs, qui servent à nommer les variables, de C++ sont définis par
short year; le même langage formel que les identificateurs de C. Les identifica-
Parution : août 2003 - Ce document a ete delivre pour le compte de 7200038948 - universite de limoges // karim ZIBANI // 164.81.14.58

} birth; teurs sont définis par l’expression rationnelle [a-zA-Z_ ][a-zA-Z0-9_ ]✽.
}; De plus, C++ réserve quelques identificateurs, qui ne peuvent donc
pas être utilisés pour les variables (il s’agit principalement des iden-
// un pointeur vers un enregistrement tificateurs servant à désigner les mots-clés des instructions et les
typedef person ✽ptr_person; identificateurs servant d’annotations aux définitions).
// un pointeur vers une fonction retournant un pointeur vers Il existe trois sortes de variables :
// une personne acceptant deux chaînes de caractères, un jour — les variables globales. Elles conservent leur valeur tout au long
// et deux nombres de la durée de l’exécution du programme. Ces variables ne sont pas
typedef ptr_person (✽fun)(char ✽, char ✽, day, char, short); obligatoirement visibles depuis toutes les fonctions. En effet, il
existe plusieurs moyens de restreindre la visibilité des variables glo-
// un pointeur vers un entier constant
bales. On peut réserver leur usage au fichier qui les définit. On peut
typedef const int ✽cint;
restreindre leur usage à une fonction particulière ou encore à un
// un pointeur constant vers un entier ensemble de classes ; dans ce cas, on parlera de membres statiques
typedef int ✽ const pint; de classe. Nous reviendrons sur ce point (§ 6.1) ;
— les variables locales. Elles ne sont visibles que dans le bloc qui
les définit (§ 4.2.1). Elles « perdent » leur valeur à chaque invocation
En plus des constructeurs précédemment cités, comme cela sera pré- de la fonction qui contient le bloc. Les paramètres d’une fonction
senté au paragraphe 6, chaque classe introduit un ou plusieurs types. ont le même statut que les variables locales. Leur visibilité est éten-
due à toute la fonction ;
— les variables d’instances. Ces variables ne sont accessibles
que par le biais de fonctions membres (§ 6). Le corpus C++ a préféré
3.3 Conversions de types l’expression de donnée membre pour les désigner. C’est donc cette
terminologie que nous utiliserons par la suite.
C++ a hérité de C la capacité de convertir des données d’un type vers
un autre (cast en anglais). Ces conversions font parfois intervenir des
opérations lors de l’exécution (par exemple, lorsqu’un nombre entier
4.1.2 Appel de fonction
est converti en nombre flottant) mais souvent, il s’agit simplement
d’informations fournies au compilateur pour la phase d’analyse de L’appel de fonction (tableau 1) est bien entendu un autre type
types (par exemple, lorsque l’on convertit deux types pointeurs) qui d’expression. Il faut noter qu’à la différence d’autres langages tels
n’ont aucune répercussion à l’exécution. Les conversions font intrinsè- que Pascal, C++ ne fait pas de distinction entre fonction et procé-
quement partie du système de types des deux langages. C++ en décou- dure. Il n’existe pas de procédure (c’est-à-dire de fonction ne
rage toutefois un usage intensif car les conversions explicites construisant pas de valeur mais opérant un effet visible sur la
diminuent la pertinence des messages du compilateur. mémoire ou sur une variable) en C++ mais il existe un type parti-
culier (void) qui indique qu’une fonction ne retourne pas de résultat.
Au demeurant, les appels de fonctions de type void sont tout de
même considérés comme des expressions.
4. Expressions et instructions (0)

Tableau 1 – Appel de fonction


C++ partage avec C presque intégralement une syntaxe commune
pour définir des expressions et des instructions. Nous la présentons Opérateur Description Usage
succinctement ici en insistant sur les quelques différences entre les
deux langages. Les opérateurs ont des priorités qui spécifient dans () appel de fonction expr (expr1, …, exprn)

Toute reproduction sans autorisation du Centre français d’exploitation du droit de copie est strictement interdite.
© Techniques de l’Ingénieur, traité Informatique H 3 078 − 5

tiwekacontentpdf_h3078 v1 Ce document a ete delivre pour le compte de 7200038948 - universite de limoges // karim ZIBANI // 164.81.14.58
Ce document a ete delivre pour le compte de 7200038948 - universite de limoges // karim ZIBANI // 164.81.14.58

LANGAGE C++ _________________________________________________________________________________________________________________________

Il faut remarquer que comme le langage C++ permet de manipuler c’est-à-dire qu’ils ont plusieurs définitions. Les types des sous-
des pointeurs sur fonctions, l’expression en position fonctionnelle expressions permettent de déterminer, parmi l’ensemble des défini-
sur un site d’appel est une expression quelconque. En particulier, il tions, celle qui doit être utilisée. Pour << et >>, c’est le type de
n’y a pas de restriction qui impose que cette expression soit un iden- l’expression située à gauche de l’opérateur qui détermine la nature
tificateur de fonction. de l’opération. Si le type de cette expression est ostream (respecti-
vement istream), alors c’est une écriture (respectivement une lec-
ture) qui est effectuée. Si le type est un nombre entier, alors c’est un
4.1.3 Opérateurs arithmétiques décalage qui est effectué. La surcharge des opérateurs sera présen-
tée plus en détail au paragraphe 6.2.
Il existe six opérateurs arithmétiques (tableau 2).
Le type d’une expression arithmétique est conditionné par le type
des opérandes. Ainsi, ajouter deux entiers produit un entier, diviser 4.1.6 Opérateurs booléens
deux entiers produit également un entier, etc.
(0) Les opérateurs booléens (tableau 5) sont utilisés pour exprimer
des conditions.
Tableau 2 – Opérateurs arithmétiques (0)
Opérateur Description Usage
+ addition expr1 + expr2 Tableau 5 – Opérateurs booléens
– soustraction expr1 – expr2
Opérateur Description Usage
– opposé – expr
! négation logique ! expr
✽ multiplication expr1 ✽ expr2
inférieur,
/ division expr1 / expr2 <, <= inférieur ou égal expr1 < expr2
Parution : août 2003 - Ce document a ete delivre pour le compte de 7200038948 - universite de limoges // karim ZIBANI // 164.81.14.58

% reste euclidien expr1 % expr2 supérieur,


>, >= supérieur ou égal expr1 > expr2

==, != égalité, inégalité expr1 == expr2


4.1.4 Opérateurs bit-à-bit et logique,
&&, || ou logique expr1 && expr2
Les opérateurs bit-à-bit (tableau 3) permettent à C++ de contrôler
finement les représentations des entiers et des pointeurs. (0)
4.1.7 Opérateurs de contrôle
Tableau 3 – Opérateurs bit-à-bit
Certaines expressions permettent d’exprimer des propriétés sur
Opérateur Description Usage le fil d’exécution du programme (ce que l’on nomme généralement
le « contrôle d’exécution »).
^ ou exclusif entier1 ^ entier2 Ces deux opérateurs (tableau 6) placent C++ à mi-chemin entre
et bit-à-bit, les langages d’instructions et les langages d’expressions (comme
&, | ou bit-à-bit entier1 & entier2 par exemple Lisp). Ils permettent en particulier d’alléger l’écriture de
certains programmes.
~ opposé ~ entier
(0)
décalage à gauche,
<<, >> décalage à droite entier1 << entier2
Tableau 6 – Opérateurs de contrôle
Opérateur Description Usage
4.1.5 Opérateurs d’écriture et de lecture
?: conditionnelle expr1 ? expr2 : expr3
Les opérateurs d’affichage et de lecture (tableau 4) permettent à , séquence expr1 , expr2
C++ de proposer des opérations traitant les entrées/sorties de
manière sûre et, comme nous le verrons (§ 6.2), extensible. (0)
Par exemple, le programme suivant :

Tableau 4 – Opérateurs d’écriture et de lecture


if (x > 0) {
Opérateur Description Usage foo(1);
} else {
<< écriture expr1 << expr2 bar(0);
>> lecture expr1 >> expr2 foo(0);
}

Comme on peut le constater, les opérateurs << et >> ont deux peut être écrit :
rôles différents. Ils peuvent être utilisés pour opérer un décalage à
gauche ou à droite dans des entiers et ils peuvent être utilisés pour
réaliser des entrées ou des sorties. Ces opérateurs sont surchargés, foo(x > 0 ? 1 : (bar(0), 0));

Toute reproduction sans autorisation du Centre français d’exploitation du droit de copie est strictement interdite.
H 3 078 − 6 © Techniques de l’Ingénieur, traité Informatique

tiwekacontentpdf_h3078 v1 Ce document a ete delivre pour le compte de 7200038948 - universite de limoges // karim ZIBANI // 164.81.14.58
Ce document a ete delivre pour le compte de 7200038948 - universite de limoges // karim ZIBANI // 164.81.14.58

________________________________________________________________________________________________________________________ LANGAGE C++

4.1.8 Opérateurs d’affectation


Tableau 8 – Mémoire

Opérateur Description Usage


Le test d’égalité est noté en C++ ==. Il ne faut pas le confondre
avec l’affectation qui est notée =. Cette dernière est également une ✽ déréférence ✽expr
expression produisant une valeur. Par ailleurs, comme toute valeur
C++ peut jouer le rôle d’un booléen dans un test, le compilateur n’a new allocation mémoire new
que peu de chance de détecter la confusion entre == et =. Cette désallocation
confusion est une des principales sources d’erreurs pour les pro- delete mémoire delete
grammeurs novices.
& référence &expr
Les opérations d’affectation (tableau 7) font apparaître un nouveau
type d’expression : les lvalues. Lvalue est une notation abrégée pour champ expr.ident
. d’une structure
l’anglais left-value. Elle désigne toutes les expressions qui peuvent
être placées à gauche d’un signe =, autrement dit tout ce qui peut
–> champ d’un pointeur expr–>ident
recevoir une valeur. Une lvalue peut être une variable ou une adresse sur une structure
mémoire (élément d’un tableau, d’une structure, pointeur, référence). (0)

[] élément d’un tableau expr1[expr2]


taille mémoire
sizeof d’un type sizeof(type)
Tableau 7 – Opérateurs d’affectation
sizeof taille mémoire d’une sizeof expr
expression
Opérateur Description Usage

= affectation lvalue = expr


Parution : août 2003 - Ce document a ete delivre pour le compte de 7200038948 - universite de limoges // karim ZIBANI // 164.81.14.58

4.2 Instructions
opération
op= arithmétique lvalue op= expr
et affectation Les instructions permettent d’exprimer l’ordre dans lequel les
expressions sont évaluées. Elles permettent donc de spécifier le fil
d’exécution d’un programme. Le corps d’une fonction (le traitement à
++, – – incrément lvalue++, ++lvalue effectuer lorsqu’une fonction est appelée) est spécifié au moyen d’un
et décrément bloc qui est un type particulier d’instruction. Les instructions les plus
élémentaires sont constituées d’expressions suivies du caractère ; .

La forme lvalue op= expr est un raccourci. Elle est synonyme de


lvalue = lvalue op expr. L’opérateur op= peut être l’un des opéra- 4.2.1 Blocs
teurs suivants : +=, –=, ✽=, /=, %=,<<=, >>=, &=, ^=, |=.
Les incréments ++ et – – sont également des raccourcis. Ils peu-
vent être préfixes ou suffixes. Ainsi ++lvalue1 est une notation abré- {
gée pour (lvalue2 = lvalue1, lvalue1 = lvalue1 + 1, lvalue2) alors que instruction✽
lvalue1++ est un raccourci pour (lvalue1 = lvalue1 + 1, lvalue1). }

Contrairement au langage C, C++ n’impose pas que les variables


4.1.9 Mémoire automatiques (les variables locales) soient déclarées en début de
bloc, c’est-à-dire que C++ considère la déclaration de variable
comme une instruction normale qui peut donc avoir lieu n’importe
Le langage C++ est un langage qui explicite la gestion de la où dans un bloc. La portée d’une variable automatique commence à
mémoire (tableau 8). À ce titre, il est possible de manipuler des la ligne qui la déclare et se termine à l’accolade fermante qui ter-
valeurs et des pointeurs sur les valeurs. Obtenir la valeur pointée mine le bloc contenant la déclaration.
par un pointeur se fait au moyen de l’opérateur ✽. Comme cela a été
présenté au paragraphe 3.2, il est possible de déclarer une variable
de type pointeur. Il existe alors trois façons de construire des valeurs 4.2.2 Alternative
de type pointeur :
— en prenant l’adresse d’une case mémoire (d’une variable, d’un
élément d’un tableau ou d’une structure) ; if (expr)
— en récupérant l’adresse d’une zone mémoire allouée dynami- instruction
quement. L’allocation dynamique peut se faire au moyen de fonc- else
tions de la bibliothèque d’exécution telles que malloc, calloc, brk ou instruction
encore alloca qui sont héritées de C. En général, en C++, on préfère
utiliser l’opérateur new pour allouer des blocs mémoires (et l’opéra-
teur delete pour les libérer) ; Cette instruction est le pendant de l’opérateur ? : déjà vu au para-
— par une opération arithmétique sur un pointeur déjà existant. graphe 4.1.7. Elle permet d’exprimer classiquement les traitements
conditionnels. L’expression expr est automatiquement convertie en
L’accès aux champs des structures et aux variables d’instance des une valeur booléenne, c’est-à-dire que 0 dénote le booléen faux et
classes se fait au moyen de l’opérateur “.”. L’opérateur –> est un rac- toutes les autres valeurs sont considérées comme vrai. La partie
courci syntaxique permettant d’accéder à un champ d’une structure else qui décrit le traitement à effectuer lorsque l’expression expr est
pointée. L’expression ptr–>champ est un raccourci pour (✽ptr).champ. fausse est optionnelle.

Toute reproduction sans autorisation du Centre français d’exploitation du droit de copie est strictement interdite.
© Techniques de l’Ingénieur, traité Informatique H 3 078 − 7

tiwekacontentpdf_h3078 v1 Ce document a ete delivre pour le compte de 7200038948 - universite de limoges // karim ZIBANI // 164.81.14.58
Ce document a ete delivre pour le compte de 7200038948 - universite de limoges // karim ZIBANI // 164.81.14.58

LANGAGE C++ _________________________________________________________________________________________________________________________

4.2.3 Boucles L’instruction switch permet de réaliser des branchements indexés :

long roman2arabic(char n) {
Les boucles de C++ n’ont pas une grande originalité. Le langage
switch (n) {
supporte les trois types de boucles classiques : les répétitions avec
case’m’:
tests à l’entrée ou à la sortie de la boucle while et do/while, les bou-
case ’M’: return 1000;
cles avec initialisation et incrément (for). La syntaxe des boucles est :
case’d’:
case ’D’: return 500;
for (expr-ou-declaration✽; expr; expr) instruction
while (expr) instruction case’c’:
do instruction while (expr) case ’C’: return 100;

default: return 0;
Voici un exemple d’implantation de la fonction strlen qui fait par- }
tie de la bibliothèque standard. Cette version met en œuvre une }
boucle while :

int strlen(const char ✽s) {


char ✽w; 5. Fonctions
while (✽w++);
return w-s;
Les fonctions sont essentielles à C++. Ce sont elles qui expriment
}
les traitements à exécuter sur les données. Les fonctions possèdent
une définition et, de façon optionnelle, une ou plusieurs déclara-
Parution : août 2003 - Ce document a ete delivre pour le compte de 7200038948 - universite de limoges // karim ZIBANI // 164.81.14.58

Le second exemple repose sur une boucle for pour inverser les tions. Une fonction accepte des valeurs en paramètre et elle peut
caractères contenus dans deux chaînes de même taille. Il faut noter retourner un résultat. Le corps d’une fonction est un bloc, tel que
que la possibilité de déclarer des variables dont la portée est réduite défini au paragraphe 4.2.1. La valeur de retour d’une fonction est
à l’instruction for est une extension de C++. C ne le permet pas. indiquée au moyen de l’instruction return expr.

void swap_strings(const char ✽s1, const char ✽s2) { 5.1 Déclarations et prototypes
int 1=strlen(s1)–1;
for(int i1=0, i2=1; i2>=0; i1++, i2– –){
char a=s1[i1]; À la différence du langage C, C++ impose que toute fonction soit
s1[i1]=s2[i2]; déclarée avant d’être utilisée. Une fonction peut être déclarée par sa
s2[i2]=a; définition ou simplement par son prototype. Le prototype d’une fonc-
} tion indique le type des paramètres et le type de la valeur de retour de
} la fonction mais ne donne pas la définition de son corps. Syntaxique-
ment, le prototype d’une fonction ressemble à une définition mais le
corps est simplement remplacé par un ;. Le prototype d’une fonction
C++ diffère légèrement du prototype d’une fonction C. En C, il est per-
4.2.4 Branchements mis de ne pas spécifier tous les paramètres d’une fonction. À ce titre,
tous les paramètres omis sont considérés comme de type int. Par
exemple, le prototype suivant int foo() ; indique qu’il existe une fonc-
goto label tion nommée foo, qui retourne un entier. Aucune information sur les
label: instruction arguments n’est alors connue. Pour indiquer que la fonction foo
switch (expr) bloc n’attend aucun argument, il faut utiliser la syntaxe suivante int
foo(void) ;. En C++, les prototypes sont complets. Ainsi int foo() ; indi-
que que la fonction ne prend aucun paramètre.
Le couple goto/label permet de réaliser des branchements incon-
ditionnels à l’intérieur d’une fonction. Ces branchements peuvent
éventuellement traverser des blocs. L’instruction goto a longtemps 5.1.1 Fonctions d’arité variable
eu mauvaise réputation car elle est soupçonnée de rendre les pro-
grammes difficiles à comprendre et à maintenir. Toutefois, la ges- Il est parfois nécessaire de définir des fonctions qui attendent un
tion d’erreur est généralement un usage bien admis, comme dans nombre variable d’arguments. On parle alors de fonction d’arité
l’exemple suivant : variable. C++ permet de définir de telles fonctions au moyen de la
notation …. Cette notation indique au compilateur que la fonction
char ✽foo(char ✽s) { attend des arguments optionnels dont les types se sont pas connus.
char ✽res; Elle a donc pour effet de déconnecter le système de type. Considé-
long len = strlen(s); rons par exemple une fonction eval, qui applique un opérateur op à
une liste d’entiers strictement positifs passée en paramètre (la fin de
if(!(res = malloc(len))) la liste de paramètres sera marquée par la valeur 0). Cette fonction
goto end; acceptera les quatre opérateurs suivants : +, –, / et ✽. Ainsi :

end:
return res; eval (’+’, 1, 2, 3, 4, 0) → 10
} eval (’✽’, 2, 3, eval(’–’, 10, 5, 2, 0), 0) → 18

Toute reproduction sans autorisation du Centre français d’exploitation du droit de copie est strictement interdite.
H 3 078 − 8 © Techniques de l’Ingénieur, traité Informatique

tiwekacontentpdf_h3078 v1 Ce document a ete delivre pour le compte de 7200038948 - universite de limoges // karim ZIBANI // 164.81.14.58
Ce document a ete delivre pour le compte de 7200038948 - universite de limoges // karim ZIBANI // 164.81.14.58

________________________________________________________________________________________________________________________ LANGAGE C++

Puisque la fonction eval attend un paramètre obligatoire de type la fonction pour chaque appel de fonction). Cette zone mémoire est
char suivi d’un nombre variable d’entiers, son prototype est : nommée « bloc d’activation ». Elle est détruite dès le retour de la
fonction.

eval(char op, …) ; Comme les variables locales sont conceptuellement placées dans
les blocs d’activation, la politique de gestion des blocs conditionne
l’usage des variables locales. En C++, comme un nouveau bloc est
La lecture des paramètres optionnels se fait au moyen de macros alloué par invocation, d’un appel à l’autre, les variables locales (par-
prédéfinies dans le fichier stdarg.h : va_list, va_start, va_arg et va_end. fois nommées variables automatiques) « perdent » leur valeur.
Ainsi la fonction eval peut être définie telle que dans l’encadré 1. L’allocation dynamique des blocs d’activation permet l’emploi de
fonctions récursives car ils permettent de gérer plusieurs appels en
attente pour une même fonction. Une définition récursive est une
5.1.2 Paramètre par défaut
définition de fonction qui fait usage d’elle-même dans son corps.
Par exemple, la fonction exp définie ci-après calcule la factorielle
Les initialisations par défaut des paramètres formels permettent, d’un nombre au moyen de la définition récursive xn = x ✽ xn – 1 :
dans certains cas, de se passer de fonctions d’arité variable. Les ini-
tialisations par défaut permettent l’omission d’un ou de plusieurs
paramètres effectifs. Tout se passe alors comme si les paramètres double exp(double x, int n) {
omis étaient remplacés, appel par appel, par la valeur qui constitue if (n == 0)
l’initialisation par défaut. À titre d’exemple, considérons la fonction return 1.;
stringToInt qui convertit une chaîne de caractères en nombre entier. else
Cette fonction accepte un second paramètre optionnel qui indique la return x ✽ exp(x, n – 1);
base utilisée par la chaîne de caractères. Par défaut, c’est la base 10 }
qui est utilisée. Ainsi, il serait possible de définir stringToInt sans
fournir de valeur pour ce second argument : Les appels récursifs peuvent être « croisés » comme dans l’exem-
ple suivant :
Parution : août 2003 - Ce document a ete delivre pour le compte de 7200038948 - universite de limoges // karim ZIBANI // 164.81.14.58

int stringToInt(char ✽str,int base = 10) {


int res = 0; int odd(int);
int even(int);
while (✽str) { int odd(int n) {
res (res ✽ base) + (✽str++ – ’0’); if (n < 1)
} return 1;
return res; else
} return even(n – 1);
}

Encadré 1 – Fonction eval int even(int n) {


if (n < 1)
#include <iostream> return 0;
#include <stdarg.h> else
return odd(n – 1);
}
int eval(char op, …) {
int sum, num;
Enfin, il faut noter que tous les langages de programmation
va_list argl; n’allouent pas dynamiquement un nouveau bloc par appel de fonc-
va_start(argl, op); tion. Par exemple, le langage Fortran’77 associe un bloc unique par
fonction. En d’autres termes, toutes les invocations d’une même
if(!(sum=va_arg(argl, int))) goto end;
fonction partagent la même zone mémoire. Ce langage n’offre donc
while(num=va_arg(argl, int)) { pas de support pour la récursivité.
switch(op) {
case ’+’ : sum += num; break;
case ’–’ : sum –= num; break; 5.3 Fonctions inline
case ’/’ : sum /= num; break;
case ’✽’ : sum ✽= num; break;
Le rôle premier des fonctions est de rassembler les instructions
default: cerr << "Error," << op << "\n";
afin d’améliorer la lisibilité du code source. À ce titre, elles factori-
}
sent les traitements en évitant la redondance des codes sources.
}
Toutefois, les fonctions ont un coût en temps pendant l’exécution.
end: En effet, considérons, par exemple, la fonction abs du programme
va_end(argl); suivant :
return sum;
} double abs(double v) {
return v < 0 ? –v : v;
}
5.2 Blocs d’activation et récursion {
double x;
À chaque appel de fonction est associée une nouvelle zone …
mémoire qui sert à sauvegarder toutes les informations propres à abs(x + 1.);
l’invocation (paramètres d’appel et variables locales, position dans }

Toute reproduction sans autorisation du Centre français d’exploitation du droit de copie est strictement interdite.
© Techniques de l’Ingénieur, traité Informatique H 3 078 − 9

tiwekacontentpdf_h3078 v1 Ce document a ete delivre pour le compte de 7200038948 - universite de limoges // karim ZIBANI // 164.81.14.58
Ce document a ete delivre pour le compte de 7200038948 - universite de limoges // karim ZIBANI // 164.81.14.58

LANGAGE C++ _________________________________________________________________________________________________________________________

Il a été jugé pertinent de définir une fonction nommée abs plutôt entiers dans la fonction swap mais plutôt des pointeurs sur des
que d’écrire la comparaison à 0 dans le code source pour une raison entiers. Ainsi une solution convenable est :
de lisibilité. La définition de fonction a, en quelque sorte, permis de
nommer le test. Si la lisibilité du programme a été améliorée, ses per-
formances ont, a priori, été dégradées. En effet, sauf si le compilateur // solution basée sur un pointeur
déploie des optimisations idoines, appeler une fonction « coûte » en void swap2(int ✽x, int ✽y) {
temps d’exécution car l’appel nécessite de sauvegarder un contexte int t = ✽x;
et de le restaurer au retour de la fonction (§ 5.2). Puisque dégradation ✽x = ✽y;
de performances il y a, il convient de se demander s’il était judicieux ✽y = t;
de définir la fonction abs. Cet exemple est particulièrement criant car }
le traitement effectué dans la fonction étant très simple, le temps
nécessaire pour appeler la fonction est probablement beaucoup plus

important que celui nécessaire pour calculer son corps. Il existe deux
swap2(&X, &Y);
solutions pour allier lisibilité et performance :
— les macrofonctions : elles ont été présentées au paragraphe
2.2. Comme l’appel à une macrofonction est expansé pendant la Si cette solution est correcte, elle a le désavantage de faire appa-
compilation, il n’y a aucun coût additionnel imposé à l’exécution. raître des pointeurs alors que la fonction swap n’en fait pas usage
Néanmoins, remplacer une fonction par une macrofonction pose en tant que tels. Ainsi, pour réaliser des fonctions comme swap, C++
quelques problèmes car les deux sortes de fonctions n’ont pas la préconise l’emploi de passage par référence plutôt que la manipula-
même sémantique : le passage des paramètres diffère. Une macro- tion de pointeurs.
fonction peut poser des problèmes de capture d’identificateurs et
elle ne permet pas d’exprimer des définitions récursives. De plus, il
n’est pas possible d’obtenir l’adresse d’une macrofonction puisque
ce type de fonction n’a pas d’incarnation pendant l’exécution ; // solution basée sur une référence
void swap3(int &x, int &y) {
— les fonctions inline : il s’agit de fonctions classiques dont la
int t = x;
Parution : août 2003 - Ce document a ete delivre pour le compte de 7200038948 - universite de limoges // karim ZIBANI // 164.81.14.58

définition est précédée du mot-clé inline. Cette annotation indique


x = y;
au compilateur qu’il est souhaitable de remplacer l’appel à la fonc-
y = t;
tion par une copie du corps de la fonction. Le compilateur décide en
}
dernier ressort si cette expansion est souhaitable et lorsque l’expan-
sion a lieu, le compilateur prend soin d’éviter tout problème de cap- …
ture d’identificateurs en renommant éventuellement toutes les swap3(X, Y);
variables utilisées dans le corps de la fonction inline. L’annotation
inline permet donc d’éliminer le coût d’appel de la fonction tout en
préservant la lisibilité du code. Le passage par référence est indiqué par l’opérateur & appliqué
C++ préconise l’emploi de fonctions inline par rapport à celui de aux arguments. Lors d’un appel, aucun signe syntaxique ne diffé-
macrofonctions. rencie un argument passé par référence d’un argument passé par
valeur. De la même façon, dans le corps de la fonction, aucun signe
syntaxique ne distingue l’usage d’un paramètre formel passé par
référence d’un paramètre passé par valeur. En terme d’exécution, la
5.4 Passage des arguments version swap3 est strictement équivalente à la version swap2. Ainsi,
les restrictions quant aux valeurs que l’on peut passer à swap3 sont
les mêmes que celles pour l’appel à swap2 : on ne peut passer à
Inspiré de langages tels que Pascal, C++ admet deux modes (voire swap3 que des expressions dont on peut prendre l’adresse au
trois si l’on considère les macrofonctions de CPP) de passage des moyen de l’opérateur & (c’est-à-dire des variables, des éléments de
arguments : le passage par valeur et le passage par référence. Ce tableau ou des champs de structure).
dernier est une sorte d’abstraction du passage de pointeurs. Consi-
dérons l’exemple classique d’une fonction dont l’objet est d’échan-
ger ses deux arguments entiers : la fonction swap. Comment
implanter une telle fonction ? La première solution qui pourrait venir 5.5 Surcharge
à l’esprit est :
La surcharge est un mécanisme de typage qui permet de donner
plusieurs définitions à une unique fonction. La version idoine est
// mauvaise solution choisie en fonction du type des arguments et du prototype de la
void swap1(int x, int y) { fonction. La surcharge peut être statique comme en C++ mais elle
int t = x; peut aussi être dynamique comme, par exemple, dans le langage
x = y; Clos [6]. Certains opérateurs de C++ ont des définitions surchargées.
y = t; Par exemple, lorsque l’on écrit x + 23, de quelle addition s’agit-il ?
} D’une addition entière ou d’une addition flottante ? Tout dépend du
type de la variable x. Si c’est un entier, alors il s’agit d’une addition
… entière ; si c’est un nombre flottant, alors c’est une addition flottante
swap1(X, Y); et le nombre 23 est automatiquement remplacé, pendant la compila-
tion, par le nombre 23.0. Contrairement à de nombreux langages,
C++ permet à toute fonction utilisateur d’être surchargée, c’est-à-
Conformément à la sémantique des appels de fonctions, telle dire d’avoir plusieurs définitions. C’est donc le compilateur qui choi-
qu’elle a été succinctement présentée (§ 5.2), cette solution est erro- sit quelle version appeler en fonction du type statique des expres-
née car les variables x et y ont la même durée de vie que l’activation sions passées en argument à la fonction.
de la fonction swap. Autrement dit, les variables X et Y sont différen- Imaginons par exemple que l’on souhaite implanter une fonction
tes des variables x et y et donc, changer la valeur de x n’a pas d’inci- nommée toInt qui convertit son argument en nombre entier. Avec la
dence sur la variable X. La façon « classique » (du moins en C) de surcharge, on peut définir cette fonction, au cas par cas, en fonction
résoudre ce problème est de ne pas manipuler directement des des types que l’on a à traiter :

Toute reproduction sans autorisation du Centre français d’exploitation du droit de copie est strictement interdite.
H 3 078 − 10 © Techniques de l’Ingénieur, traité Informatique

tiwekacontentpdf_h3078 v1 Ce document a ete delivre pour le compte de 7200038948 - universite de limoges // karim ZIBANI // 164.81.14.58
Ce document a ete delivre pour le compte de 7200038948 - universite de limoges // karim ZIBANI // 164.81.14.58

________________________________________________________________________________________________________________________ LANGAGE C++

#include <stdlib.h> template <<T1>, <T2> …,<Tn>>


#include <ctype.h> <ident>(<ident1>, <ident2>, …, <identn>) {
bloc
int toInt(char c) { }
return isdigit(c) ? c – ’0’ : – 1;
}
Les arguments <T1>, <T2>… sont des paramètres formels de type.
int toInt(double x) { Chacun d’entre eux comprend le mot réservé class suivi d’un iden-
return (int)x; tificateur de type. La version template de notre exemple max précé-
} dent pourrait être :

int toInt(char ✽s) {


return atoi(s); #include <iostream>
}
template <class T> T max(T x, T y) {
Sur un site d’appel tel que toInt(x), le compilateur détermine la return x < y ? y : x;
version de la fonction à appeler en fonction du type de x. Si aucune }
définition n’est compatible avec ce type, alors une erreur sera signa-
lée lors de la compilation. Il n’est pas obligatoire que toutes les défi- void main(int argc, char ✽argv[]) {
nitions d’une fonction surchargée aient le même nombre cout << max(argc, 10);
d’arguments. Par exemple, nous pourrions définir une nouvelle ver- cout << max((double)argc, 10.0);
sion de toInt pour convertir des chaînes exprimées dans une base }
quelconque :
Parution : août 2003 - Ce document a ete delivre pour le compte de 7200038948 - universite de limoges // karim ZIBANI // 164.81.14.58

Dans cette définition, on a indiqué que le type des paramètres for-


#include <stdlib.h>
mels x et y de la fonction est un type commun quelconque et que ce
même type est également le type du résultat de max.
int toInt(char ✽s, int base) {
return strtol(s, 0L, base);
}

En revanche, le type de retour d’une fonction n’est pas pris en 6. Objets


compte pour résoudre une surcharge. Il en résulte qu’il est illégal de
donner deux définitions d’une même fonction qui ne diffèrent que
par leur type de retour.
Le modèle par objets de C++ a été inspiré par un certain nombre
de prédécesseurs. L’influence la plus importante est probablement
due à Simula 67 mais d’autres langages, tels que Smalltalk, ont eu
5.6 Fonction templates un impact non négligeable. La programmation par objets se fait en
C++ par l’intermédiaire de classes. Une classe peut être construite à
partir d’une ou de plusieurs autres classes (on parle d’héritage mul-
tiple). Le langage dissocie la notion de classe et la notion de type (en
Les fonctions surchargées offrent une solution élégante pour particulier parce que plusieurs types peuvent être associés à une
implanter des opérations identiques sur des types différents. Par- classe). Enfin, les méthodes de langages comme Smalltalk existent
fois, non seulement les opérations sont les mêmes mais aussi les en C++ où elles portent la dénomination de fonctions membres vir-
implantations (les corps sont identiques). Ce peut être le cas, par tuelles.
exemple, d’une fonction max qui retourne le plus grand de deux
nombres :
Pour plus de détails, le lecteur est invité à consulter l’article
Conception par objets en C++ [H 3 138].
int max(int x, int y) {
return x < y ? y : x;
}
6.1 Classes
double max(double x, double y) {
return x < y ? y : x;
} Les classes permettent de définir de nouveaux types ou d’étendre des
types déjà existants. Une classe est caractérisée par trois éléments :
— un nom. Il permet de désigner la classe. Cet identificateur de
Au lieu d’utiliser une fonction surchargée pour implanter la fonc- classe permet d’accéder aux types associés. Ces types peuvent être
tion max, une solution utilisant une macrofonction de CPP peut être utilisés dans n’importe quel contexte où un type est attendu. On
préférée. Cette solution ne serait pourtant pas totalement satisfai- peut donc définir des variables dont le type est une classe, des poin-
sante car, comme cela a été vu au paragraphe 5.3, les macrofonc- teurs sur des classes, etc. ;
tions n’ont pas la même sémantique que les fonctions et il convient — des données membres. La terminologie objet a parfois préféré
donc d’être prudent dans leur emploi. l’expression de variable d’instances ou même de champ d’instances.
C++ permet d’exprimer en une seule définition une famille de Toutes désignent la même chose : des données qui sont portées par
fonctions qui diffèrent par le type des arguments qu’elles manipu- les objets. Il est possible de contrôler les droits d’accès à ces données
lent. Ces familles de fonctions, appelées fonctions templates, sont au moyen de trois directives : public, private et protected. Ce contrôle
caractérisées par des paramètres de type. Leur syntaxe est : des droits d’accès implique que plusieurs types puissent être

Toute reproduction sans autorisation du Centre français d’exploitation du droit de copie est strictement interdite.
© Techniques de l’Ingénieur, traité Informatique H 3 078 − 11

tiwekacontentpdf_h3078 v1 Ce document a ete delivre pour le compte de 7200038948 - universite de limoges // karim ZIBANI // 164.81.14.58
Ce document a ete delivre pour le compte de 7200038948 - universite de limoges // karim ZIBANI // 164.81.14.58

LANGAGE C++ _________________________________________________________________________________________________________________________

associés à une classe. En effet, les opérations applicables à une don-


née caractérisent son type. Avec les protections de C++, les opéra- #include "String.h"
tions applicables dépendent du contexte d’utilisation. En
char ✽String::toString() const {
conséquence, le type des données peut varier d’un contexte à l’autre ;
return chars;
— des fonctions membres. Là aussi, la terminologie objet a pro- }
duit des variantes. Souvent, on trouve le terme de méthode à la
place de fonction membre. Les deux terminologies ne recouvrent unsigned int String::Length() const {
toutefois pas exactement les mêmes concepts. Cette différence sera return len;
étudiée au paragraphe 6.5. Tout comme l’accès aux données mem- }
bres, l’accès aux fonctions membres peut être contrôlé au moyen
des directives public, private et protected.
La syntaxe de déclaration des classes est la suivante : Chaque classe définit sa propre portée. Les noms des membres
des classes sont locaux dans la portée de leur classe. Si le nom
d’une variable du niveau de la portée du fichier est réutilisé par un
class <ident> { membre de classe, le nom de cette variable sera masqué à l’inté-
public : rieur de la classe. L’opérateur :: est un opérateur de portée. Il permet
Les données et fonctions membres publiques de désigner des identificateurs de la portée supérieure. La syntaxe
private : class::id désigne l’identificateur id défini dans la portée de la classe
Les données et fonctions membres privées class. Nous l’avons utilisé pour définir les méthodes Length et
protected : toString en dehors de la définition de la classe String.
Les données et fonctions membres protégées
} ■ Classes internes
Il est possible d’imbriquer des classes dans d’autres classes. La
Les données membres sont déclarées comme les champs d’une visibilité da la classe imbriquée est alors limitée à la portée de sa
structure C classique. Les fonctions membres peuvent être définies classe englobante. La visibilité d’une classe locale est réduite à la
Parution : août 2003 - Ce document a ete delivre pour le compte de 7200038948 - universite de limoges // karim ZIBANI // 164.81.14.58

dans la classe ou simplement déclarées au moyen d’un prototype. portée de son bloc ou de sa fonction englobante. Contrairement à
Les données et fonctions membres déclarées dans la section public une classe imbriquée, les fonctions membres d’une classe locale
sont accessibles depuis tous les points du programme. Les données doivent être définies à l’intérieur de la définition de la classe. Les
et fonctions membres déclarées dans la section private ne sont classes imbriquées de C++ se distinguent des classes imbriquées de
accessibles que dans la classe elle-même. Enfin, les données et Java [H 3 088] ou des fermetures des langages fonctionnels [8]
fonctions déclarées dans la section protected peuvent être utilisées parce qu’elles n’ont pas accès aux données membres de leur classe
dans la classe et ses sous-classes (§ 6.4). englobante.
L’exemple suivant contient la définition d’une classe modélisant
des chaînes de caractères que nous caractérisons ici par une lon- ■ Données et fonctions statiques d’une classe
gueur et un tableau de caractères. Les données statiques sont déclarées avec le mot réservé static.
Elles constituent un moyen de partage des données entre les diffé-
rentes instances d’une classe. Par exemple, nous pourrions partager
class String { plusieurs données pour nos chaînes de caractères : en premier lieu,
public : la longueur maximum tolérée pour les chaînes, la variable
unsigned int Length() const; MAX_LENGTH ; en second lieu, le nombre de chaînes créées depuis
char ✽toString() const; le début de l’exécution du programme, la variable count. Les fonc-
private : tions d’une classe peuvent elles aussi être déclarées statiques. Dans
unsigned int len; ce cas, elles ne sont plus invoquées par l’intermédiaire d’une ins-
char ✽chars; tance de la classe et elles ne peuvent référencer que des variables
} également statiques de la classe.

Il a été choisi dans cet exemple de ne pas rendre les données


membres visibles en dehors de la classe. Leurs valeurs peuvent int String::getCount() {
néanmoins être connues par le biais des deux fonctions membres. return count;
L’intérêt de cette démarche est de cacher la représentation interne }
de la classe. La seule chose que l’utilisateur d’une chaîne doit savoir,
c’est qu’il existe deux fonctions toString et Length pour manipuler int main() {
ces données. Il ne doit pas savoir comment la classe est implantée. cout <<String::getCount() <<String::MAX_LENGTH;
Ces fonctions qui ne font que lire une valeur d’une donnée membre }
sont appelées des « fonctions d’accès », ou plus simplement des
« accesseurs ». Une première utilisation de notre classe pourrait
être :
6.2 Surcharge des opérateurs
String s1, s2;
int l;
l = s1.Length(); Une des particularités de C++ (et aussi une de ses sources de
complexité) est de permettre de redéfinir, pour chaque classe, les
opérateurs usuels. Il est ainsi possible de changer la définition des
Fortement influencée par la terminologie de Smalltalk [7], opérateurs tels que l’addition +, l’affectation =, l’accès aux tableaux
l’expression s1.length() est souvent désignée comme l’« envoi du [], l’appel de fonctions (), etc. Illustrons cette possibilité par deux
message Length à l’objet s1 ». Les données dont le type est une exemples : la surcharge de l’opérateur + pour réaliser la concaténa-
classe sont appelées des instances de cette classe. Après avoir vu la tion de deux chaînes de caractères et la surcharge de l’opérateur []
définition de la classe String, voici maintenant l’implantation des pour tester la validité d’un accès dans une chaîne. Ces deux exem-
fonctions membres : ples reposent sur la classe String présentée au paragraphe 6.1.

Toute reproduction sans autorisation du Centre français d’exploitation du droit de copie est strictement interdite.
H 3 078 − 12 © Techniques de l’Ingénieur, traité Informatique

tiwekacontentpdf_h3078 v1 Ce document a ete delivre pour le compte de 7200038948 - universite de limoges // karim ZIBANI // 164.81.14.58
Ce document a ete delivre pour le compte de 7200038948 - universite de limoges // karim ZIBANI // 164.81.14.58

________________________________________________________________________________________________________________________ LANGAGE C++

Par la suite, il faut donner une implantation pour cette surcharge


class String { d’opérateur :

String &operator+(String &);
String &operator+(char ✽); ostream &operator<<(ostream &os, String &s) {
char &operator[](int); os << s.chars;
… return os;
} }

La surcharge d’un opérateur est syntaxiquement proche de la Avec cette version, dès que l’expression à droite de l’opérateur <<
déclaration d’une fonction. Comme pour les fonctions, il est possi- sera de type String, les caractères contenus dans cette chaîne seront
ble de donner plusieurs définitions pour un même opérateur. C’est écrits sur le flot de sortie.
le cas dans notre exemple précédent, où nous avons donné deux
définitions pour +.
Le prototype de l’opérateur [] est char &operator[](int);. Il est impé-
ratif que cet opérateur retourne une référence sur un caractère et
6.3 Construction et destruction
non pas simplement un caractère car les expressions formées à par- des instances
tir de l’opérateur [] doivent pouvoir apparaître en partie gauche
d’une affectation (les accès dans des tableaux sont des lvalues, voir
§ 4.1.8). Les instances de classes peuvent avoir différentes durées de vie :
— une durée de vie globale : ces objets ont la même durée de vie
Les opérateurs surchargés s’utilisent comme n’importe quel autre
que le programme. Ils sont alloués dans le tas lors de l’initialisation
opérateur, comme cela est visible dans l’exemple suivant :
du programme :
Parution : août 2003 - Ce document a ete delivre pour le compte de 7200038948 - universite de limoges // karim ZIBANI // 164.81.14.58

String &String::operator+(String &s) { static int x = 9;


chars = (char ✽)realloc(chars, len + s.len + 1); int y = 10;
strcpy(&chars[len], s.chars); int tab[10];
len += s.len; char ✽s = "toto";
return ✽this;
} class point {
String &String::operator+(char ✽s) { public:
int slen = strlen(s); int x, y;
chars = (char ✽)realloc(chars, len + slen + 1); }
strcpy(&chars[len], s);
len += slen; class point p;
return ✽this;
}
— une durée de vie liée à celle d’un bloc. Les objets sont alloués
char &String::operator[](int o) { en pile et ont la même durée de vie que le bloc qui les définit :
if (o >= 0 && o < len)
return chars[o];
else foo() {
throw("index out of bound"); int x;
} char y[10];
class point p;
int main() {
String s; p.x = 8;
}
s = s + "foo";
s[2] = ’_’;
} — une durée de vie dynamique. Ils sont alloués dans le tas au
moyen d’appels à une fonction de la bibliothèque telle que malloc
ou au moyen de l’opérateur new :
Les fonctions amies servent à divulguer des données privées à
certaines autres classes. Cette fonctionnalité est notamment utile
lorsqu’il s’agit de surcharger une méthode définie dans une autre foo() {
classe. C’est par exemple le cas de la surcharge de l’opérateur de int x;
flot <<. Cet opérateur prend en argument gauche un ostream. Son char ✽y = (char ✽)malloc(sizeof(char ✽) ✽10);
type de retour est ostream& afin de pouvoir enchaîner les affichages class point ✽p = new point;
en cascade. La surcharge d’un opérateur tel que << doit pouvoir
accéder même aux membres privés d’une classe. Pour cela, il p–>x = 8;
convient de déclarer, dans la classe String, que la classe ostream qui }
contient la définition de l’opérateur est une « amie » de la classe
String. Cette indication est donnée au compilateur au moyen du ■ Constructeurs
mot-clé friend. Ainsi, dans la classe String, il faut ajouter une décla-
ration telle que : C++ permet d’initialiser, c’est-à-dire de fournir des valeurs aux
données membres dès la construction des instances. Imaginons,
pour l’exemple, l’allocation et l’initialisation d’une instance de la
classe point représentant l’origine des points (le point de coordon-
friend ostream &operator<<(ostream &, String &); nées <0,0>). Rappelons que ni malloc ni new n’initialisent les zones

Toute reproduction sans autorisation du Centre français d’exploitation du droit de copie est strictement interdite.
© Techniques de l’Ingénieur, traité Informatique H 3 078 − 13

tiwekacontentpdf_h3078 v1 Ce document a ete delivre pour le compte de 7200038948 - universite de limoges // karim ZIBANI // 164.81.14.58
Ce document a ete delivre pour le compte de 7200038948 - universite de limoges // karim ZIBANI // 164.81.14.58

LANGAGE C++ _________________________________________________________________________________________________________________________

allouées. Avec les outils à notre disposition, une première version Le constructeur est invoqué après que l’objet a été alloué. S’il
de notre programme ressemblerait probablement à : requiert des arguments, ils doivent être fournis sur le lieu de la créa-
tion de l’objet. Lorsqu’une classe définit un constructeur par défaut
{ (c’est-à-dire un constructeur qui n’attend aucun paramètre), le com-
class point org; pilateur produit un code pour invoquer ce constructeur si aucun
autre constructeur n’est utilisé pour cet objet. Si une classe a des
org.x = 0; constructeurs mais pas de constructeur par défaut, les arguments
org.y = 0; des constructeurs doivent être fournis chaque fois qu’une instance
} est créée.
Ce code est fastidieux (et plus particulièrement pour des objets ■ Initialisation des données membres
qui ont plus que deux données membres) et présente l’inconvénient
d’imposer que les données membres soient publiques afin d’être Si une classe cla contient des données membres qui sont des instan-
affectées depuis l’extérieur de la classe. ces d’autres classes, ces données membres sont initialisées dans
l’ordre de leur déclaration, avant que l’instance de cla ne soit initialisée.
Une notation plus courte, mais équivalente, est : Les arguments sont passés aux constructeurs des membres instances
des classes au moyen d’une liste d’initialisation des membres, consti-
{ tuée de paires nom/valeur séparées par des virgules. Par exemple :
class point orig = {0,0};
}
point::point(int _x, int _y) : x(_x), y(_y) {
Une version plus correcte serait alors de placer l’initialisation de
}
notre objet dans une fonction membre de la classe point. C++ offre un
mécanisme généralisant ces initialisations : les constructeurs. Un
constructeur est une pseudo-fonction membre d’une classe qui porte
le même nom que la classe. Il ne s’agit pas de fonctions standards La liste d’initialisation des membres suit le prototype du construc-
teur et est séparée par :. Chaque membre ne peut être nommé
Parution : août 2003 - Ce document a ete delivre pour le compte de 7200038948 - universite de limoges // karim ZIBANI // 164.81.14.58

parce que les constructeurs ont une syntaxe particulière et parce


qu’on ne précise jamais le type de retour des constructeurs. Ces der- qu’une fois dans la liste. La liste d’initialisation des membres
niers sont invoqués automatiquement (le code d’invocation du cons- n’apparaît que dans la définition du constructeur. Elle ne peut pas
tructeur est automatiquement produit par le compilateur). Dans notre être spécifiée dans sa déclaration. C++ réalise l’exécution d’un cons-
exemple des points, un constructeur pourrait être défini comme : tructeur en deux phases : l’initialisation et l’affectation. Quand le
corps du constructeur est vide, il n’y a pas de phase d’affectation.
class point {
■ Destructeurs
int x, y;
public: Symétriquement à l’initialisation (construction) des objets, il est
point(int, int); possible de définir des destructeurs d’objets. Ces destructeurs sont
}; automatiquement invoqués lorsqu’un objet est détruit (soit par un
appel explicite à delete, soit parce qu’il était alloué en pile et que
point::point(int _x, int _y){ l’exécution quitte le bloc définissant l’objet). La syntaxe des destruc-
x = _x; teurs est très proche de celle des constructeurs. Il suffit pour un des-
y = _y; tructeur de faire précéder le nom de la classe par le caractère ~. Voici
} un exemple de destructeur :

L’allocation d’une instance peut alors être :


#include <iostream>
{
… class point {
class point ✽p = new point (0,0); int x, y;
… public:
} $_~$point();
};
On peut spécifier des paramètres par défaut pour les construc- point::$_~$point() {
teurs. Ainsi, il est permis d’écrire : cout << "destroying point <" << x << "," << y << ">";
}
class point {
public:
point(int = 0, int = 0); La logique des destructeurs suit celle des constructeurs, c’est-à-
int x, y; dire que les destructeurs sont récursivement invoqués si des don-
}; nées membres sont des instances de classe qui ont elles-mêmes
des destructeurs définis.
point::point(int _x, int _y) {
x = _x;
y = _y;
} 6.4 Héritage
{ Comme nous l’avons préalablement vu, C++ est un langage par
… objets à base de classes. Jusqu’à présent, les classes ont été définies
class point ✽p = new point (); indépendamment de toute hiérarchie. Nous allons voir maintenant
… qu’il est possible de définir des classes par dérivation, c’est-à-dire par
} extension de classes déjà existantes. C’est ce qu’on nomme l’héritage.

Toute reproduction sans autorisation du Centre français d’exploitation du droit de copie est strictement interdite.
H 3 078 − 14 © Techniques de l’Ingénieur, traité Informatique

tiwekacontentpdf_h3078 v1 Ce document a ete delivre pour le compte de 7200038948 - universite de limoges // karim ZIBANI // 164.81.14.58
Ce document a ete delivre pour le compte de 7200038948 - universite de limoges // karim ZIBANI // 164.81.14.58

________________________________________________________________________________________________________________________ LANGAGE C++

6.4.1 Classes dérivées : héritage simple


#include <iostream>
class point {
Une classe dérivée se définit presque comme une classe normale. public:
Il faut juste ajouter un peu de syntaxe entre le nom de la classe et point (int, int);
l’accolade ouvrante pour dire que la classe déclarée est construite int x, y;
par dérivation d’une classe déjà existante (classe de base). Prenons };
l’exemple d’une classe de points en trois dimensions : class point3d : public point {
public:
point 3d(int, int, int);
int z;
class point3d : public point {
};
int z; point::point(int _x, int _y)
: x(_x), y(_y) {
};
cout <<"point(int, int)\n";
}
La seule différence réside donc dans l’en-tête de la classe. On point3d::point3d(int _x, int _y, int _z)
accède aux membres hérités exactement comme s’ils étaient des : z(_z), point(_x, _y) {
membres directs de notre nouvelle classe. Pour nos point3d, on cout <<"point3d(int, int, int);\n";
accède aux champs x et y comme s’il s’agissait de la classe point : }
main() {
point3d p(1, 2, 3);
cout <<"<"<<p.point::x<<","<<p.y<<","<<p.z<<">\n";
main(point3d &p) { }
Parution : août 2003 - Ce document a ete delivre pour le compte de 7200038948 - universite de limoges // karim ZIBANI // 164.81.14.58

printf("<%d, %d, %d>\n", p.x, p.y, p.z);


} produit l’affichage :

Bien que l’on puisse accéder à ces membres comme s’ils étaient point(int, int);
des membres de la classe dérivée, ils conservent leur appartenance point3d(int, int, int);
à la classe de base. On peut aussi y accéder au moyen de l’opérateur <1,2,3>
de portée de classe, l’opérateur ::. Par exemple :

6.4.3 Héritage multiple


main(point3d &p) {
printf("<%d, %d, %d>\n", p.point::x, p.y, p.z); Une classe peut être dérivée de plusieurs classes. Dans ce cas, on
} parle d’héritage multiple. Illustrons cette possibilité par un exemple.
Considérons les classes des Point et des Color :

Dans la plupart des cas, l’utilisation de l’opérateur de portée est


inutile car le compilateur trouve le membre voulu sans aide lexicale class Point {
supplémentaire. Néanmoins, cette aide devient nécessaire si un private:
nom de membre hérité est réutilisé dans la classe dérivée. Le mem- char ✽ident;
bre hérité est masqué si son nom est réutilisé à l’intérieur de la public:
classe dérivée, exactement comme quand un identificateur local int x, y;
réutilise un nom de variable défini au niveau de la portée englo- char ✽getIdent();
bante. Dans ces deux cas, la résolution du nom s’effectue avec l’ins- };
tance de la portée la plus proche. class Color {
private:
char ✽ident;
public:
6.4.2 Initialisation des classes dérivées unsigned char red, green, blue;
char ✽getIdent();
};
Lors de l’initialisation d’une classe dérivée, l’instance commence
par être initialisée comme si elle était une instance de la classe de
base. La liste d’initialisation des membres est utilisée pour passer Étudions maintenant une implantation pour des points colorés.
des arguments à un constructeur d’une classe de base. On écrit le Une première version pourrait être:
nom de la classe de base suivi de sa liste d’arguments entre paren-
thèses (la liste d’initialisation des membres ne peut apparaître que
class ColorPoint {
dans la définition du constructeur et non dans sa déclaration). Il
public:
n’est pas nécessaire d’inscrire dans la liste d’initialisation des mem-
Point p;
bres une classe de base qui ne définit pas de constructeur ou qui
Color c;
définit un constructeur qui n’attend pas d’argument explicite. Ainsi,
};
le programme :

Toute reproduction sans autorisation du Centre français d’exploitation du droit de copie est strictement interdite.
© Techniques de l’Ingénieur, traité Informatique H 3 078 − 15

tiwekacontentpdf_h3078 v1 Ce document a ete delivre pour le compte de 7200038948 - universite de limoges // karim ZIBANI // 164.81.14.58
Ce document a ete delivre pour le compte de 7200038948 - universite de limoges // karim ZIBANI // 164.81.14.58

LANGAGE C++ _________________________________________________________________________________________________________________________

Cette solution n’est pas forcément satisfaisante car ici, la classe


des ColorPoint n’hérite ni de Point ni de Color. Ainsi il ne sera pas
possible d’envoyer un message d’une de ces deux classes à un
point coloré. Une autre solution met en œuvre l’héritage multiple : Point Color

class ColorPoint : public Point, Color {…};


Point3d ColorPoint

Cette fois-ci, un point coloré est à la fois une instance de Point et


de Color. Il ne peut y avoir de cycle dans le graphe d’héritage. Une ColorPoint3d
classe ne peut pas hériter, directement ou indirectement, d’elle-
même. Lors de la construction des objets, les constructeurs sont
invoqués dans l’ordre d’héritage des classes. L’héritage multiple Figure 2 – Graphe d’héritage virtuel de ColorPoint3d
peut entraîner des ambiguïtés. C’est le cas pour notre classe de
ColorPoint, lorsque l’on utilise la méthode getIdent : laquelle doit-
on utiliser, celle des Point ou celle des Color ? En fait, les compila-
teurs C++ refusent cette ambiguïté. Le programme doit explicite- 6.5 Fonctions membres virtuelles
ment la lever.
Dans la terminologie objet, une méthode est une fonction membre
particulière, dite virtuelle en C++, qui repose sur un mécanisme de
sélection dynamique. Cette opération est souvent nommée typage
6.4.4 Héritage virtuel dynamique ou liaison tardive. On préférera cette deuxième désigna-
tion car la première entraîne une confusion. En effet, la qualification
Parution : août 2003 - Ce document a ete delivre pour le compte de 7200038948 - universite de limoges // karim ZIBANI // 164.81.14.58

typage dynamique désigne souvent les langages de programmation


L’héritage multiple peut conduire à une représentation incorrecte qui repoussent à l’exécution la vérification de la conformité des opé-
des données. Imaginons que nous souhaitions modéliser les rations vis-à-vis des types des arguments. En C++, les méthodes sont
ColorPoint3d qui héritent des ColorPoint et Point3d. déclarées au moyen du mot-clé virtual. Lorsqu’une fonction membre
n’est pas déclarée virtuelle, le compilateur choisit, en fonction du type
statique de l’argument, la version de la fonction à appeler. Lorsque la
fonction membre est virtuelle, c’est à l’exécution, en fonction du type
class Point3d : public Point {…}; réel, connu au moment de l’appel, que la fonction est choisie. Ainsi en
class ColorPoint3d : public Point3d, public ColorPoint {…}; est-il dans l’exemple suivant :

Cette représentation n’est pas bonne car, dans le graphe d’héri- #include <iostream>
tage que nous avons construit, un Point3d hérite par deux branches class point {
différentes de la classe Point (figure 1). protected:
Pour pallier ce problème, C++ propose l’héritage virtuel, marqué int x, y;
par le mot-clé virtual dans la déclaration d’une classe. Une classe public
héritée virtuelle n’existe qu’en un seul exemplaire dans les instances char ✽getClass() {return "point";}
de ses descendantes, même si elle est héritée plusieurs fois. Ainsi : virtual char ✽getClass2() {return "point";}
};
class point3d : public point {
class ColorPoint : public virtual Point, Color {…}; protected:
class Point3d : public virtual Point {…}; int z;
class ColorPoint3d : public Point3d, public ColorPoint {…}; public:
char ✽getClass() {return "point3d";}
virtual char ✽getClass2() {return "point3d";}
Le graphe d’héritage est alors celui de la figure 2. };
int main(int argc, char ✽argv[]) {
point ✽p = new point ();
point3d ✽p3 = new point3d ();
point ✽p2 = (point ✽)p3;
// point point
Point Point Color cout << p–>getClass() << " " << p–>getClass2() << "\n";
// point3d point3d
cout << p3–>getClass() << " " << p3–>getClass2() << "\n";
// point point3d
Point3d ColorPoint cout << p2–>getClass() << " " << p2–>getClass2() << "\n";
}

La dernière ligne d’affichage permet de saisir les différences entre


ColorPoint3d fonction membre et fonction membre virtuelle. La variable p2 est de
type point ✽. Néanmoins, lors de l’exécution, sa valeur est un pointeur
sur un objet de type point3d. Cette valeur est correcte puisque la
Figure 1 – Graphe d’héritage incorrect de ColorPoint3d classe point3d hérite de la classe point. La fonction membre getClass

Toute reproduction sans autorisation du Centre français d’exploitation du droit de copie est strictement interdite.
H 3 078 − 16 © Techniques de l’Ingénieur, traité Informatique

tiwekacontentpdf_h3078 v1 Ce document a ete delivre pour le compte de 7200038948 - universite de limoges // karim ZIBANI // 164.81.14.58
Ce document a ete delivre pour le compte de 7200038948 - universite de limoges // karim ZIBANI // 164.81.14.58

________________________________________________________________________________________________________________________ LANGAGE C++

n’est pas virtuelle. Ainsi, dès la compilation, le compilateur détermine


que c’est la fonction définie dans la classe point (le type de p2) qui template <class Tk, class Ti> Ti HashTable<Tk, Ti>::get(Tk k) {
doit être invoquée. En revanche, comme getClass2 est virtuelle, c’est …
lors de l’exécution, en fonction du type de la valeur pointée par p2, }
que la méthode est choisie. Ici, c’est donc la méthode getClass2 de la
classe point3d qui est sélectionnée à l’exécution.
L’opérateur surchargé << est également template. Ainsi, sa défini-
Il ne faut pas confondre surcharge et liaison tardive. La première tion pourrait, par exemple, être :
est une opération statique. Autrement dit, c’est le compilateur qui,
après comparaison entre les types des paramètres effectifs et les
divers prototypes déclarés pour une fonction, choisit la version à template <class tk, class ti> ostream &operator
appeler. Il n’y a donc pas de trace qui subsiste lors de l’exécution de <<(ostream &out, HashTables<tk, ti> &f) {
cette opération. La liaison tardive repose, comme nous l’avons vu, cout << "<HashTable" << … << ">";
sur une opération à l’exécution. Enfin, il est bien sûr possible de
return out;
mélanger surcharge et liaison tardive. Dans ce cas, une partie de la
résolution de la sélection de la fonction sera effectuée à la compila- }
tion et l’autre à l’exécution.
Les classes templates ne peuvent pas être directement utilisées
dans des déclarations de variables ou de fonctions. Il faut les instan-
6.6 Classes templates cier, c’est-à-dire associer des types concrets à leurs paramètres de
type. Par exemple, pour créer une variable t1, de type pointeur sur
table de hachage associant des entiers à des chaînes de caractères,
Les classes templates de C++ sont mises à disposition des pro- on pourrait écrire :
grammeurs pour implanter des types paramétriques, c’est-à-dire
des types dont la définition contient des variables qui dénotent
elles-mêmes des types. Les types paramétriques sont utiles en de
Parution : août 2003 - Ce document a ete delivre pour le compte de 7200038948 - universite de limoges // karim ZIBANI // 164.81.14.58

nombreuses circonstances comme par exemple pour implanter des HashTable<int, char ✽> ✽t1 = new HashTable<int, char ✽>();
containers tels que des listes, des tables, etc. Leur syntaxe est :
Le type HashTable<int, char ✽> est concret et il peut être utilisé
dans tout contexte où un identificateur de classe peut être mentionné.
template <class T1, class T2, …> classid {

}

7. Exceptions
Nous limitons le présent exposé au cas où les paramètres d’une
classe template, les arguments T1, T2, sont des types. Une classe
template définit une famille de types qui partagent une implantation Dans une programmation basée sur les types abstraits, les opéra-
commune des fonctions membres. L’instanciation d’une classe tem- teurs sur ces types doivent se prémunir contre des utilisations anor-
plate définit de nouveaux types. Ces types jouent le même rôle que males (par exemple, dépiler une pile vide). Il faut en ce cas avertir
tous les autres types de C++. l’utilisateur que l’opération n’a pas pu être effectuée. Terminer le pro-
Détaillons les classes templates sur un exemple : les tables de gramme (avec par exemple des fonctionnalités comme assert, § 2.4)
hachage. Il s’agit de tables d’associations clé/valeur. L’implantation est souvent trop radical. Donner simplement un code d’erreur,
d’une telle table ne dépend ni du type des valeurs à stocker ni de comme en C, est peu robuste car cela suppose que l’utilisateur teste
leur clé pour les retrouver. Il est donc logique en C++ de les implan- ce code après chaque utilisation d’opérateur. Cela ne favorise pas la
ter au moyen d’une classe template. Pour notre exemple, nous dési- lisibilité du code et est sujet à erreur car il est alors facile d’oublier de
gnerons par Tk le type des clés et par Ti le type des valeurs. faire le contrôle. Pour cette raison, C++ propose une solution diffé-
rente en introduisant les exceptions. Cela consiste à abandonner
l’exécution normale du programme et à permettre à l’utilisateur
d’effectuer les actions appropriées. Cette dernière approche présente
template <class Tk, class Ti> class HashTable {
aussi l’avantage de localiser les traitements de situations normales et
… de pallier les déficiences d’un utilisateur désinvolte en ne reprenant
HashTable(int); un traitement normal qu’après la prise en compte de l’erreur.
void put (Tk, Ti ✽);
Ti ✽get(Tk);

template <class tk, class ti> friend ostream& operator<< 7.1 Lever des exceptions
(ostream &, HashTable<tk, ti>&);
};
Pour simplifier cet exposé sur les exceptions, supposons une
classe implantant des piles avec des fonctionnalités traditionnelles.
Que faire, par exemple, lorsque l’on tente de dépiler un élément
Les fonctions membres d’une classe template sont des fonctions
d’une pile vide ?
templates (§ 5.6). Ainsi les définitions des fonctions membres put et
get ressembleront à :
char stack::pop() {
if (top == 0) {
template <class Tk, class Ti> // que faire ?
void HashTable<Tk, Ti>::put(Tk k, Ti i) { }
… return tab[– –top];
} }

Toute reproduction sans autorisation du Centre français d’exploitation du droit de copie est strictement interdite.
© Techniques de l’Ingénieur, traité Informatique H 3 078 − 17

tiwekacontentpdf_h3078 v1 Ce document a ete delivre pour le compte de 7200038948 - universite de limoges // karim ZIBANI // 164.81.14.58
Ce document a ete delivre pour le compte de 7200038948 - universite de limoges // karim ZIBANI // 164.81.14.58

LANGAGE C++ _________________________________________________________________________________________________________________________

Les exceptions sont présentes en C++ pour traiter ce genre de La notation catch(…) permet de spécifier des clauses qui intercep-
situation. Elles permettent de signaler une erreur en détruisant les tent toutes les exceptions. Enfin, si aucune clause catch n’intercepte
objets locaux et en sautant directement à un morceau de code que l’exception, celle-ci est de nouveau levée (impliquant donc destruc-
l’utilisateur est obligé de fournir. Lever une exception se fait au tion des objets locaux et sortie de la fonction).
moyen de l’instruction throw <expr>. Ainsi, on pourrait modifier Le code exécuté dans une clause catch n’est plus dans la portée
notre classe de pile en : du bloc try. En particulier, s’il lève une exception, cette dernière
devra être interceptée par un bloc try englobant. Ainsi, l’affichage
main:9 est produit par le code suivant :

class stack {
public: #include <stdio.h>
class empty : public exception {};
char pop () { int g(int x) {
if (top == 0) { if (x > 0) throw(x);
throw empty(); return – x;
} }
return tab[– –top];
}; void f(int x) {
… try {
}; g(x);
} catch(int v) {
throw(g(x-1));
}
Lorsque l’instruction throw est exécutée, un objet temporaire est }
construit (par appel du constructeur par défaut de stack::empty sur
cet exemple). Tous les objets construits dans la fonction sont
Parution : août 2003 - Ce document a ete delivre pour le compte de 7200038948 - universite de limoges // karim ZIBANI // 164.81.14.58

int main() {
détruits et l’exécution de la fonction est abandonnée. Les exceptions
try {
sont traditionnellement représentées par des classes (dérivées de la
f(10);
classe standard exception) mais il est possible d’utiliser une valeur
} catch (int v) {
de n’importe quel type. En particulier, nous aurions pu utiliser dans
printf("main: %d\n", v);
notre exemple une chaîne de caractères représentant un message
}
d’erreur. La hiérarchie des exceptions standards a pour racine la
}
classe exception et se subdivise en deux grandes catégories :
— logic_error pour les exceptions qui correspondent à des
erreurs de programmation (violation de préconditions, etc.) ;
— runtime_error pour les exceptions qui correspondent à des
erreurs uniquement détectables à l’exécution (overflow_error, etc.). 8. Interopérabilité avec C
et les bibliothèques
7.2 Intercepter les exceptions
C et C++ partagent le même modèle mémoire (gestion mémoire
explicite à base de pointeurs) et le même modèle fonctionnel (blocs
d’activation, § 5.2). Ainsi, C et C++ sont totalement compatibles et
Les blocs try/catch constituent la structure de contrôle des excep- les deux langages peuvent échanger des structures de données et
tions. Les instructions susceptibles de lever des exceptions doivent appeler mutuellement leurs fonctions sans aucun ralentissement.
être encapsulées dans la partie try. Les exceptions qui ont pu être Cette interopérabilité est un atout fort de C++ car les nombreux
levées sont traitées dans la suite des clauses catch : bibliothèques et développements faits autour de C peuvent être uti-
lisés presque directement en C++. La seule précaution à prendre
vient du codage des noms réalisé par les compilateurs C++. En effet,
à cause de la surcharge statique, les compilateurs C++ encodent
try { généralement le nom des fonctions en utilisant les identificateurs
instr0 donnés par les utilisateurs et le type des arguments. Cette opération
} est nommée en anglais name mangling. Les compilateurs C n’ont
catch (exc1) {instr1} pas recours à cette technique. Ainsi, lorsque l’on souhaite utiliser
catch(exc2) {instr2} une fonction ou une variable C depuis un programme C++, il faut
indiquer au compilateur de ne pas appliquer le name mangling sur

les identificateurs C. Cela se fait au moyen d’une déclaration particu-
lière dont la syntaxe est :

Lorsqu’une exception est levée, le contrôle passe dans la suite de


clauses catch qui sont examinées dans l’ordre textuel. La première extern "C" {
clause pour laquelle le type de l’exception est compatible avec le

type du paramètre est sélectionnée et son corps est exécuté. Une
clause catch(T1 e) ou catch(T1 &e) intercepte une exception de type }
T2 si :
— T1 et T2 sont de même type ;
Pour l’exemple, imaginons que l’on souhaite utiliser une fonction
— T2 est une classe dérivée publique de T1 ; C nommée getenv prenant en paramètre une chaîne de caractères et
— T2 est un pointeur sur une classe C2 dérivée publique d’une retournant une valeur du même type. Alors il faudra écrire un pro-
classe C1, et T1 est un pointeur sur C1. gramme ressemblant au suivant :

Toute reproduction sans autorisation du Centre français d’exploitation du droit de copie est strictement interdite.
H 3 078 − 18 © Techniques de l’Ingénieur, traité Informatique

tiwekacontentpdf_h3078 v1 Ce document a ete delivre pour le compte de 7200038948 - universite de limoges // karim ZIBANI // 164.81.14.58
Ce document a ete delivre pour le compte de 7200038948 - universite de limoges // karim ZIBANI // 164.81.14.58

________________________________________________________________________________________________________________________ LANGAGE C++

#include <iostream> 9. Conclusion


extern "C" { C++ est un langage imposant, complexe et d’un apprentissage
char ✽getenv(char ✽); long et difficile. Il possède beaucoup de constructions et met en
} œuvre de nombreux concepts. Certains pourront même y voir une
multitude de « verrues » agglutinées les unes aux autres. Beaucoup
de constructions de C++ sont difficiles à bien cerner parce qu’elles
int main(int argc, char ✽argv[]) {
mettent en œuvre des concepts complexes. Par exemple, l’héritage
cout << getenv(argv[1]) << "\n"; multiple de C++ complique grandement le modèle objet et a des
} répercussions sémantiques dans de nombreux recoins du langage
(méthodes, constructeurs, destructeurs, conversion de types, etc.).
C++ n’est pas seulement un langage difficile à bien maîtriser, c’est
aussi un langage difficile à bien implanter ! Les temps où les compi-
On peut constater que la fonction C getenv est, une fois déclarée lateurs C++ n’implantaient qu’un sous-ensemble du langage et avec
dans la clause extern "C", utilisée comme n’importe quelle fonction des interprétations parfois assez divergentes de telle ou telle
C++ et il n’y a aucune précaution à prendre pour l’appeler. construction ne sont pas si éloignés. La situation est maintenant
En plus de pouvoir utiliser, quasiment sans effort, toutes les bien meilleure.
bibliothèques conçues pour C, C++ dispose de bibliothèques parti- On peut s’interroger sur la nécessité de la complexité de C++. Très
culières. La plus fréquemment utilisée est iostream qui contient la probablement, elle ne s’explique que par la volonté d’allier pro-
définition des opérateurs << et >>. Cette bibliothèque pallie deux grammation de haut niveau (couche objet, exceptions, etc.) et pro-
lacunes des entrées/sorties de C : elle est robuste vis-à-vis des types grammation efficace. Ce mariage, assez incertain, impose parfois
et elle est extensible. Autrement dit, l’utilisateur peut ajouter ses des contorsions qui peuvent sembler arbitraires mais qui sont en
propres formats d’impression et de lecture. fait des nécessités d’implantation. On peut assez légitimement
La Standard Template Library (STL) [9] est une autre bibliothè- reprocher à C++ de faire transparaître, dans sa conception, des con-
Parution : août 2003 - Ce document a ete delivre pour le compte de 7200038948 - universite de limoges // karim ZIBANI // 164.81.14.58

que importante pour les programmeurs C++. Elle propose un sidérations d’implantation mais il faut alors lui reconnaître l’indénia-
ensemble de structures de données génériques ainsi que des algo- ble mérite d’avoir abordé de front le problème de l’efficacité des
rithmes pour les manipuler. Elle met en avant des composants qui langages de haut niveau et d’être parvenu à un équilibre que peu
permettent d’allier généricité et performance. La STL ayant été ont atteint. Pour cette raison, en dépit des attaques qu’il a subi ces
adoptée par le comité ISO C++, elle fait dorénavant quasiment par- dernières années, notamment de la part de Java ou maintenant de
tie du langage C++. C#, il est peu probable de le voir s’éteindre dans un avenir proche.

Références bibliographiques

Références [4] KERNIGHAN (B.) et RICHIE (D.). – The C Pro- [7] GOLDBERG (A.) et ROBSON (D.). – Smalltalk-
gramming Language. Prentice Hall (1978). 80 : The Language and Its Implementation.
Addison-Wesley (1983).
[1] STROUSTRUP (B.). – The C++ Programming [5] HARBISSON (S.P.) et STEELE (G.L.). – C a
Language. Addison-Wesley (1991). Reference Manual. Prentice Hall, Software
[8] QUEINNEC (C.). – Lisp In Small Pieces. Cam-
Series (1991).
[2] STROUSTRUP (B.). – The Design and Evolu- bridge University Press (1996).
tion of C++. Addison-Wesley (1994). [6] BOBROW (D.), DE MICHIEL (L.), GABRIEL (R.),
KEENE (S.), KICZALES (G.) et MOON (D.). – [9] LEE (M.) et STEPANOV (A.). – The Standard
[3] LIPPMAN (S.). – Inside The C++ Object Model. Common lisp object system specification. Template Library. http://www.cs.rpi.edu/
Addison-Wesley (1996). Special issue, Sigplan Notices, 23 (1988). projects/STL/htdocs/stl.html

Toute reproduction sans autorisation du Centre français d’exploitation du droit de copie est strictement interdite.
© Techniques de l’Ingénieur, traité Informatique H 3 078 − 19

tiwekacontentpdf_h3078 v1 Ce document a ete delivre pour le compte de 7200038948 - universite de limoges // karim ZIBANI // 164.81.14.58

Vous aimerez peut-être aussi