Explorer les Livres électroniques
Catégories
Explorer les Livres audio
Catégories
Explorer les Magazines
Catégories
Explorer les Documents
Catégories
: H3078 V1
Langage C++
Date de publication :
10 août 2003
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.
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)
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
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.
#include <file>
2. Macroexpanseur CPP ou
#include "file"
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
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>
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").
#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
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
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)
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
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
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
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 ; .
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
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 :
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
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
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
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
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
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
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.
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
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
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
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
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
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 :
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
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";
}
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
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
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 :
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
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