Está en la página 1de 522

Cours de C/C++

Christian Casteyde

Cours de C/C++ par Christian Casteyde Copyright 2005 Christian Casteyde Historique des versions

Version 2.0.2 01/01/2007 Revu par : CC Conversion du document en XML pour permettre lutilisation des feuilles de style XML et des outils de formatage XML-FO. Corre Version 2.0.1 26/02/2006 Revu par : CC Corrections orthographiques. Correction sur la gestion des exceptions dans les constructeurs par les constructeurs try. Prcisions su Version 2.0.0 03/07/2005 Revu par : CC Ajout dune introduction sur les langages de programmation et refonte de la premire partie. Ajout des dnitions des termes lors d Version 1.40.6 16/05/2004 Revu par : CC Correction de lexemple dallocation dynamique de mmoire en C. Correction de lexemple de fonction nombre variable de param Version 1.40.5 14/06/2003 Revu par : CC Correction de lallocation dynamique de tableaux plus dune dimension. Version 1.40.4 21/09/2002 Revu par : CC Correction de lexemple de recherche sur les chanes de caractres. Ajout des initialiseurs C99. Prcisions sur la portabilit des type Version 1.40.3 12/05/2002 Revu par : CC Nombreuses corrections orthographiques. Quelques corrections et prcisions. Clarication de quelques exemples. Version 1.40.2 26/01/2001 Revu par : CC Corrections orthographiques. Ajout dun lien sur les spcications Single Unix de lOpen Group. Version 1.40.1 09/09/2001 Revu par : CC Corrections orthographiques. Prcisions sur les optimisations des oprateurs dincrmentation et de dcrmentation postxs et pr Version 1.40.0 30/07/2001 Revu par : CC Version nale. Rorganisation partielle de la premire partie. Scission du chapitre contenant les structures de contrle et les dniti Version 1.39.99 24/06/2001 Revu par : CC Description des locales standards. Prcision sur linitialisation des variables lors de leurs dclarations. Prcision sur les droits dacc Version 1.39.4 27/05/2001 Revu par : CC Description des ux dentre / sortie de la bibliothque standard. Modication de la prsentation sommaire des ux dans le chapitre Version 1.39.3 03/05/2001 Revu par : CC Description des algorithmes de la bibliothque standard. Version 1.39.2 22/04/2001 Revu par : CC Description des conteneurs de la bibliothque standard. Ajout dune traduction de la licence FDL. Suppression des symboles &colo Version 1.39.1 05/03/2001 Revu par : CC Description des types de donnes complmentaires de la bibliothque standard C++. Correction du comportement du bloc catch de Version 1.39.0 04/02/2001 Revu par : CC Mise en conformit des en-ttes C++ des exemples avec la norme. Correction des exemples utilisant des noms rservs par la biblio Version 1.38.1 14/10/2000 Revu par : CC Prcisions sur les classes de base virtuelles. Corrections orthographiques. Version 1.38.0 01/10/2000 Revu par : CC Corrections typographiques. Prcisions sur les oprateurs & et *. Version 1.37 23/08/2000 Revu par : CC Passage au format de chier SGML. Ajout des liens hypertextes. Corrections mineures. Version 1.36 27/07/2000 Revu par : CC Complment sur les parenthses dans les dnitions de macros. Corrections sur la numrotation des paragraphes. Version 1.35 10/07/2000 Revu par : CC Corrections sur les dclarations using. Version 1.34 09/07/2000 Revu par : CC Passage en licence FDL. Ajout de la table des matires. Version 1.33 22/60/2000 Revu par : CC Correction dune erreur dans le paragraphe sur les paramtres template template. Corrections orthographiques diverses. Version 1.32 17/06/2000/ Revu par : CC Correction dune erreur dans le programme dexemple du premier chapitre. Correction dune erreur dans un exemple sur la drivati Version 1.31 12/02/2000 Revu par : CC Corrections mineurs. Ajout du paragraphe sur la spcialisation dune fonction membre dune classe template. Version 1.30 05/12/1999 Revu par : CC Ajout de la licence. Modications mineures du formatage. Version <1.30 <1998 Revu par : CC

Version initiale.

Table des matires


Avant-propos ..................................................................................................................................... xv I. Le langage C++............................................................................................................................xvii 1. Premire approche du C/C++.................................................................................................. 1 1.1. Les ordinateurs, les langages et le C++ ...................................................................... 1 1.1.1. Les ordinateurs et la programmation ............................................................. 1 1.1.2. Les langages de programmation .................................................................... 1 1.1.3. Le langage C/C++ .......................................................................................... 3 1.1.4. Les outils de programmation ......................................................................... 3 1.2. Notre premier programme .......................................................................................... 4 1.2.1. Hello World! .................................................................................................. 4 1.2.2. Analyse du programme .................................................................................. 5 1.2.3. Gnralisation ................................................................................................ 6 1.3. Les commentaires en C/C++ ...................................................................................... 8 1.4. Les variables ............................................................................................................... 9 1.4.1. Dnition des variables.................................................................................. 9 1.4.2. Les types de base du C/C++ ........................................................................ 10 1.4.3. Notation des valeurs..................................................................................... 12 1.4.3.1. Notation des valeurs boolennes ..................................................... 12 1.4.3.2. Notation des valeurs entires........................................................... 13 1.4.3.3. Notation des valeurs en virgule ottantes ....................................... 13 1.4.3.4. Notation des caractres.................................................................... 14 1.4.3.5. Notation des chanes de caractres.................................................. 15 1.5. Les instructions......................................................................................................... 15 1.5.1. Les instructions simples............................................................................... 16 1.5.2. Les instructions compose ........................................................................... 19 1.5.3. Les structures de contrle ............................................................................ 19 1.6. Les fonctions et les procdures................................................................................. 19 1.6.1. Dnition des fonctions et des procdures .................................................. 20 1.6.2. Appel des fonctions et des procdures......................................................... 21 1.6.3. Notion de dclaration................................................................................... 22 1.6.4. Surcharge des fonctions ............................................................................... 23 1.6.5. Fonctions inline............................................................................................ 24 1.6.6. Fonctions statiques....................................................................................... 25 1.6.7. Fonctions prenant un nombre variable de paramtres ................................. 25 1.7. Les entres / sorties en C .......................................................................................... 27 1.7.1. Gnralits sur les ux dentre / sortie....................................................... 27 1.7.2. Les fonctions dentre / sortie de la bibliothque C .................................... 28 1.7.3. La fonction printf ..................................................................................... 29 1.7.4. La fonction scanf.......................................................................................... 31 2. Les structures de contrle ..................................................................................................... 35 2.1. Les tests .................................................................................................................... 35 2.1.1. La structure conditionnelle if ....................................................................... 35 2.1.2. Le branchement conditionnel....................................................................... 36 2.2. Les boucles ............................................................................................................... 37 2.2.1. La boucle for ................................................................................................ 37 2.2.2. Le while ....................................................................................................... 38 2.2.3. Le do ............................................................................................................ 39 2.3. Les instructions de rupture de squence et de saut................................................... 39 2.3.1. Les instructions de rupture de squence ...................................................... 39

2.3.2. Le saut.......................................................................................................... 40 3. Types avancs et classes de stockage .................................................................................... 43 3.1. Types de donnes portables ...................................................................................... 43 3.2. Structures de donnes et types complexes................................................................ 44 3.2.1. Les tableaux ................................................................................................. 45 3.2.2. Les chanes de caractres ............................................................................. 46 3.2.3. Les structures ............................................................................................... 47 3.2.4. Les unions .................................................................................................... 49 3.2.5. Les champs de bits ....................................................................................... 50 3.2.6. Initialisation des structures et des tableaux.................................................. 51 3.3. Les numrations...................................................................................................... 52 3.4. Les alias de types...................................................................................................... 53 3.4.1. Dnition dun alias de type ........................................................................ 53 3.4.2. Utilisation dun alias de type ....................................................................... 54 3.5. Transtypages et promotions...................................................................................... 55 3.6. Les classes de stockage ............................................................................................ 57 4. Les pointeurs et rfrences.................................................................................................... 61 4.1. Notion dadresse....................................................................................................... 61 4.2. Notion de pointeur.................................................................................................... 61 4.3. Drfrencement, indirection ................................................................................... 62 4.4. Notion de rfrence .................................................................................................. 64 4.5. Lien entre les pointeurs et les rfrences.................................................................. 64 4.6. Passage de paramtres par variable ou par valeur .................................................... 65 4.6.1. Passage par valeur........................................................................................ 65 4.6.2. Passage par variable ..................................................................................... 66 4.6.3. Avantages et inconvnients des deux mthodes........................................... 66 4.6.4. Comment passer les paramtres par variable en C ?.................................... 67 4.6.5. Passage de paramtres par rfrence............................................................ 67 4.7. Rfrences et pointeurs constants et volatiles .......................................................... 69 4.8. Arithmtique des pointeurs....................................................................................... 72 4.9. Utilisation des pointeurs avec les tableaux............................................................... 73 4.10. Les chanes de caractres : pointeurs et tableaux la fois ! ................................... 74 4.11. Allocation dynamique de mmoire ........................................................................ 75 4.11.1. Allocation dynamique de mmoire en C ................................................... 75 4.11.2. Allocation dynamique en C++................................................................... 80 4.12. Pointeurs et rfrences de fonctions ....................................................................... 82 4.12.1. Pointeurs de fonctions................................................................................ 82 4.12.2. Rfrences de fonctions ............................................................................. 84 4.13. Paramtres de la fonction main - ligne de commande............................................ 85 4.14. DANGER................................................................................................................ 86 5. Le prprocesseur C................................................................................................................ 89 5.1. Dnition.................................................................................................................. 89 5.2. Les directives du prprocesseur................................................................................ 89 5.2.1. Inclusion de chier....................................................................................... 89 5.2.2. Constantes de compilation et remplacement de texte .................................. 90 5.2.3. Compilation conditionnelle.......................................................................... 91 5.2.4. Autres directives .......................................................................................... 92 5.3. Les macros................................................................................................................ 92 5.4. Manipulation de chanes de caractres dans les macros........................................... 95 5.5. Les trigraphes ........................................................................................................... 96 6. Modularit des programmes et gnration des binaires........................................................ 97 6.1. Pourquoi faire une programmation modulaire ?....................................................... 97

vi

6.2. Les diffrentes phases du processus de gnration des excutables......................... 98 6.3. Compilation spare en C/C++ .............................................................................. 100 6.4. Syntaxe des outils de compilation .......................................................................... 101 6.4.1. Syntaxe des compilateurs........................................................................... 101 6.4.2. Syntaxe de make ........................................................................................ 102 6.5. Problmes syntaxiques relatifs la compilation spare ....................................... 103 6.5.1. Dclaration des types ................................................................................. 103 6.5.2. Dclaration des variables ........................................................................... 103 6.5.3. Dclaration des fonctions........................................................................... 103 6.5.4. Directives ddition de liens ...................................................................... 104 7. C++ : la couche objet .......................................................................................................... 107 7.1. Gnralits.............................................................................................................. 107 7.2. Extension de la notion de type du C....................................................................... 108 7.3. Dclaration de classes en C++................................................................................ 108 7.4. Encapsulation des donnes ..................................................................................... 112 7.5. Hritage .................................................................................................................. 114 7.6. Classes virtuelles .................................................................................................... 117 7.7. Fonctions et classes amies ...................................................................................... 118 7.7.1. Fonctions amies ......................................................................................... 119 7.7.2. Classes amies ............................................................................................. 119 7.8. Constructeurs et destructeurs.................................................................................. 120 7.8.1. Dnition des constructeurs et des destructeurs ........................................ 121 7.8.2. Constructeurs de copie............................................................................... 125 7.8.3. Utilisation des constructeurs dans les transtypages ................................... 126 7.9. Pointeur this............................................................................................................ 127 7.10. Donnes et fonctions membres statiques.............................................................. 128 7.10.1. Donnes membres statiques..................................................................... 129 7.10.2. Fonctions membres statiques ................................................................... 130 7.11. Surcharge des oprateurs ...................................................................................... 131 7.11.1. Surcharge des oprateurs internes............................................................ 132 7.11.2. Surcharge des oprateurs externes ........................................................... 134 7.11.3. Oprateurs daffectation........................................................................... 137 7.11.4. Oprateurs de transtypage........................................................................ 138 7.11.5. Oprateurs de comparaison...................................................................... 139 7.11.6. Oprateurs dincrmentation et de dcrmentation ................................. 139 7.11.7. Oprateur fonctionnel .............................................................................. 140 7.11.8. Oprateurs dindirection et de drfrencement ...................................... 142 7.11.9. Oprateurs dallocation dynamique de mmoire ..................................... 143 7.12. Des entres - sorties simplies ........................................................................... 150 7.13. Mthodes virtuelles .............................................................................................. 152 7.14. Drivation ............................................................................................................. 154 7.15. Mthodes virtuelles pures - Classes abstraites ..................................................... 156 7.16. Pointeurs sur les membres dune classe ............................................................... 161 8. Les exceptions en C++ ........................................................................................................ 165 8.1. Techniques de gestion des erreurs .......................................................................... 165 8.2. Lancement et rcupration dune exception........................................................... 169 8.3. Hirarchie des exceptions....................................................................................... 171 8.4. Traitement des exceptions non captes................................................................... 173 8.5. Liste des exceptions autorises pour une fonction ................................................. 174 8.6. Gestion des objets exception .................................................................................. 176 8.7. Exceptions dans les constructeurs et les destructeurs............................................. 177 9. Identication dynamique des types..................................................................................... 181

vii

9.1. Identication dynamique des types ........................................................................ 181 9.1.1. Loprateur typeid ...................................................................................... 181 9.1.2. La classe type_info .................................................................................... 183 9.2. Transtypages C++................................................................................................... 183 9.2.1. Transtypage dynamique ............................................................................. 184 9.2.2. Transtypage statique .................................................................................. 186 9.2.3. Transtypage de constance et de volatilit................................................... 187 9.2.4. Rinterprtation des donnes ..................................................................... 187 10. Les espaces de nommage .................................................................................................. 189 10.1. Dnition des espaces de nommage..................................................................... 189 10.1.1. Espaces de nommage nomms................................................................. 189 10.1.2. Espaces de nommage anonymes .............................................................. 191 10.1.3. Alias despaces de nommage ................................................................... 192 10.2. Dclaration using.................................................................................................. 192 10.2.1. Syntaxe des dclarations using ................................................................ 192 10.2.2. Utilisation des dclarations using dans les classes .................................. 194 10.3. Directive using...................................................................................................... 195 11. Les template ...................................................................................................................... 199 11.1. Gnralits............................................................................................................ 199 11.2. Dclaration des paramtres template.................................................................... 199 11.2.1. Dclaration des types template ................................................................ 199 11.2.2. Dclaration des constantes template ........................................................ 200 11.3. Fonctions et classes template................................................................................ 201 11.3.1. Fonctions template ................................................................................... 201 11.3.2. Les classes template................................................................................. 202 11.3.3. Fonctions membres template ................................................................... 205 11.4. Instanciation des template .................................................................................... 208 11.4.1. Instanciation implicite.............................................................................. 208 11.4.2. Instanciation explicite .............................................................................. 209 11.4.3. Problmes soulevs par linstanciation des template............................... 210 11.5. Spcialisation des template................................................................................... 211 11.5.1. Spcialisation totale ................................................................................. 211 11.5.2. Spcialisation partielle ............................................................................. 212 11.5.3. Spcialisation dune mthode dune classe template............................... 214 11.6. Mot-cl typename................................................................................................. 215 11.7. Fonctions exportes .............................................................................................. 216 12. Conventions de codage et techniques de base................................................................... 217 12.1. Conventions de codage ......................................................................................... 217 12.1.1. Gnralits ............................................................................................... 217 12.1.2. Lisibilit et cohrence du code ................................................................ 218 12.1.2.1. Rgles de nommage des identicateurs ...................................... 218 12.1.2.2. Rgles de cohrence et de portabilit .......................................... 221 12.1.2.3. Rgles de formatage et dindentation.......................................... 224 12.1.3. Rduction des risques .............................................................................. 225 12.1.3.1. Rgles de simplicit .................................................................... 225 12.1.3.2. Rgles de rduction des effets de bords ...................................... 226 12.1.3.3. Rgles de prvention ................................................................... 228 12.1.4. Optimisations ........................................................................................... 233 12.1.4.1. Rgles doptimisation gnrales.................................................. 233 12.1.4.2. Autres rgles doptimisation ....................................................... 234 12.2. Mthodes et techniques classiques ....................................................................... 236 12.2.1. Mthodologie objet .................................................................................. 236

viii

12.2.1.1. Dnir le besoin et le primtre des fonctionnalits ................... 236 12.2.1.2. Identier les entits et leur nombre ............................................. 237 12.2.1.3. Analyser les interactions et la dynamique du systme ................ 238 12.2.1.4. Dnir les interfaces.................................................................... 238 12.2.1.5. Ralisation et codage................................................................... 239 12.2.1.6. Les tests ....................................................................................... 239 12.2.1.7. Les design patterns ...................................................................... 240 12.2.2. Programmation objet en C ....................................................................... 241 12.2.2.1. Principe de base........................................................................... 241 12.2.2.2. Dnition dinterfaces................................................................. 241 12.2.2.3. Compatibilit binaire................................................................... 244 12.2.3. ABI et API ............................................................................................... 245 12.2.3.1. Choix de lABI ............................................................................ 245 12.2.3.2. Dnition de lAPI ...................................................................... 246 12.2.3.2.1. Simplicit dutilisation.................................................... 246 12.2.3.2.2. Interfaces synchrones et asynchrones ............................. 247 12.2.3.2.3. Gestion des allocations mmoire .................................... 247 12.2.3.2.4. Allocation des valeurs de retour ..................................... 248 12.3. Considrations systme ........................................................................................ 249 12.3.1. La scurit................................................................................................ 249 12.3.2. Le multithreading..................................................................................... 250 12.3.2.1. Gnralits................................................................................... 250 12.3.2.2. Utilisation du multithreading ...................................................... 251 12.3.2.3. Les piges du multithreading ...................................................... 252 12.3.2.4. Multithreading et programmation objet ...................................... 253 12.3.2.5. Limitations et contraintes lies au multithreading ...................... 254 12.3.3. Les signaux .............................................................................................. 255 12.3.4. Les bibliothques de liaison dynamique .................................................. 256 12.3.4.1. Les avantages des bibliothques de liaison dynamique .............. 256 12.3.4.2. Les mcanismes de chargement .................................................. 256 12.3.4.3. Relogement et code indpendant de la position .......................... 257 12.3.4.4. Optimisation des bibliothques de liaison dynamique ................ 258 12.3.4.5. Initialisation des bibliothques de liaison dynamique................. 261 12.3.5. Les communications rseau ..................................................................... 262 12.3.5.1. Fiabilit des informations............................................................ 262 12.3.5.2. Performances des communications ............................................. 263 12.3.5.3. Pertes de connexion..................................................................... 264 II. La bibliothque standard C++ ................................................................................................. 265 13. Services et notions de base de la bibliothque standard ................................................... 267 13.1. Encapsulation de la bibliothque C standard........................................................ 267 13.2. Dnition des exceptions standards ..................................................................... 269 13.3. Abstraction des types de donnes : les traits ........................................................ 272 13.4. Abstraction des pointeurs : les itrateurs.............................................................. 274 13.4.1. Notions de base et dnition.................................................................... 274 13.4.2. Classication des itrateurs...................................................................... 275 13.4.3. Itrateurs adaptateurs ............................................................................... 277 13.4.3.1. Adaptateurs pour les ux dentre / sortie standards .................. 278 13.4.3.2. Adaptateurs pour linsertion dlments dans les conteneurs ..... 280 13.4.3.3. Itrateur inverse pour les itrateurs bidirectionnels..................... 283 13.5. Abstraction des fonctions : les foncteurs.............................................................. 285 13.5.1. Foncteurs prdnis ................................................................................. 285

ix

13.5.2. Prdicats et foncteurs doprateurs logiques............................................ 290 13.5.3. Foncteurs rducteurs ................................................................................ 291 13.6. Gestion personnalise de la mmoire : les allocateurs ......................................... 293 13.7. Notion de complexit algorithmique .................................................................... 297 13.7.1. Gnralits ............................................................................................... 297 13.7.2. Notions mathmatiques de base et dnition........................................... 298 13.7.3. Interprtation pratique de la complexit .................................................. 299 14. Les types complmentaires ............................................................................................... 301 14.1. Les chanes de caractres...................................................................................... 301 14.1.1. Construction et initialisation dune chane .............................................. 305 14.1.2. Accs aux proprits dune chane .......................................................... 306 14.1.3. Modication de la taille des chanes........................................................ 307 14.1.4. Accs aux donnes de la chane de caractres ......................................... 308 14.1.5. Oprations sur les chanes........................................................................ 310 14.1.5.1. Affectation et concatnation de chanes de caractres ................ 310 14.1.5.2. Extraction de donnes dune chane de caractres ...................... 312 14.1.5.3. Insertion et suppression de caractres dans une chane............... 313 14.1.5.4. Remplacements de caractres dune chane ................................ 314 14.1.6. Comparaison de chanes de caractres..................................................... 316 14.1.7. Recherche dans les chanes...................................................................... 317 14.1.8. Fonctions dentre / sortie des chanes de caractres............................... 319 14.2. Les types utilitaires............................................................................................... 320 14.2.1. Les pointeurs auto .................................................................................... 320 14.2.2. Les paires ................................................................................................. 323 14.3. Les types numriques ........................................................................................... 324 14.3.1. Les complexes.......................................................................................... 325 14.3.1.1. Dnition et principales proprits des nombres complexes ...... 325 14.3.1.2. La classe complex ....................................................................... 327 14.3.2. Les tableaux de valeurs ............................................................................ 330 14.3.2.1. Fonctionnalits de base des valarray ........................................... 331 14.3.2.2. Slection multiple des lments dun valarray............................ 335 14.3.2.2.1. Slection par un masque ................................................. 335 14.3.2.2.2. Slection par indexation explicite................................... 336 14.3.2.2.3. Slection par indexation implicite .................................. 337 14.3.2.2.4. Oprations ralisables sur les slections multiples......... 339 14.3.3. Les champs de bits ................................................................................... 340 15. Les ux dentre / sortie.................................................................................................... 345 15.1. Notions de base et prsentation gnrale.............................................................. 345 15.2. Les tampons.......................................................................................................... 347 15.2.1. Gnralits sur les tampons ..................................................................... 347 15.2.2. La classe basic_streambuf........................................................................ 348 15.2.3. Les classes de tampons basic_stringbuf et basic_lebuf ......................... 353 15.2.3.1. La classe basic_stringbuf............................................................. 354 15.2.3.2. La classe basic_lebuf................................................................. 356 15.3. Les classes de base des ux : ios_base et basic_ios ............................................. 357 15.3.1. La classe ios_base .................................................................................... 358 15.3.2. La classe basic_ios................................................................................... 364 15.4. Les ux dentre / sortie ....................................................................................... 367 15.4.1. La classe de base basic_ostream .............................................................. 367 15.4.2. La classe de base basic_istream............................................................... 373 15.4.3. La classe basic_iostream.......................................................................... 379 15.5. Les ux dentre / sortie sur chanes de caractres .............................................. 380

15.6. Les ux dentre / sortie sur chiers .................................................................... 381 16. Les locales......................................................................................................................... 385 16.1. Notions de base et principe de fonctionnement des facettes ................................ 386 16.2. Les facettes standards ........................................................................................... 391 16.2.1. Gnralits ............................................................................................... 391 16.2.2. Les facettes de manipulation des caractres ............................................ 392 16.2.2.1. La facette ctype ........................................................................... 392 16.2.2.2. La facette codecvt........................................................................ 396 16.2.3. Les facettes de comparaison de chanes................................................... 400 16.2.4. Les facettes de gestion des nombres ........................................................ 403 16.2.4.1. La facette num_punct .................................................................. 403 16.2.4.2. La facette dcriture des nombres ............................................... 405 16.2.4.3. La facette de lecture des nombres ............................................... 406 16.2.5. Les facettes de gestion des monnaies....................................................... 407 16.2.5.1. La facette money_punct .............................................................. 408 16.2.5.2. Les facettes de lecture et dcriture des montants....................... 410 16.2.6. Les facettes de gestion du temps.............................................................. 411 16.2.6.1. La facette dcriture des dates ..................................................... 413 16.2.6.2. La facette de lecture des dates..................................................... 413 16.2.7. Les facettes de gestion des messages....................................................... 415 16.3. Personnalisation des mcanismes de localisation................................................. 417 16.3.1. Cration et intgration dune nouvelle facette ......................................... 417 16.3.2. Remplacement dune facette existante..................................................... 421 17. Les conteneurs................................................................................................................... 425 17.1. Fonctionnalits gnrales des conteneurs............................................................. 425 17.1.1. Dnition des itrateurs ........................................................................... 426 17.1.2. Dnition des types de donnes relatifs aux objets contenus .................. 427 17.1.3. Spcication de lallocateur mmoire utiliser....................................... 427 17.1.4. Oprateurs de comparaison des conteneurs ............................................. 428 17.1.5. Mthodes dintrt gnral ...................................................................... 429 17.2. Les squences ....................................................................................................... 429 17.2.1. Fonctionnalits communes....................................................................... 429 17.2.1.1. Construction et initialisation ....................................................... 429 17.2.1.2. Ajout et suppression dlments ................................................. 431 17.2.2. Les diffrents types de squences ............................................................ 432 17.2.2.1. Les listes ...................................................................................... 433 17.2.2.2. Les vecteurs................................................................................. 436 17.2.2.3. Les deques ................................................................................... 438 17.2.2.4. Les adaptateurs de squences ...................................................... 439 17.2.2.4.1. Les piles .......................................................................... 439 17.2.2.4.2. Les les........................................................................... 440 17.2.2.4.3. Les les de priorits........................................................ 440 17.3. Les conteneurs associatifs .................................................................................... 442 17.3.1. Gnralits et proprits de base des clefs............................................... 443 17.3.2. Construction et initialisation .................................................................... 444 17.3.3. Ajout et suppression dlments .............................................................. 445 17.3.4. Fonctions de recherche ............................................................................ 447 18. Les algorithmes ................................................................................................................. 453 18.1. Oprations gnrales de manipulation des donnes ............................................. 453 18.1.1. Oprations dinitialisation et de remplissage........................................... 454 18.1.2. Oprations de copie.................................................................................. 455 18.1.3. Oprations dchange dlments ............................................................ 456

xi

18.1.4. Oprations de suppression dlments..................................................... 457 18.1.5. Oprations de remplacement.................................................................... 459 18.1.6. Rorganisation de squences ................................................................... 460 18.1.6.1. Oprations de rotation et de permutation .................................... 461 18.1.6.2. Oprations dinversion ................................................................ 462 18.1.6.3. Oprations de mlange ................................................................ 463 18.1.7. Algorithmes ditration et de transformation........................................... 464 18.2. Oprations de recherche ....................................................................................... 469 18.2.1. Opration de recherche dlments.......................................................... 469 18.2.2. Oprations de recherche de motifs........................................................... 471 18.3. Oprations dordonnancement.............................................................................. 473 18.3.1. Oprations de gestion des tas................................................................... 474 18.3.2. Oprations de tri....................................................................................... 476 18.3.3. Oprations de recherche binaire............................................................... 480 18.4. Oprations de comparaison .................................................................................. 483 18.5. Oprations ensemblistes ....................................................................................... 485 18.5.1. Oprations dinclusion ............................................................................. 485 18.5.2. Oprations dintersection ......................................................................... 486 18.5.3. Oprations dunion et de fusion............................................................... 488 18.5.4. Oprations de diffrence .......................................................................... 490 18.5.5. Oprations de partitionnement................................................................. 492 19. Conclusion ................................................................................................................................. 495 A. Liste des mots cls du C/C++ .................................................................................................... 497 B. Priorits des oprateurs............................................................................................................. 499 C. Draft Papers ............................................................................................................................... 501 BIBLIOGRAPHIE ......................................................................................................................... 503

xii

Liste des tableaux


1-1. Types de base du langage ............................................................................................................ 10 1-2. Oprateurs du langage C/C++ ..................................................................................................... 16 1-3. Chanes de format de printf pour les types de base................................................................. 29 1-4. Options de taille pour les types drivs....................................................................................... 30 1-5. Options dalignements et de remplissange.................................................................................. 31 2-1. Oprateurs de comparaison ......................................................................................................... 35 2-2. Oprateurs logiques..................................................................................................................... 35 3-1. Classes de stockages.................................................................................................................... 57 3-2. Qualicatifs de constance............................................................................................................ 58 5-1. Trigraphes.................................................................................................................................... 96 7-1. Droits daccs sur les membres hrits ..................................................................................... 114 12-1. Prxes en notation hongroise simplie................................................................................ 220 14-1. Fonctions de recherche dans les chanes de caractres ........................................................... 317 14-2. Fonctions spciques aux complexes...................................................................................... 329 15-1. Options de formatage des ux................................................................................................. 360 15-2. Modes douverture des chiers ............................................................................................... 361 15-3. Directions de dplacement dans un chier.............................................................................. 361 15-4. tats des ux dentre / sortie ................................................................................................. 362 15-5. Manipulateurs des ux de sortie.............................................................................................. 371 15-6. Manipulateurs utilisant des paramtres ................................................................................... 372 15-7. Manipulateurs des ux dentre .............................................................................................. 379 16-1. Fonctions C de gestion des dates............................................................................................. 412 17-1. Mthodes spciques aux listes .............................................................................................. 434 A-1. Mots cls du langange .............................................................................................................. 497 B-1. Oprateurs du langage .............................................................................................................. 499

xiii

xiv

Avant-propos
Ce livre est un cours de C et de C++. Il sadresse aux personnes qui ont dj quelques notions de programmation dans un langage quelconque. Les connaissances requises ne sont pas trs leves cependant : il nest pas ncessaire davoir fait de grands programmes pour lire ce document. Il suft davoir vu ce quest un programme et compris les grands principes de la programmation. Ce livre est structur en deux grandes parties, traitant chacune un des aspects du C++. La premire partie, contenant les chapitres 1 12, traite du langage C++ lui-mme, de sa syntaxe et de ses principales fonctionnalits. La deuxime partie quant elle se concentre sur la bibliothque standard C++, qui fournit un ensemble de fonctionnalits cohrentes et rutilisables par tous les programmeurs. La bibliothque standard C++ a galement lavantage dutiliser les constructions les plus avances du langage, et illustre donc parfaitement les notions qui auront t abordes dans la premire partie. La description de la bibliothque standard stend du chapitre 13 au chapitre 18. Le plan de ce document a t conu pour tre didactique. Toutefois, certaines notions font rfrence des chapitres ultrieurs. Cela nest le cas que pour des points de dtails, et les paragraphes en question sont identis en tant que tels. Ils pourront tre passs en premire lecture. Si la bibliothque standard C++ est dcrite en dtail, il nen est pas de mme pour les fonctions de la bibliothque C. Vous ne trouverez donc pas dans ce livre la description des fonctions classiques du C, ni celle des fonctions les plus courantes de la norme POSIX. En effet, bien que prsentes sur quasiment tous les systmes dexploitation, ces fonctions sont spciques la norme POSIX et nappartiennent pas au langage en soi. Seules les fonctions incontournables de la bibliothque C seront donc prsentes ici. Si vous dsirez plus de renseignements, reportez-vous aux spcications des appels systmes POSIX de lOpenGroup (http://www.unix-systems.org/single_unix_specication/), ou la documentation des environnements de dveloppement et laide des kits de dveloppement des systmes dexploitation (SDK). Ce livre a pour but de prsenter le langage C++ tel quil est dcrit par la norme ISO 14882 du langage C++. Cette norme nest pas disponible librement, aussi pourrez-vous vous rabattre sur les documents non ofciels du projet de normalisation du langage les plus rcents. Il sagit des Working Paper for Draft Proposed International Standard for Information Systems -- Programming Language C++ (http://casteyde.christian.free.fr/cpp/cours/drafts/index.html) , qui, bien quils datent du 2 dcembre 1996, sont encore tout fait exploitables. Notez que les compilateurs qui respectent cette norme se comptent encore sur les doigts dune main, et que les informations et exemples donns ici peuvent ne pas savrer exacts avec certains produits. En particulier, certains exemples ne compileront pas avec les compilateurs les plus mauvais. Notez galement que certaines constructions du langage nont pas la mme signication avec tous les compilateurs, parce quelles ont t implmentes avant que la norme ne les spcie compltement. Ces diffrences peuvent conduire du code non portable, et ont t signales chaque fois dans une note. Le fait que les exemples de ce livre ne fonctionnent pas avec de tels compilateurs ne peut donc pas tre considr comme une erreur, mais plutt comme une non-conformit des outils utiliss, qui sera sans doute leve dans les versions ultrieures de ces produits. Enn, ce livre est un document vivant. Vous en trouverez toujours la dernire version sur mon site web (http://casteyde.christian.free.fr). Bien entendu, toute remarque est la bienvenue, et je tcherai de corriger les erreurs que lon me signalera et dapporter les modications ncessaires si un point est obscur. Si vous prenez le temps de menvoyer les remarques et les erreurs que vous avez pu dtecter, je vous saurais gr de vrier au pralable quelles sont toujours dactualit dans la dernire version de ce document. cette n, un historique des rvisions a t inclus en premire page pour permettre lidentication des diffrentes ditions de ce document.

xv

Avant-propos

xvi

I. Le langage C++
Tout le dbut de cette partie (chapitres 1 6) traite des fonctionnalits communes au C et au C++, en insistant bien sur les diffrences entre ces deux langages. Ces chapitres prsentent essentiellement la syntaxe des constructions de base du C et du C++. Le dbut de cette partie peut donc galement tre considr comme un cours allg sur le langage C. Cependant, les constructions syntaxiques utilises sont crites de telle sorte quelles sont compilables en C++. Cela signie quelles nutilisent pas certaines fonctionnalits douteuses du C. Ceux qui dsirent utiliser la premire partie comme un cours de C doivent donc savoir quil sagit dune version pure de ce langage. En particulier, les appels de fonctions non dclares ou les appels de fonctions avec trop de paramtres ne sont pas considrs comme des pratiques de programmation valables. Les chapitres suivants (chapitres 7 11) ne traitent que du C++. Les constructions utilises pour permettre la programmation oriente objet, ainsi que toutes les extensions qui ont t apportes au langage C pour grer les objets y sont dcrits. Le mcanisme des exceptions du langage, qui permet de grer les erreurs plus facilement, est ensuite prsent, de mme que les mcanismes didentication dynamique des types. Enn, les notions despaces de nommage et de modles de fonctions et de classes seront dcrites. Ces dernires fonctionnalits sont utilises intensivement dans la bibliothque standard C++, aussi la lecture complte de la premire partie est-elle indispensable avant de sattaquer la deuxime. Enn, le dernier chapitre de cette partie (chapitre 12) traite des rgles de codage et donne des conseils utiles la ralisation de programmes plus srs, plus maintenables et plus volutifs. La lecture de ce chapitre nest donc pas lie au langage C/C++ et nest pas techniquement obligatoire, mais elle donne des ides qui pourront tre suivies ou adaptes dans le but dacqurir ds le dpart de bonnes habitudes. Dans toute cette premire partie, la syntaxe sera donne, sauf exception, avec la convention suivante : ce qui est entre crochets ([ et ]) est facultatif. De plus, quand plusieurs lments de syntaxe sont spars par une barre verticale (|), lun de ces lments, et un seulement, doit tre prsent (cest un ou exclusif). Enn, les points de suspension dsigneront une itration ventuelle du motif prcdent. Par exemple, si la syntaxe dune commande est la suivante :
[fac|rty|sss] zer[(kfl[,kfl[...]])];

les combinaisons suivantes seront syntaxiquement correctes :


zer; fac zer; rty zer; zer(kfl); sss zer(kfl,kfl,kfl,kfl);

mais la combinaison suivante sera incorrecte :


fac sss zer()

pour les raisons suivantes :

fac et sss sont mutuellement exclusifs, bien que facultatifs tous les deux ; au moins un k est ncessaire si les parenthses sont mises ; il manque le point virgule nal.

Rassurez-vous, il ny aura pratiquement jamais de syntaxe aussi complique. Je suis sincrement dsol de la complexit de cet exemple.

Chapitre 1. Premire approche du C/C++


Lapprentissage dun langage de programmation nest pas chose aise. Une approche progressive est ncessaire. Ce chapitre donnera donc les concepts de base de la programmation et prsentera le langage C/C++ de manire pratique, laide dexemples de complexit progressive. Les notions plus complexes seront prsentes dans les chapitres ultrieurs, une fois que lon aura limin les premires apprhensions.

1.1. Les ordinateurs, les langages et le C++


1.1.1. Les ordinateurs et la programmation
Les ordinateurs sont des machines conues pour tre gnriques et effectuer des oprations a priori non prvues lors de leur conception. Ils se distinguent en cela des machines conues dans un but spcique, an de raliser une tche prdtermine. Cest cette gnricit qui leur donne toute leur utilit. Si, dans certaines situations, des machines spciques sont particulirement appropries, notamment pour des raisons de performances, les ordinateurs sont incontournables ds lors que les tches accomplir sont diverses ou ds lors quelles sont nouvelles et nont pas de solution existante. Cest pour cette raison quils sont particulirement utiliss dans le monde de la simulation, o les dveloppements spciques sont raliss an dtudier le phnomne observer. De mme, ils sont utiliss pour modliser et concevoir les machines spciques qui effectueront la tche au nal. Malgr leur gnricit, les ordinateurs sont parvenus des performances plus quhonorables en termes de puissance de calcul, de stockage et de communication, et peuvent prsent tre utiliss l o des circuits spciques taient ncessaires il y a encore peu de temps. Le gain est alors double : les produits sont volutifs et les cots de fabrication moindre, puisque le march de chaque circuit spcique est bien plus restreint que celui des circuits gnriques prsent. Mais, en raison mme de leur gnricit, ces machines ne savent effectuer que trs peu de choses par dfaut. En ralit, elles ne savent excuter que des instructions de base. Par consquent, pour parvenir au but recherch, il est ncessaire de spcier de longues listes dinstructions, devant tre excutes en squence, voire parfois en parallle, selon larchitecture matrielle de lordinateur. Cette phase de spcication sappelle la programmation.

1.1.2. Les langages de programmation


La programmation directe dun ordinateur nest pas une tche aise, car les instructions lmentaires ne font rellement pas grand chose chacune. An de faciliter cette programmation, des logiciels complets ont t crits. Les plus importants sont sans doute les systmes dexploitation, qui prennent en charge la gestion de lordinateur lui-mme et fournissent des fonctionnalits de haut niveau, et les logiciels de langages de programmation, dont le rle est de permettre de raliser dautres programmes laide dun langage de plus haut niveau que le langage de la machine cible. prsent, les systmes et les logiciels des langages de programmation sont eux-mmes programms laide des langages de programmation, si bien que la programmation directe des ordinateurs en langage machine nest plus rserve que pour des applications trs spciques ou lorsque de trs grandes performances sont recherches.

Chapitre 1. Premire approche du C/C++ Les programmes crits dans un langage de programmation ne sont videmment pas comprhensibles par lordinateur. Les logiciels de langages de programmation doivent donc sassurer que les programmes sont correctement excuts par lordinateur. Deux solutions ont t adoptes pour rsoudre ce problme : soit le texte du programme (cest--dire ce que lon appelle le code source ) est traduit en langage machine par un logiciel appel compilateur , puis excut nativement, soit il est lu et interprt par un logiciel qui effectue les tches programmes lui-mme. Les langages de la premire catgorie sont des langages dits compils , alors que les langages de la deuxime catgorie sont des langages dits interprts . Les deux techniques ont leurs avantages et leurs inconvnients. Les langages compils sont les plus rapides, alors que les langages interprts sont les plus facile mettre au point (le programme pouvant souvent tre dbogu et modi en cours dexcution par linterprteur). Les langages de programmation permettent, au sens large, dexprimer ce que lon veut faire faire un ordinateur. Cela implique que, contrairement aux langages naturels tels que le franais ou langlais, toute ambigut doit tre absente. Cela suppose une syntaxe dnie mathmatiquement, et une smantique parfaitement dnie sur les contructions du langage que lon sautorise utiliser. Les langages de programmation sont donc dnis de manire rigoureuse, et rejoignent les mathmatiques. Toutefois, en pratique, ces langages ont pour but dtre humainement comprhensibles et utilisables. Les aspects thoriques ne sont donc intressants quau niveau de la recherche, et si le langage rpond au besoin initial (qui est de programmer lordinateur), il doit tre facile utiliser. La thorie des langages de programmation distingue essentiellement trois grandes classes de langages de programmation, dont les langages sont plus ou moins appropris aux diffrents problmes que le programmeur doit rsoudre. Ces trois classes de langages sont respectivement :

les langages impratifs, qui permettent de faire des programmes constitus de suites dinstructions permettant de modier ltat de lordinateur stock dans sa mmoire ; les langages fonctionnels, qui nutilisent pas la notion dtat mais qui considrent les programmes comme des suites de fonctions dont les rsultats constituent le comportement du programme excut ; les langages logiques, qui sintressent plus aux rsultats que le programme doit fournir, et cherchent les caractriser par des contraintes logiques que lordinateur rsoud ensuite grce un moteur dinfrence.

Bien entendu, chaque type de langage a ses avantages et ses inconvnients, et donc ses domaines dapplications. Le principe fondamental est donc ici de choisir le type de langage en fonction du problme, an de le rsoudre le plus facilement. Par exemple, les langages logiques sont particulirement apprcis pour raliser les systmes experts, dont le rle est de fournir une solution un problme partir de rgles dnies par les hommes de lart du domaine considr. Le programme se base dans ce cas sur ces rgles et les combine logiquement pour obtenir la solution du problme (la manire de lobtenir important ici moins que les rgles que ce rsultat doit vrier). Lavantage des langages impratifs, dont fait partie le C/C++, rside dans le fait que les ordinateurs sont des machines elles-mmes conues pour excuter des programmes impratifs (cest--dire les listes dinstructions du langage machine). Les langages impratifs sont donc trs faciles compiler, et permettent daccder aux fonctionnalits des ordinateurs de manire extrmement simple. Bien entendu, les performances sont garanties, si bien sr les algorithmes utiliss sont appropris. Enn, le modle impratif correspond particulirement bien aux ralits industrielles et aux automates, avec lesquels la communication se fait gnralement sous forme dordres. En revanche, contrairement aux autres classes de langage, les langages impratifs sont sujets des bogues provenant dincohrences dans la gestion de ltat du programme. Les techniques de dveloppement permettent toutefois de limiter ces erreurs, comme nous le verrons plus loin dans ce document.

Chapitre 1. Premire approche du C/C++

1.1.3. Le langage C/C++


Le C/C++ est un langage impratif compil, du mme type que le Pascal par exemple. Cest lun des langages de programmation les plus utiliss actuellement. Il est la fois facile utiliser et trs efcace. Il souffre cependant de la rputation dtre compliqu, illisible et, en raison de son aspect bas niveau, de permettre des bogues dans la gestion de la mmoire. Cette rputation est en partie justie. La complexit du langage est invitable lorsquon cherche avoir beaucoup de fonctionnalits. La lisibilit des programmes en revanche ne dpend que de la bonne volont du programmeur, et peut tre amliore laide de conventions de codage simples. Quant aux bogues bas niveau que le langage permet, ils sont la contrepartie dun contrle total du comportement du programme et donc de la machine. De plus, ils deviennent relativement rares avec lexprience et peuvent galement tre grandement limits par lutilisation de rgles de codage simples. Les caractristiques du C/C++ en font un langage idal pour certains types de projets. Il est incontournable dans la ralisation des programmes orients systmes ou temps rels. Il est galement trs utilis par de grands programmes, tant en raison de lhistorique que des performances pratiques que lon obtient au nal. Ces performances sont garanties aussi bien par le fait que le programmeur matrise rellement le programme, par la possibilit daccder lensemble des fonctions systme, et par les optimisations fournies par les compilateurs actuels. Cest galement un langage normalis et prsent sur lensemble des plateformes, ce qui permet de raliser des programmes portables au niveau source (les langages interprts prtendent une portabilit suprieure, mais celle-ci reste relative au contexte dexcution et dplace le problme vers la disponibilit des interprteurs sur lensemble des plateformes). Enn, les outils disponibles sont nombreux et ables. Le langage C++ constitue en soi une extension du langage C qui apporte de rels avantages :

contrle derreurs accru grce un typage fort des donnes ; facilit dutilisation des langages objets ; grand nombre de fonctionnalits complmentaires ; performances du C ; facilit de conversion des programmes C en C++, et, en particulier, possibilit dutiliser toutes les fonctionnalits du langage C.

On dispose donc de quasiment tout : puissance, fonctionnalit, portabilit et sret. La richesse du contrle derreurs du langage, bas sur un typage trs fort, permet de signaler un grand nombre derreurs la compilation. Toutes ces erreurs sont autant derreurs que le programme ne fait pas lexcution. Le C++ peut donc tre considr comme un super C . Le revers de la mdaille est que les programmes C ne se compilent pas directement en C++ : il est courant que de simples avertissements en C soient des erreurs bloquantes en C++. Quelques adaptations sont souvent ncessaires. Cependant, celles-ci sont minimes, puisque la syntaxe du C++ est base sur celle du C. On remarquera que tous les programmes C peuvent tre corrigs pour compiler la fois en C et en C++.

1.1.4. Les outils de programmation


Quel que soit le type dordinateur et le type de systme dexploitation que vous utilisez, il existe certainement un compilateur C/C++ pour cette plateforme. Pour certains systmes, le compilateur est un compilateur dit crois , cest--dire un compilateur qui produit du code excutable pour une autre machine que celle sur laquelle il fonctionne. Sur les plateformes les plus courantes, un

Chapitre 1. Premire approche du C/C++ choix abondant denvironnements de dveloppement est propos par les diteurs de logiciels ou par la communaut des logiciels libres. Ces produits sont gnralement de qualit, et ils sont prsent tous utilisables pour dvelopper des applications C/C++, et souvent mme dans dautres langages. Si vous disposez de ces environnement, je vous invite les utiliser. Dans le cas contraire, vous devrez installer au minimum un compilateur. Le compilateur GCC de la Free Software Foundation sera gnralement un bon choix, car il sagit du compilateur install par dfaut sur les machines fonctionnant sous Linux et sous MacOS X (voir le site de GCC (http://gcc.gnu.org)), et il existe une version installable facilement pour Windows (disponible sur le site du projet MinGW (http://www.mingw.org), Minimalist GNU for Windows). Vous devriez galement vous assurer que vous disposez du dbogueur en ligne de commande GDB, qui est le dbogueur par dfaut sous Linux et MacOS X, et qui est galement fourni par le projet MinGW. La suite de ce document supposera que vous utilisez ces logiciels, en raison de leur qualit et de leur disponibilit sur lensemble des plateformes. Vous pourrez vous assurer que GCC est correctement install en excutant la commande suivante dans une fentre de commande (mulateur de terminal sous Linux ou MacOS X, ou fentre MS-DOS sous Windows) :
c++ --version

Cette commande doit afcher un rsultat semblable au suivant :


c++ (GCC) 3.4.4 Copyright (C) 2004 Free Software Foundation, Inc. This is free software; see the source for copying conditions. There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

La premire ligne afche indique la version du logiciel.


Note : La commande c++ est la commande classiquement utilise sous Unix pour appeler le compilateur pour le langage C++. Si lon dsire ne faire que des programmes C, la commande utiliser est cc. Ces commandes sont gnralement des alias pour les commandes natives de GCC, qui sont gcc et g++ respectivement pour les langages C et C++. Si ces alias ne sont pas dnis, vous pouvez les dnir ou utiliser les commandes natives de GCC directement.

1.2. Notre premier programme


Nous allons maintenant entrer dans le vif du sujet et prsenter notre premier programme. Lusage en informatique est de faire un programme qui nafche quune ligne de texte et salue la compagnie : linvitable Hello World! ...

1.2.1. Hello World!


Voici donc notre premier programme : Exemple 1-1. Hello World!
#include <stdlib.h> #include <stdio.h>

Chapitre 1. Premire approche du C/C++

int main(void) { printf("Hello World!\n"); return EXIT_SUCCESS; }

Ce programme se compile avec la commande suivante :


c++ hello.cpp -o hello.exe

en supposant que le chier source se nomme hello.cpp et que le nom de lexcutable produire soit hello.exe. Son excution dans un mulateur de terminal ou une fentre de commandes (pas partir dun environnement graphique intgr, qui lancera le programme et fermera sa fentre immdiatement aprs la n de celui-ci) donne le rsultat suivant :
Hello World!

Note : Lextension des chiers sources pour le langage C est classiquement .c . Les chiers sources pour le langage C++ sont souvent .C , .cc ou .cpp . Lextension .C est particulirement dconseille, puisquelle ne se distingue de lextension des chiers C que par la casse, et que certains systmes de chiers ne sont pas capables de distinguer des chiers uniquement par leur casse. Lextension .cpp est reconnue par lensemble des compilateurs et est par consquent fortement conseille. De mme, il nest pas ncessaire dutiliser lextension .exe sous Linux et MacOS X. En revanche, cest impratif sous Windows, parce que celui-ci se base sur les extensions pour dterminer la nature des chiers. Par consquent, elle a t ajoute dans cet exemple.

1.2.2. Analyse du programme


Dcorticons prsent ce programme. Les deux premires lignes permettent dinclure le contenu de deux chiers nomms stdlib.h et stdio.h. Ces chiers, que lon nomme gnralement des chiers den-tte, contiennent les dclarations de fonctions et de constantes que le reste du programme va utiliser. Ces dclarations sont ncessaires pour pouvoir les utiliser. Sans elles, le compilateur signalerait quil ne connat pas ces fonctions et ces constantes. Comme vous pouvez le voir dans cet exemple, il est dusage dutiliser lextension .h pour les chiers den-tte. Il existe de nombreux chiers den-tte, chacun regroupant un jeu de fonctions utilitaires. Connatre les fonctionnalits disponibles et les chiers den-tte inclure pour y accder nest pas une tche facile, et ce savoir ne peut tre acquis que par lexprience et la consultation de la documentation des environnements de dveloppement. En particulier, un grand nombre de fonctionnalits sont fournies avec la bibliothque de fonctions du langage C, mais les dcrire toutes ncessiterait un livre complet en soi. En ce qui concerne notre exemple, sachez que le chier den-tte stdlib.h contient les dclarations des fonctions et des constantes de base de la bibliothque C, et que le chier den-tte stdio.h les dclarations des principales fonctions dentre/sortie permettant de lire et interprter des donnes et dafcher les rsultats du programme.

Chapitre 1. Premire approche du C/C++ La quatrime ligne contient la dclaration de la fonction main . Cette fonction est le point dentre du programme, cest--dire la fonction que le programme excutera lorsquil sera lanc. Le systme identie cette fonction justement par le fait quelle sappelle main , ne vous amusez-donc pas changer le nom de cette fonction. Pour information, main signie principal en anglais, ce qui indique bien que cette fonction est la fonction principale du programme. Comme vous pouvez le constater, le nom de la fonction est prcd du mot cl int , qui reprsente le type de donnes entier (type de donnes utilis pour stocker des donnes entires). Cela signie que cette fonction retourne une valeur entire. En effet, il est dusage que les programmes renvoient un code numrique de type entier au systme la n de leur excution. Gnralement, la valeur 0 indique une excution correcte, et toute autre valeur une erreur. En C/C++, cette valeur est tout simplement la valeur retourne par la fonction main. Gnralement, les arguments passs au programme par le systme sur sa ligne de commandes peuvent tre rcuprs en tant que paramtres de la fonction main. En C/C++, les paramtres de fonctions sont spcis entre parenthses, sous la forme dune liste de paramtres spars par des virgules. Dans cet exemple cependant, notre programme ne prend pas de paramtres, et nous ne dsirons pas rcuprer la ligne de commande utilise par le systme pour lancer le programme. Cest pour cela que le mot cl void est-il utilis dans la liste des paramtres pour indiquer que celle-ci est vide. Aprs la ligne de dclaration de la fonction main, vous trouverez son implmentation (cest--dire le code source qui dcrit ce quelle fait). Celle-ci est encadre par des accolades (caractres { et }), qui sont les caractres utiliss pour regrouper des instructions. Le corps de la fonction principale commence par appeler la fonction de la bibliothque C printf . Cette fonction, dont la dclaration est dans le chier den-tte stdio.h, permet dafcher une chane de caractres sur la sortie standard du programme. Dans notre exemple, on se contente dafcher la chane de caractres Hello World! lcran. Vous pouvez ainsi constater que les chanes de caractres sont fournies entre guillemets anglais (caractre "). La n de la chane de caractres afche contient un marqueur de saut de ligne ( newline en anglais), reprsent par \n . Nous verrons plus loin que le C/C++ fournit un certain nombre de marqueurs spciaux de ce type pour reprsenter les caractres de contrle ou non imprimables.
Note : La fonction printf est en ralit beaucoup plus puissante et permet dafcher virtuellement nimporte quoi, simplement partir des valeurs des informations afcher et dune chane de format (qui dcrit justement comment ces informations doivent tre afches). Cette fonction permet donc deffectuer des sorties formates sur la sortie standard du programme, do son nom ( PRINT Formatted ).

Enn, la dernire opration que fait la fonction main est de retourner le code de rsultat du programme au systme dexploitation. En C/C++, la valeur de retour des fonctions est spcie laide du mot cl return , suivi de cette valeur. Dans cet exemple, nous utilisons la constante EXIT_SUCCESS , dnie dans le chier den-tte stdlib.h. Cette constante a pour valeur la valeur utilise pour signaler quun programme sest excut correctement au systme (0 sur la plupart des systmes). Si nous avions voulu signaler une erreur, nous aurions par exemple utilis la constante EXIT_FAILURE .

1.2.3. Gnralisation
Tout programme crit dans un langage impratif a pour but deffectuer des oprations sur des donnes. La structure fondamentale dun programme en C/C++ est donc la suivante :
ENTRE DES DONNES (clavier, souris, fichier, autres priphriques)

Chapitre 1. Premire approche du C/C++


| TRAITEMENT DES DONNES | SORTIE DES RSULTATS (cran, imprimante, fichier, autres priphriques)

Pour les programmes les plus simples, les donnes proviennent du ux dentre standard, dun chier ou dune connexion rseau, et les rsultats sont mis sur le ux de sortie standard, dans un chier ou vers une connexion rseau. Les ux dentre et de sortie standards sont les ux par dfaut pour les programmes, et sont gnralement associs au clavier et la console du terminal ou de lmulateur de terminal dans lequel le programme fonctionne. Dans lexemple que nous venons de voir, il ny a aucune entre, mais une sortie qui est effectue sur le ux de sortie standard grce la fonction printf. Pour les programmes plus volus en revanche, le traitement nest pas aussi linaire, et seffectue souvent en boucle, de manire rpondre interactivement aux entres. Par exemple, pour les programmes graphiques, les donnes sont reues de la part du systme sous forme de messages caractrisant les vnements gnrs par lutilisateur ou par le systme lui-mme (dplacement de souris, fermeture dune fentre, appui sur une touche, etc.). Le traitement du programme est alors une boucle innie (que lon appelle la boucle des messages), qui permet de rcuprer ces messages et de prendre les actions en consquence. Dans ce cas, la sortie des donnes correspond au comportement que le programme adopte en rponse ces messages. Cela peut tre tout simplement dafcher les donnes saisies, ou, plus gnralement, dappliquer une commande aux donnes en cours de manipulation. Tous ces traitements peuvent tre disperss dans le programme. Par exemple, une partie du traitement peut avoir besoin de donnes complmentaires et chercher les rcuprer sur le ux dentre standard. De mme, un traitement complexe peut tre dcoup en plusieurs sous-programmes, qui sont appels par le programme principal. Ces sous-programmes sont, dans le cas du C/C++, des fonctions ou des procdures (cest--dire des fonctions qui ne retournent aucune valeur), qui peuvent tre appeles par la fonction principale, et qui peuvent appeler eux-mmes dautres fonctions ou procdures. Bien entendu, des fonctions utilisateurs peuvent tre dnies. La bibliothque C standard fournit galement un grand nombre de fonctions utilitaires de base, et des bibliothques complmentaires peuvent tre utilises pour des fonctionnalits complmentaires. Les donnes manipules par les programmes impratifs sont stockes dans des variables, cest--dire des zones de la mmoire. Cest lensemble de ces variables qui constitue ltat du programme. Les variables sont modies par le traitement des donnes, dans le cadre doprations bien prcises. Comme la plupart des langages, le C/C++ utilise la notion de type an de caractriser les oprations ralisables sur les donnes et la nature des donnes quelles reprsentent. Cela permet daccrotre la abilit des programmes, en vitant que les donnes soient manipules de manire incompatible. Par exemple, on ne peut pas ajouter des pommes des bananes, sauf dnir cette opration bien prcisment. Les oprations effectues sur les donnes dpendent donc de leur type. Le langage C/C++ fournit des types de base et des oprations prdnies sur ces types. Les oprations qui peuvent tre faites sont ralises via lapplication dun oprateur sur les expressions auxquelles il sapplique. Par exemple, laddition de deux entiers et leur affectation une variable a scrit de la manire suivante :
a=2+3

Cette expression utilise loprateur daddition entre entiers, et loprateur daffectation dun entier du type de la variable a.

Chapitre 1. Premire approche du C/C++ videmment, le programmeur pourra dnir ses propres types de donnes. Il nest pas possible de dnir de nouveaux oprateurs, mais en C++ (pas en C) les oprateurs peuvent tre rednis pour les nouveaux types que lutilisateur a crs. Si des besoins plus spciques se prsentent, il faudra crire des fonctions pour manipuler les donnes.
Note : En ralit, les oprateurs constituent une facilit dcriture, et ils sont quivalents des fonctions prenant leurs oprandes en paramtres et retournant leur rsultat. Logiquement parlant, il ny a pas de diffrence, seule la syntaxe change. Lcriture prcdente est donc strictement quivalente :
a=ajoute(2,3)

La suite de ce chapitre va prsenter la manire de dclarer les variables ainsi que les types de base du langage, la manire dcrire les instructions et dutiliser les oprateurs, et la syntaxe utilise pour dnir de nouvelles fonctions. Les notions de ux dentre/sortie seront ensuite prsentes an de permettre la rception des donnes traiter et de fournir en retour les rsultats.

1.3. Les commentaires en C/C++


Les commentaires sont des portions de texte insres dans le code source dun programme et qui ne sont pas prises en compte par le compilateur. Ils permettent, comme leur nom lindique, de documenter et dexpliquer en langage naturel tout ce que le programmeur pense tre ncessaire pour la bonne comprhension du code source. Les commentaires sont donc absolument ncessaires, mais ne doivent bien entendu ne pas tre inutiles : trop de commentaires tue le commentaire, parce que les choses importantes sont dans ce cas noyes dans les banalits. Les exemples de ce document utiliseront bien entendu des commentaires dont le but sera dappuyer les explications qui y sont relatives. Nous allons donc voir immdiatement comment raliser un commentaire en C et en C++. Il existe deux types de commentaires : les commentaires C et les commentaires C++. Les commentaires C permettent de mettre tout un bloc de texte en commentaire. Ils sont bien entendu disponibles en C++. Les commentaires C++ en revanche ne sont, normalement, pas disponibles en C, sauf extensions du compilateur C. Ils ne permettent de commenter que la n dune ligne, mais sont gnralement plus pratiques utiliser. Les commentaires C commencent avec la squence barre oblique - toile ( /* ). Ils se terminent avec la squence inverse : une toile suivie dune barre oblique ( */ ). Exemple 1-2. Commentaire C
/* Ceci est un commentaire C */

Du fait que la n du commentaire est parfaitement identie par une squence de caractres, ils peuvent stendre sur plusieurs lignes. Les commentaires C++ en revanche sarrtent la n de la ligne courante, et de ce fait nont pas de squence de terminaison. Ils permettent de commenter plus facilement les actions effectues sur la ligne courante, avant le commentaire, et forcent ainsi souvent le programmeur plus de concision

Chapitre 1. Premire approche du C/C++ dans ses commentaires (ce qui est une bonne chose). Les commentaires C++ commencent par la squence constitue de deux barres obliques. Par exemple : Exemple 1-3. Commentaire C++
ligne de code quelconque ligne de code suivante // Ceci est un commentaire C++

Les commentaires C++ peuvent tre placs dans un commentaire C. Ceci est trs pratique lorsque lon veut supprimer toute un bloc de code source comment avec des commentaires C++, pour tester une variante par exemple. En revanche, les commentaires C ne peuvent pas tre placs dans un commentaire de porte plus gnrale, parce quils ne sont pas rcursifs. En effet, lors de lajout dun commentaire C, toute ouverture de commentaires C dans le bloc comment est elle-mme mise en commentaire. De ce fait, le commentaire sarrte ds la premire squence toile-barre oblique rencontre, et la squence de terminaison du commentaire de porte globale provoque une erreur de compilation. Par exemple, dans le code suivant, la deuxime squence */ est analyse par le compilateur, qui y voit une erreur de syntaxe (les commentaires C++ sont utiliss pour expliquer lexemple) :
/* Dbut de commentaire englobant... // Ligne commente, avec son commentaire original : a = 2+3; /* On affecte 5 a */ // partir dici, le commentaire C est fini ! // Fin suppose du commentaire englobant, interprt // comme les oprateurs de multiplication et de division : */

Ce problme peut-tre gnant, et on verra lusage que la possibilit de commenter un commentaire C++ par un commentaire C incite fortement utiliser les commentaires C++ en priorit.

1.4. Les variables


Les variables sont des reprsentations de zones de la mmoire utilise par le programme pour stocker son tat. Elles sont essentielles, puisquelles contiennent les donnes sur lesquelles les programmes travaillent.

1.4.1. Dnition des variables


Toute variable doit avoir un nom qui lidentie (on appelle un tel nom un identicateur ), grce auquel elle pourra tre manipule dans la suite du programme. Comme nous lavons dit plus haut, les variables C/C++ disposent galement dun type, ce qui signie que des informations relatives la nature de la variable sont associes ce nom par le compilateur, an que celui-ci puisse dterminer les oprations qui peuvent tre appliques la variable. La dclaration dune variable permet donc dassocier un identicateur cette variable et dindiquer au compilateur son type. En pratique, ces deux oprations sont ralises en mme temps que la rservation de la mmoire ncessaire au stockage de la variable, lors de sa dnition. La dnition dune variable est donc lopration qui permet la fois de la dclarer et de lui attribuer une zone mmoire.

Chapitre 1. Premire approche du C/C++ Nous verrons la diffrence importante entre la dclaration et la dnition lorsque nous raliserons des programmes dont le code source est rparti dans plusieurs chiers. Les noms didenticateurs doivent tre choisis conformment aux rgles du langage. Le C++ est relativement permissif quant aux caractres utilisables dans les noms didenticateurs, mais en pratique les compilateurs ne le sont pas. Par consquent, on se restreindra aux caractres alphanumriques non accentus et au caractre de soulignement bas (caractre _), sachant que les chiffres ne peuvent pas tre utiliss au dbut dun nom. De plus, un certain nombre de noms sont rservs et ne peuvent tre utiliss (mots cls et quelques constantes ou noms rservs par le langage). Vous trouverez la liste des mots cls dans lAnnexe A. Par exemple, les noms couleur_objet , AgeBestiole et Parametre5 sont acceptables, mais pas 6tron , rsultat , int ou EXIT_SUCCESS . La syntaxe utilise pour dnir une variable simple est la suivante :
type identificateur;

o type est le type de la variable et identificateur est son nom. Il est possible de crer et dinitialiser une srie de variables ds leur cration avec la syntaxe suivante :
type identificateur[=valeur][, identificateur[=valeur][...]];

Exemple 1-4. Dnition de variables


int i=0, j=0; int somme; /* Dfinit et initialise deux entiers 0 */ /* Dclare une autre variable entire */

En C, les variables peuvent dnies en dehors de toute fonction (variables dites globales), au dbut des fonctions (variables locales la fonction), ou au dbut des blocs dinstructions. Le C++ est plus souple et autorise gnralement la dnition de variables galement au sein dun bloc dinstructions. Cela permet de ne dnir une variable temporaire que l o lon en a besoin, donc de rduire la globalit de ces variables et de minimiser les erreurs de programmation dues aux effets de bords. De plus, cela permet dviter davoir connatre les variables temporaires ncessaires lcriture du morceau de code qui suit leur dnition, et accrot la lisibilit des programmes. La dnition dune variable ne suft pas, en gnral, linitialiser. Les variables non initialises contenant des valeurs alatoires, il faut viter de les utiliser avant une initialisation correcte. Initialiser les variables que lon dclare leur valeur par dfaut est donc une bonne habitude prendre. Linitialisation est dailleurs obligatoire pour les variables constantes que lon peut dclarer avec le mot cl const, car ces variables ne peuvent pas tre modies aprs leur dnition. Ce mot cl sera prsent en dtail ultrieurement, dans la Section 3.6.

1.4.2. Les types de base du C/C++


Nous avons dj vu le type de donnes int dans notre premier programme pour la valeur de retour de la fonction main, et dans lexemple de dclaration de variables prcdent. Le langage C/C++ fournit bien entendu dautres types de donnes, ainsi que la possibilit de dnir ses propres types. Nous verrons la manire de procder pour cela dans le Section 3.2. En attendant, voyons les types de donnes les plus simples du langage :

10

Chapitre 1. Premire approche du C/C++ Tableau 1-1. Types de base du langage Type void Description Le type vide. Ce type est utilis pour spcier le fait quil ny a pas de valeur possible pour lentit de ce type Cela a une utilit, entre autres, pour faire des procdures (fonctions ne renvoyant aucune valeur). Type des valeurs boolennes. Ce type est utilis pour reprsenter la vracit dune expression. Il ne peut valoir que true (expression vraie) ou false (expression fausse). Ce type de donnes nest disponible quen C++. Type des caractres. Type des caractres tendus. Utilis pour reprsenter, gnralement en Unicode, les caractres qui ne sont pas reprsentables dans un jeu de caractres 8 bits. Type de base des entiers signs. Type de base des nombres en virgule ottante. Type des nombres en virgule ottante en double prcision.

bool

char wchar_t

int oat double

Les types de donnes bool, char, wchar_t et int peuvent tre utiliss pour stocker des valeurs entires (le cas du type bool tant dgnr, puisque seules deux valeurs sont utilisables !). De ce fait, on dit que ce sont des types intgraux. La conversion dun entier en boolen se fait avec la rgle selon laquelle 0 est faux et toute valeur non nulle est vraie. Inversement, false est considr comme valant 0 et true comme valant 1. Les types de donnes oat et double sont gnralement utiliss pour les calculs numriques. Ils permettent de reprsenter des nombres rels, sous la forme de leurs chiffres les plus signicatifs et dun exposant exprimant la position de lunit par rapport ces chiffres (par exemple 3,14159 ou 1,535x10^12 . De ce fait, cet exposant indique la position de la virgule du nombre rel reprsent par rapport aux chiffres signicatifs. Comme cette position dpend de cet exposant, cette position nest pas xe par le type de donnes en soi. Cest la raison pour laquelle on appelle ces nombres des nombres en virgule ottante.
Note : Les types ottants sont trs pratiques pour les calculs numriques, mais ils souffrent de problmes darrondis et derreurs de reprsentation et de conversion trs gnants. En particulier, les nombres ottants ne permettent pas de stocker les nombres dont la partie dcimale est innie (comme 1/3 par exemple). En effet, lordinateur est une machine nie, et il ne peut stocker une innit de nombres aprs la virgule. Il y a donc ncessairement une erreur de reprsentation du type de donnes utilis pour les nombres rels, quel que soit le type utilis ! De ces erreurs de reprsentation dcoulent les erreurs darrondi, qui font que deux nombres supposs gaux ne le seront qu un epsilon prs relativement faible. Les nombres ottants sont donc particulirement pnibles comparer. Aux erreurs de reprsentation sajoutent les erreurs de conversion. La reprsentation interne des nombres ottants ne permet parfois mme pas de stocker un nombre qui, dans notre criture dcimale, tombe juste (par exemple 1.2 ne tombe pas juste en reprsentation binaire, et 1.2 * 10 - 12 ne fait pas 0...). De mme, les conversions de et vers les reprsentations textuelles des nombres ottants induisent trs souvent des erreurs de conversion. De ce fait, on nutilisera les nombres ottants qu bon escient. Ils ne faut jamais utiliser de type en virgule ottante pour stocker des donnes prenant une plage de valeurs discrtes ou dans les programmes ncessitant une grande rigueur numrique. Ainsi, on ne stockera jamais un montant montaire dans un ottant (bien que de nombreux programmeurs lont fait et continueront sans doute le faire... et souffrir pour des problmes quils pouvaient viter). On ne stockera non

11

Chapitre 1. Premire approche du C/C++


plus jamais une dure informatique ou un temps dans un nombre ottant (bien que nombre de programmes sous Windows le fassent par exemple).

ces types fondamentaux sajoutent des types drivs laide des mots cls long et short . Ces types drivs se distinguent des types de base par ltendue des plages de valeurs quils peuvent stocker. Par exemple, le type long int est plus grand que le type int. Inversement, le type short int est plus court que le type int. De mme, le type long double est plus grand que le type double. Il est galement possible de nutiliser que des plages de valeurs positives pour les types intgraux (sauf pour les boolens). Pour cela, on peut utiliser les mots cls signed et unsigned. Les types non signs ne peuvent contenir de valeurs ngatives, mais leur valeur maximale est jusqu deux fois plus grande que pour leur homologues signs. Nous verrons plus en dtail les limites et les plages de valeurs des types de donnes dans le Chapitre 3. Exemple 1-5. Types signs et non signs
unsigned char signed char unsigned wchar_t signed wchar_t unsigned short int signed short int unsigned int signed int unsigned long int long unsigned int

Lorsque le type auquel ils sappliquent est le type int, les mots cls signed, unsigned, short et long peuvent tre utiliss seuls, sans le nom de type. Ainsi, le type short int peut tre not simplement short.
Note : Le type int est sign, il est donc inutile de le prciser. En revanche, les types char et wchar_t nont pas de signe proprement parler en C++, et leur signe est indtermin en C (cest-dire quil dpend du compilateur ou des options de compilation). Par consquent, on prcisera toujours le signe lorsque lon dsirera utiliser un type de caractres sign. Il ny a pas de type court pour les types rels. De plus, le type long du type oat est le type double. Le mot cl short nest donc pas utilisable avec les types rels, et le mot cl long ne lest pas avec le type oat. Il ny a pas non plus de type de base permettant de manipuler les chanes de caractres. En C/C++, les chanes de caractres sont en ralit des tableaux de caractres. Vous trouverez plus loin pour de plus amples informations sur les chanes de caractres et les tableaux.

1.4.3. Notation des valeurs


La manire de noter les valeurs numriques dpend de leur type.

12

Chapitre 1. Premire approche du C/C++

1.4.3.1. Notation des valeurs boolennes


Les valeurs boolennes se notent de manire extrmement simple, puisque seules deux valeurs sont autorises. La valeur fausse se note false et la valeur vraie true :
bool vrai = true; bool faux = false;

1.4.3.2. Notation des valeurs entires


Pour les entiers, il est possible de spcier les valeurs numriques en base dix, en base seize ou en base huit. Lcriture des entiers se fait, en fonction de la base choisie, de la manire suivante :

Avec les chiffres de 0 9 et les signes + (facultatif) et - pour la base 10 (notation dcimale). Exemple 1-6. Notation des entiers en base 10
int i = 12354; int j = -2564;

Avec les chiffres 0 9 et A F ou a f pour la base 16 (avec les conventions A=a=10, B=b=11, ... F=f=15). Les entiers nots en hexadcimal devront toujours tre prcds de 0x . Les nombres hexadcimaux ne sont pas signs. Exemple 1-7. Notation des entiers en base 16
int i_hexa = 0x1AE;

Avec les chiffres de 0 7 pour la base 8 (notation octale). Les nombres octaux doivent tre prcds dun 0, et ne sont pas non plus signs. Exemple 1-8. Notation des entiers en base 8
int i_oct1 = 01; int i_oct2 = 0154;

1.4.3.3. Notation des valeurs en virgule ottantes


Les nombres virgule ottante (pseudo rels) se notent de la manire suivante :
[signe] chiffres [.[chiffres]][e|E [signe] exposant][f|l]

o signe indique le signe. On emploie les signes + (facultatif) et - aussi bien pour la mantisse que pour lexposant. e ou E permet de donner lexposant du nombre ottant. Lexposant est facultatif. Si on ne donne pas dexposant, on doit donner des chiffres derrire la virgule avec un point et ces chiffres. Le sufxe f permet de prciser si le nombre est de type oat, et le sufxe l permet de

13

Chapitre 1. Premire approche du C/C++ prciser si le nombre est de type long double. En labsence de qualicatif, les valeurs ottantes sont de type double. Les chiffres aprs la virgule sont facultatifs, mais pas le point. Si on ne met ni le point, ni la mantisse, le nombre est un entier dcimal. Exemple 1-9. Notation des ottants
float f = -123.56f; double d = 12e-12; double i = 2.;

2 est entier, 2. est ottant.

1.4.3.4. Notation des caractres


Les caractres se notent entre guillemets simples :
char c1 = A; char c2 = c; char c3 = (;

On peut obtenir un caractre non accessible au clavier en donnant son code en octal, prcd du caractre \. Par exemple, le caractre A peut aussi tre not \101. Remarquez que cette notation est semblable la notation des nombres entiers en octal, et que le 0 initial est simplement remplac par un \. Il est aussi possible de noter les caractres avec leur code en hexadcimal, laide de la notation \xNN , o NN est le code hexadcimal du caractre. Il existe galement des squences dchappement particulires qui permettent de coder certains caractres spciaux plus facilement. Les principales squences dchappement sont les suivantes : Squence \a \b \f \n \r \t \w Signication Bip sonore Retour arrire Dbut de page suivante Saut de ligne (sans retour de chariot) Retour la ligne (sans saut de ligne) Tabulation horizontale Tabulation verticale

Dautres squences dchappement sont disponibles, an de pouvoir reprsenter les caractres ayant une signication particulire en C : Squence \\ \" \ Signication Le caractre \ Le caractre " Le caractre

14

Chapitre 1. Premire approche du C/C++ Enn, les valeurs des caractres larges de type wchar_t sont notes de la mme manire que les valeurs des caractres simples, mais doivent tre prcdes de la lettre L. Par exemple :
wchar_t c1 = LA; wchar_t c2 = Lc; wchar_t c3 = L(;

1.4.3.5. Notation des chanes de caractres


En C/C++, les chanes de caractres apparaissent comme des squences de caractres conscutifs, termines par un caractre nul. Ces chanes peuvent tre crites en spciant lensemble des caractres de la chane entre doubles guillemets :
"Exemple de chane de caractres..."

Vous remarquerez que le caractre nul terminal nest pas spci. Le compilateur lajoute automatiquement, il ne faut donc pas le faire. Les caractres spciaux peuvent tre utiliss directement dans les chanes de caractres constantes :
"Ceci est un saut de ligne :\nCeci est la ligne suivante."

Note : Attention : du fait que le caractre nul est utilis en tant que marqueur de n de chane, il ne faut pas lutiliser dans une chane de caractres. Mme si le compilateur prendra en compte les caractres qui suivent ce caractre nul, les fonctions de la bibliothque C ne parviendront pas manipuler correctement la chane et ne traiteront que les caractres prcdant le caractre nul.

Si une chane de caractres constante est trop longue pour tenir sur une seule ligne, on peut concatner plusieurs chanes en les juxtaposant :
"Ceci est la premire chane " "ceci est la deuxime."

produit la chane de caractres complte suivante :


"Ceci est la premire chane ceci est la deuxime."

Vous noterez que dans ce cas le compilateur ninsre pas de caractre nul entre les deux chanes. Enn, les chanes de caractres de type wchar_t doivent tre prcdes du prxe L :
L"Ceci est une chane de wchar_t."

15

Chapitre 1. Premire approche du C/C++

1.5. Les instructions


Les instructions sont les lments de base du programme. Tout les traitements des programmes C/C++ sont donc effectus par des suites dinstructions. Il existe plusieurs types dinstructions en C/C++ :

les instructions simples ; les instructions composes ; les structures de contrle.

1.5.1. Les instructions simples


Les instructions simples sont identies par le point virgule. Cest ce caractre qui marque la n dune instruction. Le programme evalue lexpression dlimite par linstruction prcdente et ce point virgule. Cette expression peut tre vide, le rsultat de linvocation dune fonction, ou une combinaison dexpressions plus simples par des oprateurs. Exemple 1-10. Exemple dinstructions
; printf("Hello World!\n"); i = j + m; /* Instruction rduite sa plus simple expression (ne fait rien) */ /* Instruction appelant une fonction */ /* Instruction valuant une combinaison doprateurs */

Les principales oprations que lon peut raliser dans une expression sont les suivantes : Tableau 1-2. Oprateurs du langage C/C++ Oprateur
a = b a + b a - b a * b a / b

Signication Oprateur daffectation. a reoit la valeur de b. Renvoie la valeur de b. Oprateur daddition. Renvoie la valeur de la somme de a et de b. Oprateur de soustraction. Renvoie la valeur de la soustraction de a et de b. Oprateur de multiplication. Renvoie la valeur du produit de a et de b. Oprateur de division. Pour les entiers, renvoie la valeur de la division euclidienne (division entire) de a et de b. Pour les nombres virgule ottante, renvoie la valeur de la division de a et de b. Oprateur de reste de division. Cet oprateur renvoie la valeur du reste de la division euclidienne des entiers a et b. Oprateur de conjonction. Renvoie la valeur du et logique bit bit de a et b. Chaque bit du rsultat est calcul partir des bits correspondants de a et de b avec la rgle suivante : 1 et 1 = 1, (0 et x) = (x et 0) = 0. Oprateur de disjonction. Renvoie la valeur du ou logique bit bit de a et de b. Chaque bit du rsultat est calcul partir des bits correspondants de a et de b avec la rgle suivante : 0 ou 0 = 0, (1 ou x) = (x ou 1) = 1.

a % b a & b ou a bitand b a | b ou a bitor b

16

Chapitre 1. Premire approche du C/C++ Oprateur


a ^ b ou a xor b

Signication Oprateur de disjonction exclusive. Renvoie la valeur du ou exclusif logique bit bit (gnralement appel xor ) de de a et de b. Chaque bit du rsultat est calcul partir des bits correspondants de a et de b avec la rgle suivante : 1 xor 1 = 0, 0 xor 0 = 0 et (1 xor 0) = (0 xor 1) = 1. Oprateur de ngation. Renvoie la valeur dont chaque bit est linverse du bit correspondant de a (0 devient 1 et vice versa). Oprateur de dcallage binaire gauche. Renvoie la valeur de a dont les bits ont t dcalls vers les bits de poids fort b fois. Les bits de poids faible insrs sont nuls. Comme lajout dun 0 la droite dun nombre revient le multiplier par sa base, cet oprateur permet de multiplier a par deux la puissance b (sil ny a pas de dbordement des bits par la gauche bien entendu). Oprateur de dcallage binaire droite. Renvoie la valeur de a dont les bits ont t dcalls vers les bits de poids faible b fois. Les bits de poids fort insrs sont nuls. Cela revient diviser a par deux la puissance b. Oprateur de pr-incrmentation. Ajoute 1 a et retourne le rsultat obtenu. Oprateur de post-incrmentation. Ajoute 1 a et renvoie la valeur prcdente de celui-ci. Oprateur de pr-dcrmentation. te 1 de a et renvoie la valeur obtenue. Oprateur de post-dcrmentation. te 1 de a et renvoie la valeur prcdente de celui-ci. Oprateur ternaire. value la valeur de lexpression a et, selon quelle est vraie ou non, value lexpression c ou lexpression b. Cet oprateur permet donc de faire un test et de choisir une valeur ou une autre selon le rsultat de ce test. Oprateur virgule. value les expressions a et b et retourne la valeur de b.

~a ou compl a a << b

a >> b

++a a++ --a a-a ? b : c

a , b

On notera que les affectations ne sont pas des instructions. Ce sont bien des oprations, dont la valeur est la valeur affecte. Il est donc possible de rutiliser cette valeur dans une expression plus complexe. Un cas intressant est lutilisation de cette valeur pour faire une autre affectation : il est ainsi possible deffectuer des affectations multiples :
i=j=k=m=0; /* Annule les variables i, j, k et m. */

De tous les oprateurs, cest sans doute loprateur daffectation qui est le plus utilis. Cest pour cette raison que le C et le C++ proposent des oprateurs daffectations composes. Une affectation compose est une opration permettant de raliser en une seule tape une opration normale et laffectation de son rsultat dans la variable servant de premier oprande. Les affectations composes utilisent la syntaxe suivante :
variable op_aff valeur

o op_aff est lun des oprateurs suivants : +=, -=, *=, etc. Cette syntaxe est strictement quivalente :
variable = variable op valeur

et permet donc de modier la valeur de variable en lui appliquant loprateur op.

17

Chapitre 1. Premire approche du C/C++ Exemple 1-11. Affectation compose


i*=2; /* Multiplie i par 2 : i = i * 2. */

Note : Les oprateurs &=, |= et ^= peuvent galement scrire respectivement and_eq, or_eq et xor_eq.

Les oprateurs dincrmentation et de dcrmentation ++ et -- peuvent sappliquer comme des prxes ou des sufxes sur les variables. Lorsquils sont en prxe, la variable est incrmente ou dcrmente, puis sa valeur est renvoye. Sils sont en sufxe, la valeur de la variable est renvoye, puis la variable est incrmente ou dcrmente. Par exemple :
int i=2,j,k; j=++i; k=j++; /* la fin de cette instruction, i et j valent 3. */ /* la fin de cette ligne, k vaut 3 et j vaut 4. */

Note : On prendra garde nutiliser les oprateurs dincrmentation et de dcrmentation postxs que lorsque cela est rellement ncessaire. En effet, ces oprateurs doivent contruire un objet temporaire pour renvoyer la valeur de la variable avant incrmentation ou dcrmentation. Si cet objet temporaire nest pas utilis, il est prfrable dutiliser les versions prxes de ces oprateurs.

Loprateur ternaire dvaluation conditionnelle ?: est le seul oprateur qui demande 3 paramtres ( part loprateur fonctionnel () des fonctions, qui admet n paramtres, et que lon dcrira plus tard). Cet oprateur permet de raliser un test sur une condition et de calculer une expression ou une autre selon le rsultat de ce test. La syntaxe de cet oprateur est la suivante :
test ? expression1 : expression2

Dans cette syntaxe, test est valu en premier. Son rsultat doit tre boolen ou entier. Si test est vrai (ou si sa valeur est non nulle), expression1 est calcule et sa valeur est renvoye. Sinon, cest la valeur de expression2 qui est renvoye. Par exemple, lexpression :
Min=(i<j)?i:j;

calcule le minimum de i et de j. Loprateur virgule, quant lui, permet dvaluer plusieurs expressions successivement et de renvoyer la valeur de la dernire expression. La syntaxe de cet oprateur est la suivante :
expression1,expression2[,expression3[...]]

o expression1, expression2, etc. sont les expressions valuer. Les expressions sont values de gauche droite, puis le type et la valeur de la dernire expression sont utiliss pour renvoyer le rsultat. Par exemple, lissue des deux lignes suivantes :
double r = 5;

18

Chapitre 1. Premire approche du C/C++


int i = r*3,1; r vaut 5 et i vaut 1. r*3 est calcul pour rien. Note : Ces deux derniers oprateurs peuvent nuire gravement la lisibilit des programmes. Il est toujours possible de rcrire les lignes utilisant loprateur ternaire avec un test (voir le Chapitre 2 pour la syntaxe des tests en C/C++). De mme, on peut toujours dcomposer une expression utilisant loprateur virgule en deux instructions distinctes. Ce dernier oprateur ne devra donc jamais tre utilis.

1.5.2. Les instructions compose


Il est possible de crer des instructions composes, constitues dinstructions plus simples. Les instructions composes se prsentent sous la forme de blocs dinstructions o les instructions contenues sont encadres daccolades ouvrantes et fermantes (caractres { et }). Exemple 1-12. Instruction compose
{ i=1; j=i+3*g; }

Note : Un bloc dinstructions est considr comme une instruction unique. Il est donc inutile de mettre un point virgule pour marquer linstruction, puisque le bloc lui-mme est une instruction.

1.5.3. Les structures de contrle


Enn, il existe tout un jeu dinstructions qui permettent de modier le cours de lexcution du programme, comme les tests, les boucles et les sauts. Ces instructions seront dcrites en dtail dans le chapitre traitant des structures de contrle.

1.6. Les fonctions et les procdures


Les fonctions sont des groupements dinstructions qui peuvent prendre des paramtres en entre et qui retournent une valeur. Elles peuvent tre appeles plusieurs fois, avec des valeurs de paramtres diffrentes. Elles permettent donc dimplmenter un algorithme de calcul et de le rutiliser pour diffrents jeux de paramtres. Les procdures sont des groupements dinstructions qui effectuent une tche, mais qui nont a priori pas pour but de calculer quelque chose. Elles ne retournent donc pas de valeur. Toutefois, elles peuvent prendre des arguments, dont les valeurs permettent den modier le comportement. Comme on la vu, le programme principal est lui-mme constitu dune fonction spciale, la fonction main. Cette fonction peut appeler dautres fonctions ou procdures, qui elles-mmes peuvent appeler

19

Chapitre 1. Premire approche du C/C++ encore dautres fonctions. Lexcution du programme constitue donc une suite dappels de fonctions et de procdures.

1.6.1. Dnition des fonctions et des procdures


La dnition des fonctions se fait comme suit :
type identificateur(paramtres) { ... /* Instructions de la fonction. */ }

type est le type de la valeur renvoye, identificateur est le nom de la fonction, et paramtres est une liste de paramtres. Les rgles de nommage des identicateurs de fonctions sont les mmes que pour les identicateurs de variables (voir Section 1.4). La syntaxe de la liste de paramtres est la suivante :
type variable [= valeur] [, type variable [= valeur] [...]]

o type est le type du paramtre variable qui le suit et valeur sa valeur par dfaut. La valeur par dfaut dun paramtre est la valeur que ce paramtre prend si aucune valeur ne lui est attribue lors de lappel de la fonction.
Note : Linitialisation des paramtres de fonctions nest possible quen C++, le C naccepte pas cette syntaxe.

La valeur de la fonction renvoyer est spcie en utilisant le mot cl return, dont la syntaxe est :
return valeur;

Exemple 1-13. Dnition de fonction


int somme(int i, int j) { return i+j; }

En C/C++, les procdures sont ralises simplement en dnissant une fonction qui ne retourne pas de valeur. Pour cela, on utilise le type de retour void. Dans ce cas, il nest pas ncessaire de mettre une instruction return en n de procdure. Cela est cependant faisable, il suft de ne pas donner de valeur dans linstruction return. Si une fonction ou une procdure ne prend pas de paramtres dentre, sa liste de paramtres peut tre omise. Il est galement possible de spcier explicitement que la fonction ne prend pas de paramtres en utilisant le mot cl void. . Exemple 1-14. Dnition de procdure
void rien() { return; /* Fonction nattendant pas de paramtres */ /* et ne renvoyant pas de valeur. */ /* Cette ligne est facultative. */

20

Chapitre 1. Premire approche du C/C++


}

Note : Il est spci dans la norme du C++ que la fonction main ne doit pas renvoyer le type void. En pratique cependant, beaucoup de compilateurs lacceptent galement.

1.6.2. Appel des fonctions et des procdures


Lappel dune fonction ou dune procdure se fait en donnant son nom, puis les valeurs de ses paramtres entre parenthses. Attention ! Sil ny a pas de paramtres, il faut quand mme mettre les parenthses, sinon la fonction nest pas appele. Exemple 1-15. Appel de fonction
int i=somme(2,3); rien();

Comme vous pouvez le constater, une fonction peut tre utilise en lieu et place de sa valeur de retour dans une expression. Dans lexemple prcdent, le rsultat de la fonction somme est affect la variable i. En revanche, les procdures doivent tre utilises dans des instructions simples, puisquelles ne retournent aucune valeur. En C++ (et uniquement en C++), les paramtres qui ont des valeurs par dfaut dans la dclaration de la fonction ou de la procdure peuvent tre omis lors de lappel. Les valeurs que ces paramtres auront dans la fonction seront alors les valeurs par dfaut indiques dans la dclaration. Ds quun paramtre est manquant lors de lappel, tous les paramtres qui le suivent doivent eux aussi tre omis et prendre leur valeur par dfaut. Autrement dit, le C++ ne permet pas deffectuer des appels en spciant les valeurs des paramtres explicitement par leurs noms, seule la position des paramtres dappel indique de quel paramtre dans la dnition de la fonction il sagit. Il en rsulte que seuls les derniers paramtres dune fonction peuvent avoir des valeurs par dfaut. Par exemple :
int test(int i = 0, int j = 2) { return i/j; } int main(void) { int resultat1 = test(8); int resultat2 = test(); return EXIT_SUCCESS; }

/* Appel de test(8, 2) */ /* Appel de test(0, 2) */

Lappel de la fonction test(8) est valide. Comme on ne prcise pas le dernier paramtre, j est initialis 2. Le premier rsultat est donc 4. De mme, lappel test() est valide : dans ce cas i vaut 0 et j vaut 2. En revanche, il est impossible dappeler la fonction test en ne prcisant que la valeur de j. Enn, lexpression int test(int i=0, int j) {...} serait invalide, car si on ne passait pas deux paramtres, j ne serait pas initialis. Il est possible, pour une fonction ou une procdure, de sappeler soi-mme, soit directement, soit indirectement via une autre fonction. De telles fonctions sont appeles des fonctions rcursives.

21

Chapitre 1. Premire approche du C/C++ Les appels rcursifs doivent tre limits, car lors de chaque appel, la liste des paramtres fournis est mmorise. Un appel rcursif inni induit donc une consommation mmoire innie (ce qui se traduit gnralement par un dbordement de pile au niveau du processus). Il est donc ncessaire, lorsquon ralise une fonction ou une procdure rcursive, davoir une condition de sortie qui sera toujours vrie au bout dun certain nombre dappel. Larchtype de la fonction rcursive est la fonction factorielle, qui calcule le produit des n premiers nombres entiers (avec pour convention que factorielle de 0 et de 1 vallent 1) :
int factorielle(int n) { return (n > 1) ? n * factorielle(n - 1) : 1; }

Note : Nous verrons dans le Chapitre 2 la manire dcrire un test sans avoir recours loprateur ternaire ?:. Comme lcriture prcdente le montre, cet oprateur ne facilite que trs rarement la lisibilit dun programme...

1.6.3. Notion de dclaration


Toute fonction ou procdure doit tre dclare avant dtre appele pour la premire fois. La dnition peut faire ofce de dclaration, toutefois il peut se trouver des situations o une fonction ou une procdure doit tre appele dans une autre fonction dnie avant elle. Comme cette fonction nest pas dnie au moment de lappel, elle doit tre dclare. De mme, il est courant davoir dni une fonction dans un chier et de devoir faire lappel de cette fonction partir dun autre chier. Il est donc l aussi ncessaire de dclarer cette fonction. Le rle des dclarations est donc de signaler lexistence des fonctions et des procdures au compilateur an de pouvoir les utiliser, tout en reportant leurs dnitions plus loin ou dans un autre chier. Cela permet de vrier que les paramtres fournis une fonction correspondent bien ce quelle attend, et que la valeur de retour est correctement utilise aprs lappel. La syntaxe de la dclaration dune fonction est la suivante :
type identificateur(paramtres);

o type est le type de la valeur renvoye par la fonction (ventuellement void), identificateur est son nom et paramtres la liste des types des paramtres que la fonction admet, ventuellement avec leurs valeurs par dfaut, et spars par des virgules. Exemple 1-16. Dclaration de fonction
int Min(int, int); /* Dclaration de la fonction minimum */ /* dfinie plus loin. */

/* Fonction principale. */ int main(void) { int i = Min(2,3); /* Appel la fonction Min, dj dclare. */ return 0; }

22

Chapitre 1. Premire approche du C/C++

/* Dfinition de la fonction min. */ int Min(int i, int j) { return i<j ? i : j; }

Si lon donne des valeurs par dfaut diffrentes aux paramtres dans plusieurs dclarations, les valeurs par dfaut utilises sont celles de la dclaration visible lors de lappel. Si plusieurs dclarations sont visibles et entrent en conit au niveau des valeurs par dfaut des paramtres de la fonction ou de la procdure, le compilateur ne saura pas quelle dclaration utiliser et signalera une erreur la compilation. Il est possible de complter la liste des valeurs par dfaut de la dclaration dune fonction ou dune procdure dans sa dnition. Dans ce cas, les valeurs par dfaut spcies dans la dnition ne doivent pas entrer en conit avec celles spcies dans la dclaration visible au moment de la dnition, faute de quoi le compilateur signalera une erreur.

1.6.4. Surcharge des fonctions


Il est interdit en C de dnir plusieurs fonctions qui portent le mme nom. En C++, cette interdiction est leve, moyennant quelques prcautions. Le compilateur peut diffrencier deux fonctions en regardant le type des paramtres quelle reoit. La liste de ces types sappelle la signature de la fonction. En revanche, le type du rsultat de la fonction ne permet pas de lidentier, car le rsultat peut ne pas tre utilis ou peut tre converti en une valeur dun autre type avant dtre utilis aprs lappel de cette fonction. Il est donc possible de faire des fonctions de mme nom (on les appelle alors des surcharges) si et seulement si toutes les fonctions portant ce nom peuvent tre distingues par leurs signatures. La surcharge qui sera appele sera celle dont la signature est la plus proche des valeurs passes en paramtre lors de lappel. Exemple 1-17. Surcharge de fonctions
float test(int i, int j) { return (float) i+j; } float test(float i, float j) { return i*j; }

Ces deux fonctions portent le mme nom, et le compilateur les acceptera toutes les deux. Lors de lappel de test(2,3), ce sera la premire qui sera appele, car 2 et 3 sont des entiers. Lors de lappel de test(2.5,3.2), ce sera la deuxime, parce que 2.5 et 3.2 sont rels. Attention ! Dans un appel tel que test(2.5,3), le ottant 2.5 sera converti en entier et la premire fonction sera appele. Il convient donc de faire trs attention aux mcanismes de surcharge du langage, et de vrier les rgles de priorit utilises par le compilateur. On veillera ne pas utiliser des fonctions surcharges dont les paramtres ont des valeurs par dfaut, car le compilateur ne pourrait pas faire la distinction entre ces fonctions. Dune manire gnrale, le compilateur dispose dun ensemble de rgles (dont la prsentation dpasse le cadre de ce livre) qui

23

Chapitre 1. Premire approche du C/C++ lui permettent de dterminer la meilleure fonction appeler tant donn un jeu de paramtres. Si, lors de la recherche de la fonction utiliser, le compilateur trouve des ambiguts, il gnre une erreur. Le C++ considre les types char et wchar_t comme des types part entire, utiliss pour stocker des caractres. Ils nont donc pas de signe en soi, et le compilateur considre donc comme des types distincts les versions signes et non signes du type de base. Cela signie que le compilateur traitent les types char, unsigned char et signed char comme des types diffrents, et il en est de mme pour les types wchar_t, signed wchar_t et unsigned wchar_t. Cette distinction est importante dans la dtermination de la signature des fonctions, puisquelle permet de ce fait de faire des surcharges de fonctions pour ces types de donnes.

1.6.5. Fonctions inline


Le C++ dispose du mot clef inline, qui permet de modier la mthode dimplmentation des fonctions. Plac devant la dclaration dune fonction, il propose au compilateur de ne pas instancier cette fonction. Cela signie que lon dsire que le compilateur remplace lappel de la fonction par le code correspondant. Si la fonction est grosse ou si elle est appele souvent, le programme devient plus gros, puisque la fonction est rcrite chaque fois quelle est appele. En revanche, il devient nettement plus rapide, puisque les mcanismes dappel de fonctions, de passage des paramtres et de rcupration de la valeur de retour sont ainsi vits. De plus, le compilateur peut effectuer des optimisations additionnelles quil naurait pas pu faire si la fonction ntait pas inline. En pratique, on rservera cette technique pour les petites fonctions appeles dans du code devant tre rapide ( lintrieur des boucles par exemple), ou pour les fonctions permettant de lire des valeurs dans des variables. Cependant, il faut se mer. Le mot cl inline est une autorisation donne au compilateur pour faire des fonctions inline. Il ny est pas oblig. La fonction peut donc trs bien tre implmente classiquement. Pire, elle peut tre implmente des deux manires, selon les mcanismes doptimisation du compilateur. De mme, le compilateur peut galement inliner automatiquement et de manire transparente les fonctions normales an doptimiser les performances du programme. De plus, il faut connatre les restrictions des fonctions inline :

elles ne peuvent pas tre rcursives ; elles ne sont pas instancies, donc on ne peut pas faire de pointeur sur une fonction inline.

Si lune de ces deux conditions nest pas vrie pour une fonction, le compilateur limplmentera classiquement (elle ne sera donc pas inline). Enn, du fait que les fonctions inline sont insres telles quelles aux endroits o elles sont appeles, il est ncessaire quelles soient compltement dnies avant leur appel. Cela signie que, contrairement aux fonctions classiques, il nest pas possible de se contenter de les dclarer pour les appeler, et de fournir leur dnition dans un chier spar. Dans ce cas en effet, le compilateur gnrerait des rfrences externes sur ces fonctions, et ninsrerait pas leur code. Il peut, sil voit ensuite la dnition de la fonction, linstancier sans linliner, mais dans le cas contraire, le code de la fonction ne sera pas compil. Les rfrences la fonction inline ne seront donc pas rsolues ldition de liens, et le programme ne pourra pas tre gnr. Les notions de compilation dans des chiers spars et ddition de liens seront prsentes en dtail dans le Chapitre 6. Exemple 1-18. Fonction inline
inline int Max(int i, int j) {

24

Chapitre 1. Premire approche du C/C++


return i>j ? i : j; }

Pour ce type de fonction, il est tout fait justi dutiliser le mot cl inline.

1.6.6. Fonctions statiques


Par dfaut, lorsquune fonction est dnie dans un chier C/C++, elle peut tre utilise dans tout autre chier pourvu quelle soit dclare avant son utilisation. Dans ce cas, la fonction est dite externe. Il peut cependant tre intressant de dnir des fonctions locales un chier, soit an de rsoudre des conits de noms (entre deux fonctions de mme nom et de mme signature mais dans deux chiers diffrents), soit parce que la fonction est uniquement dintrt local. Le C et le C++ fournissent donc le mot cl static qui, une fois plac devant la dnition et les ventuelles dclarations dune fonction, la rend unique et utilisable uniquement dans ce chier. part ce dtail, les fonctions statiques sutilisent exactement comme des fonctions classiques. Exemple 1-19. Fonction statique
/* Dclaration de fonction statique : */ static int locale1(void); /* Dfinition de fonction statique : */ static int locale2(int i, float j) { return i*i+j; }

Les techniques permettant de dcouper un programme en plusieurs chiers sources et de gnrer les chiers binaires partir de ces chiers seront dcrites dans le chapitre traitant de la modularit des programmes.
Note : Le C++ dispose dun mcanisme plus souple disolation des entits propres un chier, via le mcanisme des espaces de nommages. De plus, le mot clef static a une autre signication en C++ dans un autre contexte dutilisation. Lemploi de ce mot cl pour rendre local un chier une fonction est donc dconseille en C++, et lon utilisera de prfrence les mcanismes prsents dans le Chapitre 10.

1.6.7. Fonctions prenant un nombre variable de paramtres


En gnral, les fonctions ont un nombre constant de paramtres. Pour les fonctions qui ont des paramtres par dfaut en C++, le nombre de paramtres peut apparatre variable lappel de la fonction, mais en ralit, la fonction utilise toujours le mme nombre de paramtres. Le C et le C++ disposent toutefois dun mcanisme qui permet au programmeur de raliser des fonctions dont le nombre et le type des paramtres sont variables. Nous verrons plus loin que les fonctions dentre / sortie du C sont des fonctions dont la liste des arguments nest pas xe, cela an de pouvoir raliser un nombre arbitraire dentres / sorties, et ce sur nimporte quel type prdni. En gnral, les fonctions dont la liste des paramtres est arbitrairement longue disposent dun critre pour savoir quel est le dernier paramtre. Ce critre peut tre le nombre de paramtres, qui peut tre fourni en premier paramtre la fonction, ou une valeur de paramtre particulire qui dtermine la n

25

Chapitre 1. Premire approche du C/C++ de la liste par exemple. On peut aussi dnir les paramtres qui suivent le premier paramtre laide dune chane de caractres. Pour indiquer au compilateur quune fonction peut accepter une liste de paramtres variable, il faut simplement utiliser des points de suspensions dans la liste des paramtres :
type identificateur(paramtres, ...)

dans les dclarations et la dnition de la fonction. Dans tous les cas, il est ncessaire que la fonction ait au moins un paramtre classique. Les paramtres classiques doivent imprativement tre avant les points de suspensions. La difcult apparat en fait dans la manire de rcuprer les paramtres de la liste de paramtres dans la dnition de la fonction. Les mcanismes de passage des paramtres tant trs dpendants de la machine (et du compilateur), un jeu de macros a t dni dans le chier den-tte stdarg.h pour faciliter laccs aux paramtres de la liste. Pour en savoir plus sur les macros, consulter le Chapitre 5. Pour linstant, sachez seulement que linclusion du chier den-tte stdarg.h vous permettra dutiliser le type va_list et les expressions va_start, va_arg et va_end pour rcuprer les arguments de la liste de paramtres variable, un un. Le principe est simple. Dans la fonction, vous devez dclarer une variable de type va_list. Puis, vous devez initialiser cette variable avec la syntaxe suivante :
va_start(variable, paramtre);

o variable est le nom de la variable de type va_list que vous venez de crer, et paramtre est le dernier paramtre classique de la fonction. Ds que variable est initialise, vous pouvez rcuprer un un les paramtres laide de lexpression suivante :
va_arg(variable, type)

qui renvoie le paramtre en cours avec le type type et met jour variable pour passer au paramtre suivant. Vous pouvez utiliser cette expression autant de fois que vous le dsirez, elle retourne chaque fois un nouveau paramtre. Lorsque le nombre de paramtres correct a t rcupr, vous devez dtruire la variable variable laide de la syntaxe suivante :
va_end(variable);

Il est possible de recommencer ces tapes autant de fois que lon veut, la seule chose qui compte est de bien faire linitialisation avec va_start et de bien terminer la procdure avec va_end chaque fois. Exemple 1-20. Fonction nombre de paramtres variable
#include <stdarg.h> /* Fonction effectuant la somme de "compte" paramtres : */ double somme(int compte, ...) { double resultat=0; /* Variable stockant la somme. */ va_list varg; /* Variable identifiant le prochain paramtre. */ va_start(varg, compte); /* Initialisation de la liste. */ while (compte!=0) /* Parcours de la liste. */ { resultat=resultat+va_arg(varg, double);

26

Chapitre 1. Premire approche du C/C++


compte=compte-1; } va_end(varg); return resultat; }

/* Terminaison. */

La fonction somme effectue la somme de compte ottants (oat ou double) et la renvoie dans un double. Pour plus de dtails sur la structure de contrle while, voir Section 2.2.2.
Note : Il existe une restriction sur les types des paramtres des listes variables darguments. En effet, seuls quelques types de donnes sont utilisables, les autres tant convertis automatiquement vers le premier type de donnes autoris capable de stocker lensemble des valeurs du paramtre. Ces oprations sont dcrites en dtail dans la Section 3.5.

1.7. Les entres / sorties en C


Nous avons prsent au dbut de ce chapitre la fonction printf dont le rle est de permettre dcrire sur le ux de sortie standard des donnes formates. Nous avions galement indiqu que nombre de programmes rcuprent les donnes sur lesquelles ils doivent travailler sur le ux dentre standard, et envoient les rsultats sur le ux de sortie standard. Nous allons donc voire prsent un peu plus en dtail les notions de ux dentre / sortie standards et dcrire de manire plus approfondie les fonctions de la bibliothque C qui permettent de raliser ces entres / sorties.

1.7.1. Gnralits sur les ux dentre / sortie


Un ux est une notion informatique qui permet de reprsenter un ot de donnes squentielles en provenance dune source de donnes ou destination dune autre partie du systme. Les ux sont utiliss pour uniformiser la manire dont les programmes travaillent avec les donnes, et donc pour simplier leur programmation. Les chiers constituent un bon exemple de ux, mais ce nest pas le seul type de ux existant : on peut traiter un ux de donnes provenant dun rseau, dun tampon mmoire ou de toute autre source de donnes ou partie du systme permettant de traiter les donnes squentiellement. Sur quasiment tous les systmes dexploitation, les programmes disposent ds leur lancement de trois ux dentre / sortie standards. Gnralement, le ux dentre standard est associ au ux de donnes provenant dun terminal, et le ux de sortie standard la console de ce terminal. Ainsi, les donnes que lutilisateur saisit au clavier peuvent tre lues par les programmes sur leur ux dentre standard, et ils peuvent afcher leurs rsultats lcran en crivant simplement sur leur ux de sortie standard. Le troisime ux standard est le ux derreur standard qui, par dfaut, est galement associ lcran, et sur lequel le programme peut crire tous les messages derreur quil dsire. La plupart des systmes permettent de rediriger les ux standards des programmes an de les faire travailler sur des donnes provenant dune autre source de donnes que le clavier, ou, par exemple, de leur faire enregistrer leurs rsultats dans un chier. Il est mme courant de raliser des pipelines de programmes ( tubes en franais), o les rsultats de lun sont envoys dans le ux dentre standard de lautre, et ainsi de suite. Les programmes qui participent un pipeline sont classiquement appels des ltres, car les plus simples dentre eux permettent de ltrer les entres, et ventuellement de les modier, avant des les renvoyer sur le ux de sortie standard.

27

Chapitre 1. Premire approche du C/C++


Note : La manire de raliser les redirections des ux standards dpend des systmes dexploitation et de leurs interfaces utilisateurs. De plus, les programmes doivent tre capables de travailler avec leurs ux dentre / sortie standards de manire gnrique, que ceux-ci soient redirigs ou non. Les techniques de redirection ne seront donc pas dcrites plus en dtail ici.

Vous remarquerez lintrt davoir deux ux distincts pour les rsultats des programmes et leurs messages derreur. Si, lors dune utilisation normale, ces deux ux se mlangent lcran, ce nest pas le cas lorsque lon redirige le ux de sortie standard. Seul le ux derreur standard est afch lcran dans ce cas, et les messages derreur ne se mlangent donc pas aux rsultats du programme. On pourrait penser que les programmes graphiques ne disposent pas de ux dentre / sortie standards. Pourtant, cest gnralement le cas. Mme si les vnements traits par les programmes graphiques dans leur boucle de messages ne proviennent pas du ux dentre standard, mais dune autre source de donnes spcique chaque systme, ils peuvent malgr tout utiliser les ux dentre / sortie standards si cela savre ncessaire. Gnralement, les programmes graphiques utilisent le ux de sortie standard pour crire des messages de fonctionnement ou des messages derreur (cette pratique nest cependant pas courante sous Windows, pour diverses raisons).

1.7.2. Les fonctions dentre / sortie de la bibliothque C


An de permettre aux programmes dcrire sur leurs ux dentre / sortie standards, la bibliothque C dnit plusieurs fonctions extrmement utiles. Les deux principales fonctions sont sans doute les fonctions printf et scanf. La fonction printf ( print formatted en anglais) permet denvoyer des donnes formates sur le ux de sortie standard, et scanf ( scan formatted ) permet de les lire partir du ux dentre standard. Ces fonctions sont dclares dans le chier den-tte stdio.h. En ralit, ces fonctions ne font rien dautre que dappeler deux autres fonctions permettant dcrire et de lire des donnes sur un ux quelconque : les fonctions fprintf et fscanf. Ces fonctions sutilisent exactement de la mme manire que les fonctions printf et scanf, ceci prs quelles prennent en premier paramtre une structure dcrivant le ux sur lequel elles travaillent. Pour les ux dentre / sortie standards, la bibliothque C dnit les ux stdin, stdout et stderr, qui correspondent respectivement au ux dentre, au ux de sortie et au ux derreur standards. Ainsi, tout appel scanf se traduit par un appel fscanf sur le ux stdin, et tout appel printf par un appel fprintf sur le ux stdout.
Note : Il nexiste pas de fonction permettant dcrire directement sur le ux derreur standard. Par consquent, pour effectuer de telles critures, il faut imprativement passer par la fonction fprintf, en lui fournissant en paramtre le ux stderr. La description des fonctions de la bibliothque C standard dpasse de loin le cadre de ce document. Aussi les fonctions de lecture et dcriture sur les ux ne seront-elles pas dcrites plus en dtail ici. Seules les fonctions printf et scanf seront prsentes, car elles sont rellement indispensable pour lcriture dun programme C et pour la comprhension des exemples des chapitres suivants. Consultez la bibliographie si vous dsirez obtenir plus de dtails sur la bibliothque C et sur toutes les fonctions quelle contient. Le C++ dispose galement de mcanismes de gestion des ux dentre / sortie qui lui sont propres. Ces mcanismes permettent de contrler plus nement les types des donnes crites et lues de et partir des ux dentre / sortie standards. De plus, ils permettent de raliser les oprations dcriture et de lecture des donnes formates de manire beaucoup plus simple. Cependant, ces mcanismes requirent des notions objets avances et ne seront dcrits que dans les chapitres ddis au C++. Comme il est galement possible dutiliser les fonctions printf et scanf en C++ dune part, et que, dautre part, ces fonctions sont essentielles en C, la suite de

28

Chapitre 1. Premire approche du C/C++


cette section sattachera leur description. Un chapitre complet est ddi aux mcanismes de gestion des ux du C++ dans la deuxime partie de ce document.

Les fonctions printf et scanf sont toutes deux des fonctions nombre de paramtres variable. Elles peuvent donc tre utilises pour effectuer des critures et des lectures multiples en un seul appel. An de leur permettre de dterminer la nature des donnes passes dans les arguments, elles attendent toutes les deux en premier paramtre une chane de caractres descriptive des arguments suivants. Cette chane est appele chane de format, et elle permet de spcier avec prcision le type, la position et les options de format (prcision, etc.) des donnes traiter. Les deux sections suivantes dcrivent la manire dutiliser ces chanes de format pour chacune des deux fonctions printf et scanf.

1.7.3. La fonction printf


La syntaxe gnrale de la fonction printf est la suivante :
printf(chane de format [, valeur [, valeur [...]]])

La chane de format peut contenir, comme on la au dbut de ce chapitre, du texte, mais elle est essentiellement employe pour dcrire les types des paramtres suivants et le formatage quelle doit leur appliquer pour crire leur valeur sur la sortie standard. La fonction printf peut donc afcher un nombre arbitraire de valeurs, la seule contrainte tant quil y ait le bon nombre de formateurs dans la chane de format. Si le nombre de paramtres est infrieur au nombre de valeurs afcher, le programme plantera. La fonction printf insre les valeurs associes aux formateurs au sein du texte de la chane de format. Les formateurs quelle contient sont tout simplement remplacs par la reprsentation textuelle des valeurs qui leurs sont associes. La fonction printf renvoie le nombre de caractres effectivement crits. Les formateurs utiliss pour la fonction printf peuvent tre relativement complexes, surtout si lon dsire spcier prcisment le format des valeurs crites. Nous allons donc dans un premier temps prsenter les chanes de format les plus simples qui permettent dafcher les valeurs des types de base du langage. Les formateurs commencent tous par le caractre de pourcentage (caractre %). Ce caractre peut tre suivi de diffrentes options, et dune lettre dnissant le type de donnes de la valeur que le formateur doit afcher. Comme le caractre % est utilis pour identier les formateurs dans la chane de format, lafchage de ce caractre se fait simplement en le doublant. Les lettres utilises pour les types de donnes de base du langage sont indiques dans le tableau suivant : Tableau 1-3. Chanes de format de printf pour les types de base Type de donnes Entier dcimal sign Entier dcimal non sign Entier en octal Caractre de formatage d u ou i o

29

Chapitre 1. Premire approche du C/C++ Type de donnes Entier en hexadcimal Flottants de type double Caractre isol Chane de caractres Pointeur (voir Chapitre 4) Caractre de formatage x (avec les caractres a f) ou X (avec les caractres A F) f, e, g, E ou G c s p

Vous noterez que la fonction printf nest pas capable dcrire des valeurs de type oat. Elle ne peut travailler quavec des valeurs en virgule ottante de types double. Une conversion est effectue automatiquement, il nest donc pas ncessaire de la faire soi-mme. Les valeurs ottantes innies sont remplaces par les mentions +INF et -INF. Un non-nombre (NotA-Number) IEEE (norme utilise pour la reprsentation des nombres en virgule ottate) donne +NAN ou -NAN. Exemple 1-21. Afchage de valeurs simples
#include <stdlib.h> #include <stdio.h> int main(void) { /* Dfinition de quelques variables : */ int nb_notes = 3; double note_moyenne = 12.25; char classe = B; /* Exemples daffichage de rsultats : */ printf("La moyenne des %d notes de la classe %c est %f.\n", nb_notes, classe, note_moyenne); printf("La meilleure apprciation est %s\n", "Bien"); /* Exemple daffichage du caractre % : */ printf("%f%% des lves sont admis\n", 78.3); /* Exemple dcriture sur la sortie derreur standard : */ fprintf(stderr, "Programme termin avec succs !\n"); return EXIT_SUCCESS; }

En ralit, la syntaxe compltes des formateurs de printf est la suivante :


%[[indicateur]...][largeur][.prcision][taille] type

Le champ taille permet de prciser la taille du type de donnes utilis, si lon nutilise pas un type de donnes simple. Les champs largeur et precision permettent de contrler la taille prise par la donne formate et sa prcision. Enn, le champ indicateur permet de spcier le formatage du signe pour les nombres. Les valeurs disponibles pour le paramtre de taille sont les caractres suivants :

30

Chapitre 1. Premire approche du C/C++ Tableau 1-4. Options de taille pour les types drivs Option h l L Type de donnes short int long int ou wchar_t ou chane de caractres de type wchar_t long double

Ainsi, pour crire des caractres de type wchar_t et des chanes de caractres Unicode, vous devrez prxer respectivement les caractres de types c et s par la lettre l. Exemple 1-22. Afchage de chane Unicode
#include <stdlib.h> #include <stdio.h> int main(void) { printf("Ceci est une chane Unicode : %ls\n", L"Hello World!"); return EXIT_SUCCESS; }

Les valeurs disponibles pour le paramtre de indicateur sont les caractres suivants : Tableau 1-5. Options dalignements et de remplissange Option
0 +

Signication justication droite de la sortie, avec remplissage gauche par des 0. justication gauche de la sortie, avec remplissage droite par des espaces. afchage du signe pour les nombres positifs. les nombres positifs commencent tous par un espace.

espace ( )

Le champ largeur permet de spcier la largeur minimale du champ de sortie. Si la sortie est trop petite, on complte avec des 0 ou des espaces selon lindicateur utilis. Notez quil sagit bien dune largeur minimale ici et non dune largeur maximale. Le rsultat du formatage de la donne crire peut donc dpasser la valeur indique pour la largeur du champ. Enn, le champ prcision spcie la prcision maximale de la sortie (nombre de chiffres afcher pour les entiers, prcision pour les ottants, et nombre de caractres pour les chanes de caractres).

1.7.4. La fonction scanf


La fonction scanf permet de faire une ou plusieurs entres. Comme la fonction printf, elle attend une chane de format en premier paramtre. Il faut ensuite passer les variables devant contenir les entres dans les paramtres qui suivent. Sa syntaxe est la suivante :
scanf(chane de format, &variable [, &variable [...]]);

31

Chapitre 1. Premire approche du C/C++ Elle renvoie le nombre de variables lues. Ne cherchez pas comprendre pour linstant la signication du symbole & se trouvant devant chacune des variables. Sachez seulement que sil est oubli, le programme plantera. La chane de format peut contenir une chanes de caractres et non uniquement des formateurs. Toutefois, si elle contient autre chose que des formateurs, le texte saisi par lutilisateur devra imprativement correspondre avec la chane de caractres de la chane de format. scanf cherchera reconnatre cette chane, et arrtera lanalyse la premire erreur. La syntaxe des formateurs pour scanf diffre un peu de celle de ceux de printf :
%[*][largeur][taille]type

Les types utiliss pour les chanes de format de scanf sont semblables ceux utiliss pour printf. Toutefois, on prendra garde au fait que le type f correspond cette fois bel et bien au type de donnes oat et non au type de donnes double. Lanalyse dun double se fera donc en ajoutant le modicateur de taille l. Le paramtre largeur permet quant lui de spcier le nombre maximal de caractres prendre en compte lors de lanalyse du paramtre. Le paramtre * est facultatif, il indique seulement de passer la donne entre et de ne pas la stocker dans la variable destination. Attention, cette variable doit quand mme tre prsente dans la liste des paramtres de scanf. Exemple 1-23. Calcul de moyenne
#include <stdlib.h> #include <stdio.h> int main(void) { double x, y; printf("Calcul de moyenne\n"); /* Affiche le titre. */ printf("Entrez le premier nombre : "); scanf("%lf", &x); /* Entre le premier nombre. */ printf("\nEntrez le deuxime nombre : "); scanf("%lf", &y); /* Entre le deuxime nombre. */ printf("\nLa valeur moyenne de %f et de %f est %f.\n", x, y, (x+y)/2); return EXIT_SUCCESS; }

Note : Vous noterez quil y a incohrence entre la chane de format utilise pour les scanf et la chane de format pour le printf. Nul nest parfait... En pratique, la fonction scanf nanalyse les caractres provenant du ux dentre que lorsquune ligne complte a t saisie. Toutefois, elle ne supprime pas du tampon de ux dentre le caractre de saut de ligne, si bien quil sy trouvera toujours lors de lentre suivante. Cela nest pas gnant si lon nutilise que la fonction scanf pour raliser les entres de donnes dans le programme, car cette fonction ignore tout simplement ces caractres de saut de ligne. En revanche, si lon utilise une autre fonction aprs un appel scanf, il faut sattendre trouver ce caractre de saut de ligne dans le ux dentre. La fonction scanf nest pas trs adapte la lecture des chanes de caractres, car il nest pas facile de contrler la taille maximale que lutilisateur peut saisir. Cest pour cette raison que lon a gnralement recours la fonction fgets, qui permet de lire une ligne sur le ux dentre standard et de stocker le rsultat dans une chane de caractres fournie en premier paramtre et dont la

32

Chapitre 1. Premire approche du C/C++


longueur maximale est spcie en deuxime paramtre (nous verrons plus loin comment crer de telles chanes de caractres). Le troisime paramtre de la fonction fgets est le ux partir duquel la lecture de la ligne doit tre ralise, cest dire gnralement stdin. Lanalyse de la chane de caractres ainsi lue peut alors tre faite avec une fonction similaire la fonction scanf, mais qui lit les caractres analyser dans une chane de caractres au lieu de les lire directement depuis le ux dentre standard : la fonction sscanf. Cette fonction sutilise exactement comme la fonction scanf, ceci prs quil faut lui fournir en premier paramtre la chane de caractres dans laquelle se trouvent les donnes interprter. La description de ces deux fonctions dpasse le cadre de ce document et ne sera donc pas faite ici. Veuillez vous rfrer la documentation de votre environnement de dveloppement ou la bibliographie pour plus de dtails leur sujet.

33

Chapitre 1. Premire approche du C/C++

34

Chapitre 2. Les structures de contrle


Nous allons aborder dans ce chapitre un autre aspect du langage indispensable la programmation, savoir : les structures de contrle. Ces structures permettent, comme leur nom lindique, de contrler lexcution du programme en fonction de critres particuliers. Le C et le C++ disposent de toutes les structures de contrle classiques des langages de programmation comme les tests, les boucles, les sauts, etc. Toutes ces structures sont dcrites dans les sections suivantes.

2.1. Les tests


Les tests sont les structures qui permettent de slectionner une instruction ou un groupe dinstructions en fonction du rsultat dun test.

2.1.1. La structure conditionnelle if


La structure conditionnelle if permet de raliser un test et dexcuter une instruction ou non selon le rsultat de ce test. Sa syntaxe est la suivante :
if (test) opration;

o test est une expression dont la valeur est boolenne ou entire. Toute valeur non nulle est considre comme vraie. Si le test est vrai, opration est excut. Ce peut tre une instruction ou un bloc dinstructions. Une variante permet de spcier laction excuter en cas de test faux :
if (test) opration1; else opration2;

Note : Attention ! Les parenthses autour du test sont ncessaires !

Les oprateurs de comparaison sont les suivants : Tableau 2-1. Oprateurs de comparaison
a == b a != b ou a not_eq b a < b a > b a <= b a >= b

Test dgalit entre les expressions a et b Test dingalit entre les expressions a et b Test dinfriorit entre les expressions a et b Test de supriorit entre les expressions a et b Test dinfriorit ou dgalit entre les expressions a et b Test de supriorit ou dgalit entre les expressions a et b

Les oprateurs logiques applicables aux expressions boolennes sont les suivants :

35

Chapitre 2. Les structures de contrle Tableau 2-2. Oprateurs logiques


a && b ou a and Conjonction des expressions boolennes a et b ( Et logique ) b a || b ou a or b !a ou not a

Disjonction des expressions boolennes a et b ( Ou logique ) Ngation logique de lexpression boolenne a

Il ny a pas doprateur ou logique exclusif. Exemple 2-1. Test conditionnel if


if (a<b && a!=0) { m=a; nouveau_m=1; }

2.1.2. Le branchement conditionnel


Dans le cas o plusieurs instructions diffrentes doivent tre excutes selon la valeur dune variable de type intgral, lcriture de if successifs peut tre relativement lourde. Le C/C++ fournit donc la structure de contrle switch, qui permet de raliser un branchement conditionnel. Sa syntaxe est la suivante :
switch (valeur) { case cas1: [instruction; [break;] ] case cas2: [instruction; [break;] ] . . . case casN: [instruction; [break;] ] [default: [instruction; [break;] ] ] }

valeur est valu en premier. Son type doit tre entier. Selon le rsultat de lvaluation, lexcution du programme se poursuit au cas de mme valeur. Si aucun des cas ne correspond et si default est

36

Chapitre 2. Les structures de contrle prsent, lexcution se poursuit aprs default. Si en revanche default nest pas prsent, on sort du switch. Les instructions qui suivent le case appropri ou default sont excutes. Puis, les instructions du cas suivant sont galement excutes (on ne sort donc pas du switch). Pour forcer la sortie du switch, on doit utiliser le mot cl break. Exemple 2-2. Branchement conditionnel switch
i= 2; switch (i) { case 1: case 2: /* Si i=1 ou 2, la ligne suivante sera excute. */ i=2-i; break; case 3: i=0; /* Cette ligne ne sera jamais excute. */ default: break; }

Note : Il est interdit de dnir une variable dans un des case dun switch.

2.2. Les boucles


Les boucles sont des structures de contrle qui permettent de raliser une opration plusieurs fois, tant quune condition est vrie.

2.2.1. La boucle for


La structure de contrle for est sans doute lune des boucles les plus importantes. Elle permet de raliser toutes sortes de boucles et, en particulier, les boucles permettant ditrer sur un ensemble de valeur dune variable de contrle. Sa syntaxe est la suivante :
for (initialisation ; test ; itration) opration;

o initialisation est une instruction value avant le premier parcours de la boucle du for. test est une expression dont la valeur dterminera la n de la boucle. itration est une instruction effectue la n de chaque passage dans la boucle, et opration constitue le traitement de la boucle elle-mme. Chacune de ces parties est facultative. La squence dexcution est la suivante :
initialisation test si vrai : opration itration retour au test

37

Chapitre 2. Les structures de contrle


fin du for.

Exemple 2-3. Boucle for


somme = 0; for (i=0; i<=10; i=i+1) somme = somme + i;

Note : En C++, il est possible que la partie initialisation dclare une variable. Dans ce cas, la variable dclare nest dnie qu lintrieur de linstruction for. Par exemple,

for (int i=0; i<10; ++i);

est strictement quivalent :

{ int i; for (i=0; i<10; ++i); }

Cela signie que lon ne peut pas utiliser la variable i aprs linstruction for, puisquelle nest dnie que dans le corps de cette instruction. Cela permet de raliser des variables muettes qui ne servent qu linstruction for dans laquelle elles sont dnies.

Note : Cette rgle ntait pas respecte par certains compilateurs jusqu encore rcemment. En effet, historiquement, les premiers compilateurs autorisaient bien la dnition dune variable dans la partie initialisation des boucles for, mais cette variable ntait pas limite lintrieur de la boucle. Elle restait donc accessible aprs cette instruction. La diffrence est subtile, mais importante. Cela pose assurment des problmes de compatibilit et les programmes C++ relativement anciens crits pour ces compilateurs doivent tre ports, puisque dans un cas la variable doit tre redclare et dans lautre cas elle ne le doit pas. Dans ce cas, le plus simple est de ne pas dclarer la variable de contrle dans la partie initialisation de linstruction for, mais juste avant, an de revenir la smantique originelle.

2.2.2. Le while
Le while permet dexcuter des instructions en boucle tant quune condition est vraie. Sa syntaxe est la suivante :
while (test) opration;

o opration est effectue tant que test est vri. Comme pour le if, les parenthses autour du test sont ncessaires. Lordre dexcution est :
test si vrai : opration retour au test

38

Chapitre 2. Les structures de contrle

Exemple 2-4. Boucle while


somme = i = 0; while (somme<1000) { somme = somme + 2 * i / (5 + i); i = i + 1; }

2.2.3. Le do
La structure de contrle do permet, tout comme le while, de raliser des boucles en attente dune condition. Cependant, contrairement celui-ci, le do effectue le test sur la condition aprs lexcution des instructions. Cela signie que les instructions sont toujours excutes au moins une fois, que le test soit vri ou non. Sa syntaxe est la suivante :
do opration; while (test); opration est effectue jusqu ce que test ne soit plus vri.

Lordre dexcution est :


opration test si vrai, retour opration

Exemple 2-5. Boucle do


p = i = do { p = i = } while 1;

p * i; i + 1; (i != 10);

2.3. Les instructions de rupture de squence et de saut


Les instructions de rupture de squence permettent, comme leur nom lindique, dinterrompre une squence dinstructions et de passer la squence suivante directement. Elles sont souvent utilises pour sortir des boucles, pour passer litration suivante, ou pour traiter une erreur qui sest produite pendant un traitement.

39

Chapitre 2. Les structures de contrle

2.3.1. Les instructions de rupture de squence


Les commandes de rupture de squence sont les suivantes :
return [valeur]; break; continue;

return permet de quitter immdiatement la fonction en cours. Comme on la dj vu, la commande return peut prendre en paramtre la valeur de retour de la fonction. break permet de passer linstruction suivant linstruction while, do, for ou switch la plus im-

brique (cest--dire celle dans laquelle on se trouve).


continue saute directement la dernire ligne de linstruction while, do ou for la plus imbrique. Cette ligne est laccolade fermante. Cest ce niveau que les tests de continuation sont faits pour for et do, ou que le saut au dbut du while est effectu (suivi immdiatement du test). On reste donc dans la structure dans laquelle on se trouvait au moment de lexcution de continue, contrairement ce qui se passe avec le break.

Exemple 2-6. Rupture de squence par continue


/* Calcule la somme des 1000 premiers entiers pairs : */ somme_pairs=0; for (i=0; i<1000; i=i+1) { if (i % 2 == 1) continue; somme_pairs=somme_pairs + i; }

2.3.2. Le saut
Le C/C++ dispose galement dune instruction de saut permettant de poursuivre lexcution du programme en un autre point. Bien quil soit fortement dconseill de lutiliser, cette instruction est ncessaire et peut parfois tre trs utile, notamment dans les traitements derreurs. Sa syntaxe est la suivante :
goto tiquette;

o tiquette est une tiquette marquant la ligne destination dans la fonction. Les tiquettes sont simplement dclares avec la syntaxe suivante :
tiquette:

Les tiquettes peuvent avoir nimporte quel nom didenticateur. Il nest pas possible deffectuer des sauts en dehors dune fonction. En revanche, il est possible deffectuer des sauts en dehors et lintrieur des blocs dinstructions sous certaines conditions. Si la destination du saut se trouve aprs une dclaration, cette dclaration ne doit pas comporter dinitialisations. De plus, ce doit tre la dclaration dun type simple (cest--dire une dclaration qui ne demande pas lexcution de code) comme les variables, les structures ou les tableaux. Enn, si, au cours dun saut, le contrle dexcution sort de la porte dune variable, celle-ci est dtruite.

40

Chapitre 2. Les structures de contrle


Note : Ces dernires rgles sont particulirement importantes en C++ si la variable est un objet dont la classe a un constructeur ou un destructeur non trivial. Voir le Chapitre 7 pour plus de dtails ce sujet. Autre rgle spcique au C++ : il est impossible deffectuer un saut lintrieur dun bloc de code en excution protge try {}. Voir aussi le Chapitre 8 concernant les exceptions.

41

Chapitre 2. Les structures de contrle

42

Chapitre 3. Types avancs et classes de stockage


Nous avons prsent, dans le premier chapitre, les types fondamentaux du langage et leurs types drivs. Nous allons maintenant pouvoir tudier plus en dtails ces types. Nous verrons ensuite comment dnir et utiliser des types plus complexes, tels que les tableaux, les structures et les unions. Nous verrons galement comment simplier lutilisation des types de donnes et comment convertir des valeurs dun type de donnes en un autre. Enn, nous verrons les diffrentes possibilits pour spcier la porte et la dure de vie des variables, via la notion de classe de stockage.

3.1. Types de donnes portables


La taille des types nest spcie dans aucune norme. La seule chose qui est indique dans la norme C++, cest que le plus petit type est le type char. Les tailles des autres types sont donc des multiples de celle du type char. De plus, les ingalits suivantes sont toujours vries :
char short int int long int float double long double

o loprateur signie ici a une plage de valeurs plus petite ou gale que . Cela dit, tous les environnements C/C++ stockent les char sur un octet, et le type short sur deux octets. Pour les autres types, ce nest pas du tout aussi simple que cela... La norme stipule que le type int est celui qui permet de stocker les entiers au format natif du processeur utilis. Il doit donc tre cod sur deux octets sur les machines 16 bits, sur quatre octets sur les machines 32 bits, et sur 8 octets sur les machines 64 bits. Cela implique que, sur les machines 64 bits, le type long est stock lui aussi sur au moins 8 octets. De ce fait, il ne reste que le type de donnes short pour reprsenter les donnes 16 bits et 32 bits sur ces machines. De toutes vidences, il y a un problme, car on ne peut dans ce cas pas manipuler des donnes 16 bits ou 32 bits facilement. An de rsoudre ce problme, la plupart des compilateurs ne respectent tout simplement pas la norme ! Ils brisent en effet la rgle selon laquelle le type int est le type des entiers natifs du processeur, et xent sa taille 32 bits quelle que soit larchitecture utilise (sauf pour les vieilles architectures 16 bits, pour lesquelles la norme pouvait encore tre respecte). Ainsi, la taille des types utilise en pratique est rcapitule dans le tableau suivant : Type 16 bits char short int long 8 bits 16 bits 16 bits 32 bits 8 bits 16 bits 32 bits 32 bits Architecture 32 bits 8 bits 16 bits 32 bits 64 bits 64 bits

La taille des caractres de type wchar_t quant elle nest pas spcie et dpend de lenvironnement de dveloppement utilis. Ils sont gnralement cods sur deux ou sur quatre octets suivant la reprsentation utilise pour les caractres Unicode. Par exemple, ils sont cods sur deux octets sous

43

Chapitre 3. Types avancs et classes de stockage Windows, et sur quatre sous Unix. L o les choses se compliquent, cest que les valeurs accessibles sont fortement dpendantes du nombre de bits utiliss pour les stocker. Elles dpendent galement du signe du type de donnes. En effet, pour les types signs, un bit est utilis pour stocker linformation de signe de la valeur. De ce fait, la plage de valeurs utilisables en valeur absolue est moins grande. Par exemple, si le type char est cod sur 8 bits, on peut coder les nombres allant de 0 255 avec ce type en non sign (il y a 8 chiffres binaires, chacun peut valoir 0 ou 1, on a donc 2 puissance 8 combinaisons possibles, ce qui fait 256). En sign, les valeurs stendent de -128 127 (un des chiffres binaires est utilis pour le signe, il en reste 7 pour coder le nombre, donc il reste 128 possibilits dans les positifs comme dans les ngatifs. 0 est considr comme positif. En tout, il y a autant de possibilits.). De mme, si le type int est cod sur 16 bits (cas des machines 16 bits), les valeurs accessibles vont de -32768 32767 ou de 0 65535 si lentier nest pas sign. Cest le cas sur les PC en mode rel (cest-dire sous DOS) et sous Windows 3.x. Sur les machines fonctionnant en 32 bits, le type int est stock sur 32 bits : lespace des valeurs disponibles est donc 65536 fois plus large. Cest le cas sur les PC en mode protg 32 bits (Windows 9x, ou bass sur la technologie NT, DOS Extender, Linux) et sur les Macintosh. Cest aussi le cas sur la plupart des machines 64 bits, pour les raisons que lon a vues. On constate donc que la portabilit des types de base est trs alatoire. Cela signie quil faut faire extrmement attention dans le choix des types si lon veut faire du code portable (cest--dire qui compilera et fonctionnera sans modications du programme sur tous les ordinateurs). Il est dans ce cas ncessaire dutiliser des types de donnes qui donnent les mmes intervalles de valeurs sur tous les ordinateurs. An de rgler tous ces problmes, la norme ISO C99 impose de dnir des types portables an de rgler ces problmes sur toutes les architectures existantes. Ces types sont dnis dans le chier dentte stdint.h. Il sagit des types int8_t, int16_t, int32_t et int64_t, et de leurs versions non signes uint8_t, uint16_t, uint32_t et uint64_t. La taille de ces types en bits est indique dans leur nom et leur utilisation ne devrait pas poser de problme.
Note : Le problme se pose galement pour les types ottants, mais il est bien moins grave parce que la plupart des compilateurs utilisent les formats IEEE normaliss pour les nombres en virgule ottante. Le type oat est gnralement cod sur 4 octets, et les types double et long double sont souvent identiques et cods sur 8 octets. En pratique, le type oat est toutefois trs souvent trop restreint pour tre utilisable de nos jours, et on lui prfrera le type double. Le programmeur devra galement faire attention, en plus du problme de la taille des types, au problme de la reprsentation des donnes. En effet, deux reprsentations dun mme type peuvent tre diffrentes en mmoire sur deux machines darchitectures diffrentes, mme taille gale en nombre de bits. Le problme le plus courant est lordre de stockage des octets en mmoire pour les types qui sont stocks sur plus dun octet (cest--dire quasiment tous). Certaines machines stockent les octets de poids fort en premier, dautres les octets de poids faible. Ce problme revt donc une importance capitale lorsque le programme doit tre portable ou que des donnes doivent tre changes entre des machines darchitectures a priori diffrentes, par exemple dans le cadre dune communication rseau ou lors dun change de chier. Une solution simple est de toujours changer les donnes au format texte (moyennant les prcautions ncessaires pour prendre en charge les erreurs de conversion induites par le formatage), ou de choisir un mode de reprsentation de rfrence. Les bibliothques rseau disposent gnralement des mthodes permettant de convertir les donnes vers un format commun dchange de donnes par un rseau et pourront par exemple tre utilises.

44

Chapitre 3. Types avancs et classes de stockage

3.2. Structures de donnes et types complexes


En dehors des types de variables simples, le C/C++ permet de crer des types plus complexes. Ces types comprennent essentiellement les tableaux, les structures, les unions et les numrations.

3.2.1. Les tableaux


La dnition dun tableau se fait en faisant suivre le nom de lidenticateur dune paire de crochets, contenant le nombre dlment du tableau :
type identificateur[taille]([taille](...));

Note : Attention ! Les caractres [ et ] tant utiliss par la syntaxe des tableaux, ils ne signient plus les lments facultatifs ici. Ici, et ici seulement, les lments facultatifs sont donns entre parenthses.

Dans la syntaxe prcdente, type reprsente le type des lments du tableau. Exemple 3-1. Dnition dun tableau
int MonTableau[100]; MonTableau est un tableau de 100 entiers. On rfrence les lments des tableaux en donnant lindice

de llment entre crochet :


MonTableau[3]=0;

Les indices des tableaux varient de 0 taille-1. Il y a donc bien taille lments dans le tableau. Dans lexemple donn ci-dessus, llment MonTableau[100] nexiste pas : y accder plantera le programme. Cest au programmeur de vrier que ses programmes nutilisent jamais les tableaux avec des indices ngatifs ou plus grands que leur taille. En C/C++, les tableaux plus dune dimension sont des tableaux de tableaux. On prendra garde au fait que dans la dnition dun tableau plusieurs dimensions, la dernire taille indique spcie la taille du tableau dont on fait un tableau. Ainsi, dans lexemple suivant :
int Matrice[5][4]; Matrice est un tableau de taille 5 dont les lments sont eux-mmes des tableaux de taille 4. Lordre

de dclaration des dimensions est donc invers : 5 est la taille de la dernire dimension et 4 est la taille de la premire dimension. Llment suivant :
Matrice[2];

est donc le troisime lment de ce tableau de taille cinq, et est lui-mme un tableau de quatre lments. Il est possible, lors dune dclaration de tableau (pas lors dune dnition), domettre la taille de la dernire dimension dun tableau (donc la taille du premier groupe de crochets). En effet, le C/C++ ne contrle pas la taille des tableaux lui-mme et, pour lui, un tableau peut avoir une taille quelconque.

45

Chapitre 3. Types avancs et classes de stockage Seul le type de donnes des lments est important pour calculer leur taille et donc la position de chacun deux en mmoire par rapport au dbut du tableau. De ce fait, la taille de la dernire dimension nest pas ncessaire dans la dclaration, elle nest en fait utile que lors de la dnition pour rserver effectivement la mmoire du tableau. Cette criture peut par exemple tre utilise pour passer des tableaux en paramtre une fonction. Exemple 3-2. Passage de tableau en paramtre
void f(int taille, int t[][20]) { /* Utilisation de t[i][j] ... */ return; } int main(void) { int tab[10][20]; test(10, tab); /* Passage du tableau en paramtre. */ return 0; }

Dans cet exemple, la fonction f reoit un tableau deux dimensions en paramtre, dont seule la premire dimension est spcie. Bien entendu, pour quelle puisse lutiliser correctement, elle doit en connatre la taille, donc la taille de la deuxime dimension. Cela peut se faire soit en passant un paramtre explicitement comme cest le cas ici, ou en utilisant une convention dont le programmeur doit sassurer lors de lappel de la fonction.
Note : Attention, les tableaux sont passs par rfrence dans les fonctions. Cela signie que si la fonction modie les lments du tableau, elle modie les lments du tableau que lappelant lui a fourni. Autrement dit, il ny a pas de copie du tableau lors de lappel de la fonction. Les tailles des premires dimensions sont absolument ncessaires. Outre le fait quelles permettent de dterminer la taille de chaque lment de la dernire dimension, elle permettent galement au compilateur de connatre le rapport des dimensions entre elles. Par exemple, la syntaxe :
int tableau[][];

utilise pour rfrencer un tableau de 12 entiers ne permettrait pas de faire la diffrence entre les tableaux de deux lignes et de six colonnes et les tableaux de trois lignes et de quatre colonnes (et leurs transposs respectifs). Une rfrence telle que :
tableau[1][3]

ne reprsenterait rien. Selon le type de tableau, llment rfrenc serait le quatrime lment de la deuxime ligne (de six lments), soit le dixime lment, ou bien le quatrime lment de la deuxime ligne (de quatre lments), soit le huitime lment du tableau. En prcisant tous les indices sauf un, il est possible de connatre la taille du tableau pour cet indice partir de la taille globale du tableau, en la divisant par les tailles sur les autres dimensions (2 = 12/6 ou 3 = 12/4 par exemple).

46

Chapitre 3. Types avancs et classes de stockage

3.2.2. Les chanes de caractres


Comme on la dj dit, il nexiste pas de type de donnes spcique pour manipuler les chanes de caractres en C/C++. Les chanes de caractres sont en effet considres comme des tableaux de caractres, dont le dernier caractre est nul. Ce carctre permet de marquer la n de la chane de caractres et ne devra jamais tre oubli. De ce fait, les tableaux de caractres sont trs utiliss pour stocker des chanes de caractres. Il faudra toutefois faire trs attention toujours utiliser des tailles de tableaux dune unit suprieures la taille des chanes de caractres stocker, an de pouvoir placer le caractre nul terminal de la chane. Par exemple, pour crer une chane de caractres de 12 caractres au plus, il faut un tableau pour 13 caractres. Exemple 3-3. Chane de caractres C et tableau
#include <stdlib.h> #include <stdio.h> #include <string.h> int main(void) { /* Rserve assez de place pour la chane et son caractre nul terminal : */ char chaine[13]; /* Copie la chane dans le tableau : */ strcpy(chaine, "Hello World!"); /* Affiche la chane : */ printf("%s\n", chaine);

/* Affiche "Hello World!" */

/* Tronque la chane avec un autre nul : */ chaine[5] = \0; printf("%s\n", chaine); /* Affiche "Hello" */ /* Insre un caractre _ : */ chaine[5] = _; printf("%s\n", chaine); /* Affiche "Hello_World!" */ return EXIT_SUCCESS; }

Cet exemple montre bien que le caractre nul marque la n la chane de caractre pour les fonctions de la bibliothque C. Cela nimplique pas que les caractres situs aprs ce caractre nul sont dtruits, mais que du point de vue de la bibliothque, la chane sarrte au niveau de ce caractre.
Note : La fonction strcpy utilise dans cet exemple est lune des nombreuses fonctions de manipulation de chanes de caractres de la bibliothque C. Elle permet de raliser la copie dune chane de caractres dans une autre (attention, elle ne vrie pas que la taille de la chane cible est sufsante). Ces fonctions sont dclares dans le chier den-tte string.h.

47

Chapitre 3. Types avancs et classes de stockage

3.2.3. Les structures


Les structures sont des aggrgats de donnes de types plus simples. Les structures permettent de construire des types complexes partir des types de base ou dautres types complexes. La dnition dune structure se fait laide du mot cl struct :
struct [nom_structure] { type champ; [type champ; [...]] };

La structure contient plusieurs donnes membres, appeles champs. Leurs types sont donns dans la dclaration de la structure. Ces types peuvent tre nimporte quel autre type, mme une structure. Le nom de la structure est facultatif et peut ne pas tre prcis dans les situations o il nest pas ncessaire (voir plus loin). La structure ainsi dnie peut alors tre utilise pour dnir une variable dont le type est cette structure. Pour cela, deux possibilits :

faire suivre la dnition de la structure par lidenticateur de la variable ; Exemple 3-4. Dclaration de variable de type structure
struct Client { unsigned char Age; unsigned char Taille; } Jean;

ou, plus simplement :


struct { unsigned char Age; unsigned char Taille; } Jean;

Dans le deuxime exemple, le nom de la structure nest pas mis.

dclarer la structure en lui donnant un nom, puis dclarer les variables avec la syntaxe suivante :
[struct] nom_structure identificateur;

Exemple 3-5. Dclaration de structure


struct Client { unsigned char Age; unsigned char Taille; };

48

Chapitre 3. Types avancs et classes de stockage


struct Client Jean, Philippe; Client Christophe; // Valide en C++ mais invalide en C

Dans cet exemple, le nom de la structure doit tre mis, car on utilise cette structure la ligne suivante. Pour la dclaration des variables Jean et Philippe de type struct Client, le mot cl struct a t mis. Cela nest pas ncessaire en C++, mais lest en C. Le C++ permet donc de dclarer des variables de type structure exactement comme si le type structure tait un type prdni du langage. La dclaration de la variable Christophe ci-dessus est invalide en C.

Les lments dune structure sont accds par un point, suivi du nom du champ de la structure accder. Par exemple, lge de Jean est dsign par Jean.Age. Il est possible de ne pas donner de nom une structure lors de sa dnition sans pour autant dclarer une variable. De telles structures anonymes ne sont utilisables que dans le cadre dune structure incluse dans une autre structure :
struct struct_principale { struct { int champ1; }; int champ2; };

Dans ce cas, les champs des structures imbriques seront accds comme sil sagissait de champs de la structure principale. La seule limitation est que, bien entendu, il ny ait pas de conit entre les noms des champs des structures imbriques et ceux des champs de la structure principale. Sil y a conit, il faut donner un nom la structure imbrique qui pose problme, en en faisant un vrai champ de la structure principale.

3.2.4. Les unions


Les unions constituent un autre type de structure. Elles sont dclares avec le mot cl union, qui a la mme syntaxe que struct. La diffrence entre les structures et les unions est que les diffrents champs dune union occupent le mme espace mmoire. On ne peut donc, tout instant, nutiliser quun des champs de lunion. Exemple 3-6. Dclaration dune union
union entier_ou_reel { int entier; float reel; }; union entier_ou_reel x; x peut prendre laspect soit dun entier, soit dun rel. Par exemple :

x.entier=2;

49

Chapitre 3. Types avancs et classes de stockage affecte la valeur 2 x.entier, ce qui dtruit x.reel. Si, prsent, on fait :
x.reel=6.546;

la valeur de x.entier est perdue, car le rel 6.546 a t stock au mme emplacement mmoire que lentier x.entier. Les unions, contrairement aux structures, sont assez peu utilises, sauf en programmation systme o lon doit pouvoir interprter des donnes de diffrentes manires selon le contexte. Dans ce cas, on aura avantage utiliser des unions de structures anonymes et accder aux champs des structures, chaque structure permettant de manipuler les donnes selon une de leurs interprtations possibles. Exemple 3-7. Union avec discriminant
struct SystemEvent { int iEventType;

/* Discriminant de lvnement. Permet de choisir comment linterprter. */

union { struct { int iMouseX; int iMouseY; }; struct { char cCharacter; int iShiftState; }; /* etc. */ }; };

/* Structure permettant dinterprter */ /* les vnements souris. */

/* Structure permettant dinterprter */ /* les vnements clavier. */

/* Exemple dutilisation des vnements : */ int ProcessEvent(struct SystemEvent e) { int result; switch (e.iEventType) { case MOUSE_EVENT: /* Traitement de lvnement souris... */ result = ProcessMouseEvent(e.iMouseX, e.iMouseY); break; case KEYBOARD_EVENT: /* Traitement de lvnement clavier... */ result = ProcessKbdEvent(e.cCharacter, e.iShiftState); break; } return result; }

50

Chapitre 3. Types avancs et classes de stockage

3.2.5. Les champs de bits


Il est possible de dnir des structures dont les champs ne sont stocks que sur quelques bits et non sur des types de base du langage complets. De telles structures sont appeles des champs de bits . Les champs de bits se dclarent comme des structures, avec le mot cl struct, mais dans lesquelles la taille en bits de chaque champ est spcie. Les diffrents groupes de bits doivent tre conscutifs. Exemple 3-8. Dclaration dun champs de bits
struct champ_de_bits { int var1; int bits1a4 : 4; int bits5a10 : 6; unsigned int bits11a16 : 6; };

/* /* /* /*

Dfinit une variable classique. */ Premier champ : 4 bits. */ Deuxime champ : 6 bits. */ Dernier champ : 6 bits. */

La taille dun champ de bits ne doit pas excder celle dun entier. Pour aller au-del, on crera un deuxime champ de bits. La manire dont les diffrents groupes de bits sont placs en mmoire dpend du compilateur et nest pas normalise. Les diffrents bits ou groupes de bits seront tous accessibles comme des variables classiques dune structure ou dune union :
struct champ_de_bits essai; int main(void) { essai.bits1a4 = 3; /* suite du programme */ return 0; }

3.2.6. Initialisation des structures et des tableaux


Les tableaux et les structures peuvent tre initialiss ds leur cration, tout comme les types classiques peuvent ltre. La valeur servant linitialisation est dcrite en mettant les valeurs des membres de la structure ou du tableau entre accolades et en les sparant par des virgules : Exemple 3-9. Initialisation dune structure
/* Dfinit le type Client : */ struct Client { unsigned char Age; unsigned char Taille; unsigned int Comptes[10]; }; /* Dclare et initialise la variable John : */ struct Client John={35, 190, {13594, 45796, 0, 0, 0, 0, 0, 0, 0, 0}};

51

Chapitre 3. Types avancs et classes de stockage La variable John est ici dclare comme tant de type Client et initialise comme suit : son ge est de 35, sa taille de 190 et ses deux premiers comptes de 13594 et 45796. Les autres comptes sont nuls. Il nest pas ncessaire de respecter limbrication du type complexe au niveau des accolades, ni de fournir des valeurs dinitialisations pour les derniers membres dun type complexe. Les valeurs par dfaut qui sont utilises dans ce cas sont les valeurs nulles du type du champ non initialis. Ainsi, la dclaration de John aurait pu se faire ainsi :
struct Client John={35, 190, 13594, 45796};

La norme C99 du langage C fournit galement une autre syntaxe plus pratique pour initialiser les structures. Cette syntaxe permet dinitialiser les diffrents champs de la structure en les nommant explicitement et en leur affectant directement leur valeur. Ainsi, avec cette nouvelle syntaxe, linitialisation prcdente peut tre ralise de la manire suivante : Exemple 3-10. Initialisation de structure C99
/* Dclare et initialise la variable John : */ struct Client John={ .Taille = 190, .Age = 35, .Comptes[0] = 13594, .Comptes[1] = 45796 };

On constatera que les champs qui ne sont pas explicitement initialiss sont, encore une fois, initialiss leur valeur nulle. De plus, comme le montre cet exemple, il nest pas ncessaire de respecter lordre dapparition des diffrents champs dans la dclaration de la structure pour leur initialisation. Il est possible de mlanger les deux syntaxes. Dans ce cas, les valeurs pour lesquelles aucun nom de champ nest donn seront affectes au champs suivants le dernier champ nomm. De plus, si plusieurs valeurs diffrentes sont affectes au mme champ, seule la dernire valeur indique sera utilise. Cette syntaxe est galement disponible pour linitialisation des tableaux. Dans ce cas, on utilisera les crochets directement, sans donner le nom du tableau (exactement comme linitialisation des membres de la structure utilise directement le point, sans donner le nom de la structure en cours dinitialisation). Exemple 3-11. Initialisation de tableau C99
/* Dclare et initialise un tableau dentier : */ int tableau[6]={ [0] = 0, [1] = 2, [5] = 7 };

Note : La syntaxe dinitialisation C99 nest pas disponible en C++. Avec ce langage, il est prfrable dutiliser la notion de classe et de dnir un constructeur. Les notions de classe et de constructeur seront prsentes plus en dtails dans le Chapitre 7. Cest lun des rares points syntaxiques o il y a incompatibilit entre le C et le C++.

52

Chapitre 3. Types avancs et classes de stockage

3.3. Les numrations


Les numrations sont des types intgraux (cest--dire quils sont bass sur les entiers), pour lesquels chaque valeur dispose dun nom unique. Leur utilisation permet de dnir les constantes entires dans un programme et de les nommer. La syntaxe des numrations est la suivante :
enum enumeration { nom1 [=valeur1] [, nom2 [=valeur2] [...]] };

Dans cette syntaxe, enumeration reprsente le nom de lnumration et nom1, nom2, etc. reprsentent les noms des numrs. Par dfaut, les numrs reoivent les valeurs entires 0, 1, etc. sauf si une valeur explicite leur est donne dans la dclaration de lnumration. Ds quune valeur est donne, le compteur de valeurs se synchronise avec cette valeur, si bien que lnumr suivant prendra pour valeur celle de lnumr prcdent augmente de 1. Exemple 3-12. Dclaration dune numration
enum Nombre { un=1, deux, trois, cinq=5, six, sept };

Dans cet exemple, les numrs prennent respectivement leurs valeurs. Comme quatre nest pas dni, une resynchronisation a lieu lors de la dnition de cinq. Les numrations suivent les mmes rgles que les structures et les unions en ce qui concerne la dclaration des variables : on doit rpter le mot cl enum en C, ce nest pas ncessaire en C++.

3.4. Les alias de types


Le C/C++ dispose dun mcanisme de cration dalias et de synonymes dautres types. Ce mcanisme permet souvent de simplier les critures, mais peut aussi tre utile lors de la cration de nouveaux types.

3.4.1. Dnition dun alias de type


La dnition dun alias de type se fait laide du mot cl typedef. Sa syntaxe est la suivante :
typedef dfinition alias;

o alias est le nom que doit avoir le synonyme du type et dfinition est sa dnition. Exemple 3-13. Dnition de type simple
typedef unsigned int mot;

mot est alors strictement quivalent unsigned int.

53

Chapitre 3. Types avancs et classes de stockage Pour les tableaux, la syntaxe est particulire :
typedef type_tableau type[(taille)]([taille](...)); type_tableau est alors le type des lments du tableau.

Exemple 3-14. Dnition de type tableau


typedef int tab[10];

tab est le synonyme de tableau de 10 entiers . La dnition dalias de type est trs utile pour donner un nom aux structures et ainsi simplier leur utilisation en C. Exemple 3-15. Dnition de type structure
typedef struct client { unsigned int Age; unsigned int Taille; } Client;

Client reprsente la structure struct client. Le nom de lalias ( Client ) pourra ds lors tre utilis en lieu et place du nom de la structure ( struct client ).
Note : Pour comprendre la syntaxe de typedef, il suft de raisonner de la manire suivante. Si lon dispose dune expression qui permet de dclarer une variable dun type donn, alors il suft de placer le mot cl typedef devant cette expression pour faire en sorte que lidenticateur de la variable devienne un identicateur de type. Par exemple, si on supprime le mot cl typedef dans la dclaration du type Client ci-dessus, alors Client devient une variable dont le type est struct client.

3.4.2. Utilisation dun alias de type


Les alias de types peuvent tre utiliss exactement comme des types normaux. Il suft de les utiliser en lieu et place des types quils reprsentent. Par exemple, avec les alias de types dnis prcdemment, on pourrait crire :
unsigned int i = 2, j; /* Dclare deux unsigned int */ tab Tableau; /* Dclare un tableau de 10 entiers */ Client John; /* Dclare une structure client */ John.Age = 35; /* Initialise la variable John */ John.Taille = 175; for (j=0; j<10; j = j+1) Tableau[j]=j; /* Initialise Tableau */

Attention toutefois. Les alias de types constituent une facilit dcriture, rien de plus. Ils ne permettent pas, contrairement ce que lthymologie du mot cl typedef pourrait laisser penser, de crer de nouveaux types de donnes. Les alias de types sont bel et bien considrs comme tant identique aux types quils reprsentent par le compilateur.

54

Chapitre 3. Types avancs et classes de stockage Cela signie que le compilateur ne fera aucune vrication de types lors de la manipulation de types synonymes, et quil ne les distinguera pas non plus dans les signatures des fonctions. Par consquent, il nest pas possible de dnir deux fonctions surcharges dont les paramtres ne diffrent que par un alias de type. Toutefois, le fait que lon puisse crer un nouveau type en dnissant une structure peut tre utilis avantageusement dans de nombreuses situations en conjonction de la dnition dun nouveau type. La cration dun type de donnes par lintermdiaire dune structure peut en effet tre utilise dans dautres situations que la simple cration dun aggrgat de donnes de types plus simples. En effet, il est relativement courant que des donnes aient le mme type dun point de vue reprsentation des donnes, mais que leurs types effectifs naient pas la mme smantique (chose que seul le programmeur peut savoir, et pour laquelle le compilateur ne peut tre daucune aide). Par exemple, un programme de conversion dunits de mesures pourrait fort bien avoir besoin de manipuler des mesures de longueur exprimes en centimtres et dautres en pouces. Il peut stocker ces diffrentes valeurs dans des variables de type entire, mais cela nest pas recommand, car il serait alors trs facile de faire des calculs mixant les deux units de mesures. Dans ce genre de situation, il peut tre utile dexploiter les mcanismes de typage du langage. Comme typedef ne permet pas de sous-typer le type int en deux types distincts, la solution consiste dans ce cas encapsuler le type de base int dans deux structures, et de dnir ainsi deux types distincts pour chacune des units de mesures :
typedef struct { int cm; } centimetres_t; typedef struct { int inches; } pouces_t;

Une fois cela ralis, des variables de type centimetre_t et pouces_t pourront tre dnies et utilises sans risque de mlange involontaire. En effet, laffectation dune variable dun type une de lautre type provoquera immdiatement une erreur de compilation. Bien entendu, cela se fera au dtriment de la simplicit dcriture sur les oprations, puisquil faudra spcier la donne membre cm ou la donnes membre inches chaque accs la valeur du type de donnes. Mais la abilit du programme sera nettement suprieure :
centimetres_t l1, l2, l3; pouces_t l4; l1.cm = 12; l2 = l1; /* OK. */ l4 = l3; /* Erreur, les pouces et les centimtres sont incompatibles ! */ l3.cm = l1.cm * 2; /* OK. */ l4.cm = l2.cm + 1; /* Erreur, les pouces nont pas la donne membre cm ! */

Nous verrons toutefois que le C++ permet de rsoudre ce problme en rednissant les oprateurs sur les types de donnes. Ceux-ci seront alors utilisables directement, comme des types de donnes natifs (voir la Section 7.11).

3.5. Transtypages et promotions


Il est parfois utile de changer le type dune valeur. Considrons lexemple suivant : la division de 5 par 2 renvoie 2. En effet, 5/2 fait appel la division euclidienne. Comment faire pour obtenir le rsultat

55

Chapitre 3. Types avancs et classes de stockage avec un nombre rel ? Il faut faire 5./2, car alors 5. est un nombre ottant. Mais que faire quand on se trouve avec des variables entires (i et j par exemple) ? Le compilateur signale une erreur aprs i dans lexpression i./j ! Il faut changer le type de lune des deux variables. Cette opration sappelle le transtypage. On la ralise simplement en faisant prcder lexpression transtyper du type dsir entour de parenthses :
(type) expression

Exemple 3-16. Transtypage en C


int i=5, j=2; ((float) i)/j

Dans cet exemple, i est transtyp en ottant avant la division. On obtient donc 2.5. Le transtypage C est tout puissant et peut tre relativement dangereux. Le langage C++ fournit donc des oprateurs de transtypages plus spciques, qui permettent par exemple de conserver la constance des variables lors de leur transtypage. Ces oprateurs seront dcrits dans la Section 9.2 du chapitre traitant de lidentication dynamique des types. Le compilateur peut galement effectuer des transtypages automatiquement, notamment lors dune valuation dexpression ou lors du passage dun paramtre une fonction qui attend une valeur dun autre type. Gnralement, le compilateur ne ralise de tels transtypages automatiques que lorsque le type de donnes cible est considr comme plus grand que le type de donnes source. Par exemple, la conversion implicite dun entier en ottant est gnralement ralise de manire transparente, alors que la conversion inverse provoque un avertissement. Mais attention, ce nest pas toujours le cas. Par exemple, le compilateur convertira automatiquement, et sans avertissement, un entier en caractre, ou un nombre dun type non sign vers le type correspondant sign. Cela peut conduire des bogues assez graves.
Note : De ce fait, le choix du type des variables devra se faire en toute connaissance de cause. De manire gnrale, il est conseill de ne pas chercher gagner des bouts de chandelles en rduisant la taille des types de donnes, dune part, et de se baser sur la smantique des valeurs manipules, dautre part. Par exemple, la taille dun objet ne peut pas tre ngative, et devra gnralement tre manipule via un type de donnes non sign. Inversement, une diffrence entre deux valeurs sera souvent signe (sauf si lordre des oprandes est choisi de telle manire que cela ne soit pas le cas).

Enn, le compilateur peut effectuer des promotions sur les donnes dans certaines circonstances. Une promotion est un transtypage vers un type de donnes de taille suprieure, donc, a priori, sans perte de donnes. Cest en particulier le cas lors de lappel des fonctions nombre darguments variables. Ces fonctions nacceptent pas nimporte quel type de donnes pour leurs arguments. En particulier, les types char et short ne sont pas utiliss : les paramtres sont toujours promus aux type int ou long int. Les rgles de promotion utilises sont les suivantes :

les types char, signed char, unsigned char, short int ou unsigned short int sont promus en int si ce type est capable daccepter toutes leurs valeurs sur la plateforme considre. Si int est insufsant, unsigned int est utilis ;

56

Chapitre 3. Types avancs et classes de stockage

les types des numrations et wchar_t sont promus en int, unsigned int, long ou unsigned long selon leurs capacits. Le premier type capable de conserver la plage de valeur du type promouvoir est utilis ; les valeurs des champs de bits sont converties en int ou unsigned int selon la taille du champ de bit ; les valeurs de type oat sont converties en double.

3.6. Les classes de stockage


Les variables C/C++ peuvent tre cres de diffrentes manires. Il est courant, selon la manire dont elles sont cres et la manire dont elles pourront tre utilises, de les classer en diffrentes catgories de variables. La classication la plus simple que lon puisse faire des variables est la classication locale - globale. Les variables globales sont dclares en dehors de tout bloc dinstructions, dans la zone de dclaration globale du programme. Les variables locales en revanche sont cres lintrieur dun bloc dinstructions. Les variables locales et globales ont des dures de vie, des portes et des emplacements en mmoire diffrents. La porte dune variable est la zone du programme dans laquelle elle est accessible. La porte des variables globales est tout le programme, alors que la porte des variables locales est le bloc dinstructions dans lequel elles ont t cres. La dure de vie dune variable est le temps pendant lequel elle existe. Les variables globales sont cres au dbut du programme et dtruites la n, leur dure de vie est donc celle du programme. En gnral, les variables locales ont une dure de vie qui va du moment o elles sont dclares jusqu la sortie du bloc dinstructions dans lequel elles ont t dclares. Cependant, il est possible de faire en sorte que les variables locales survivent la sortie de ce bloc dinstructions, et soient nouveau utilisables ds que lon y entre nouveau.
Note : La porte dune variable peut commencer avant sa dure de vie si cette variable est dclare aprs le dbut du bloc dinstructions dans lequel elle est dclare. La dure de vie nest donc pas gale la porte dune variable.

La classe de stockage dune variable permet de spcier sa dure de vie et sa place en mmoire (sa porte est toujours le bloc dans lequel la variable est dclare). Le C/C++ dispose dun ventail de classes de stockage assez large et permet de spcier le type de variable que lon dsire utiliser : Tableau 3-1. Classes de stockages Classe
auto

Signication Classe de stockage par dfaut. Les variables ont pour porte le bloc dinstructions dans lequel elles ont t cres. Elles ne sont accessibles que dans ce bloc. Leur dure de vie est restreinte ce bloc.

57

Chapitre 3. Types avancs et classes de stockage Classe


static

Signication Classe de stockage permettant de crer des variables dont la porte est le bloc dinstructions en cours, mais qui, contrairement aux variables auto, ne sont pas dtruites lors de la sortie de ce bloc. chaque fois que lon rentre dans ce bloc dinstructions, les variables statiques existeront et auront pour valeurs celles quelles avaient avant que lon quitte ce bloc. Leur dure de vie est donc celle du programme, et elles conservent leurs valeurs. Un chier peut tre considr comme un bloc. Ainsi, une variable statique dun chier ne peut pas tre accde partir dun autre chier. Cela est utile pour isoler des variables globales dun chier vis vis des autres modules (voir le Chapitre 6 pour plus de dtails). Classe de stockage utilise dans les dclarations pour signaler que la variable ainsi dclare peut tre dnie dans un autre chier que le chier courant. Elle est utilise dans le cadre de la compilation spare (voir le Chapitre 6 pour plus de dtails). dans un registre du microprocesseur. Il faut bien connatre le langage machine pour correctement utiliser cette classe de variable. En pratique, cette classe est trs peu utilise, et on pfre de nos jours laisser le compilateur grer lui-mme lusage des registres du processeur.

extern

register Classe de stockage permettant de crer une variable dont lemplacement se trouve

volatile Cette classe de stockage sert lors de la programmation systme. Elle indique quune

variable peut tre modie en arrire-plan par un autre programme (par exemple par une interruption, par un thread, par un autre processus, par le systme dexploitation ou par un autre processeur dans une machine parallle). Cela ncessite donc de recharger cette variable chaque fois quon y fait rfrence dans un registre du processeur, et ce mme si elle se trouve dj dans un de ces registres (ce qui peut arriver si on a demand au compilateur doptimiser le programme).

Note : Le C++ dispose dun mcanisme plus souple disolation des entits propres un chier que ce que permet le mot cl static, via le mcanisme des espaces de nommages. De plus, le mot clef static a une autre signication en C++ dans un autre contexte dutilisation. Lemploi de ce mot cl pour rendre local un chier une variable globale est donc dconseille en C++, et lon utilisera de prfrence les mcanismes prsents dans le Chapitre 10.

Il existe galement des modicateurs pouvant sappliquer une variable pour prciser sa constance : Tableau 3-2. Qualicatifs de constance Qualicatif Signication

58

Chapitre 3. Types avancs et classes de stockage Qualicatif


const

Signication

Ce mot cl est utilis pour rendre le contenu dune variable non modiable. En quelque sorte, la variable devient ainsi une variable en lecture seule. Attention, une telle variable nest pas forcment une constante : elle peut tre modie soit par lintermdiaire dun autre identicateur, soit par une entit extrieure au programme (comme pour les variables volatile). Quand ce mot cl est appliqu une structure, aucun des champs de la structure nest accessible en criture. Bien quil puisse paratre trange de vouloir rendre constante une variable , ce mot cl a une utilit. En particulier, il permet de faire du code plus sr et dans certains circonstance permet au compilateur deffectuer des optimisations. Ce mot cl nest disponible quen C++. Il ne peut tre appliqu quaux membres des structures. Son rle est de permettre de passer outre la constance ventuelle dune structure pour un membre particulier. Ainsi, un champ de structure dclar mutable peut tre modi mme si sa structure contenante est dclare const.

mutable

Pour dclarer une classe de stockage particulire, il suft de faire prcder ou suivre le type de la variable par lun des mots cls auto, static, register, etc. On na le droit de nutiliser que les classes de stockage non contradictoires. Par exemple, register et extern sont incompatibles, de mme que register et volatile, et const et mutable. Par contre, static et const, de mme que const et volatile, peuvent tre utilises simultanment. Exemple 3-17. Dclaration dune variable locale statique
int appels(void) { static int n = 0; return n = n+1; }

Cette fonction mmorise le nombre dappels qui lui ont t faits dans la variable n et renvoie ce nombre. En revanche, la fonction suivante :
int appels(void) { int n = 0; return n =n + 1; }

renverra toujours 1. En effet, la variable n est cre, initialise, incrmente et dtruite chaque appel. Elle ne survit pas la n de linstruction return. Exemple 3-18. Dclaration dune variable constante
const int i=3; i prend la valeur 3 et ne peut plus tre modie.

Les variables globales qui sont dnies sans le mot cl const sont traites par le compilateur comme des variables de classe de stockage extern par dfaut. Ces variables sont donc accessibles partir de tous les chiers du programme. En revanche, cette rgle nest pas valide pour les variables dnies avec le mot cl const. Ces variables sont automatiquement dclares static par le compilateur, ce

59

Chapitre 3. Types avancs et classes de stockage qui signie quelles ne sont accessibles que dans le chier dans lequel elles ont t dclares. Pour les rendre accessibles aux autres chiers, il faut imprativement les dclarer avec le mot cl extern avant de les dnir. Exemple 3-19. Dclaration de constante externes
int i = 12; const int j = 11; extern const int k; const int k = 12; /* i est accessible de tous les fichiers. */ /* Synonyme de "static const int j = 11;". */ /* Dclare dabord la variable k... */ /* puis donne la dfinition. */

Notez que toutes les variables dnies avec le mot cl const doivent tre initialises lors de leur dnition. En effet, on ne peut pas modier la valeur des variables const, elles doivent donc avoir une valeur initiale. Enn, les variables statiques non initialises prennent la valeur nulle. Les mots cls const et volatile demandent au compilateur de raliser des vrications additionnelles lors de lemploi des variables qui ont ces classes de stockage. En effet, le C/C++ assure quil est interdit de modier (du moins sans magouiller) une variable de classe de stockage const, et il assure galement que toutes les rfrences une variable de classe de stockage volatile se feront sans optimisations dangereuses. Ces vrications sont bases sur le type des variables manipules. Dans le cas des types de base, ces vrications sont simples et de comprhension immdiate. Ainsi, les lignes de code suivantes :
const int i=3; int j=2; i=j; /* Illgal : i est de type const int. */

gnrent une erreur parce quon ne peut pas affecter une valeur de type int une variable de type const int. En revanche, pour les types complexes (pointeurs et rfrences en particulier), les mcanismes de vrications sont plus ns. Nous verrons quels sont les problmes soulevs par lemploi des mots cls const et volatile avec les pointeurs et les rfrences dans le chapitre traitant des pointeurs. Enn, en C++ uniquement, le mot cl mutable permet de rendre un champ de structure const accessible en criture : Exemple 3-20. Utilisation du mot cl mutable
struct A { int i; mutable int j; }; const A a={1, 1}; int main(void) { a.i=2; a.j=2; return 0; }

// Non modifiable si A est const. // Toujours modifiable.

// i et j valent 1.

// ERREUR ! a est de type const A ! // Correct : j est mutable.

60

Chapitre 4. Les pointeurs et rfrences


Les pointeurs sont des variables trs utilises en C et en C++. Ils doivent tre considrs comme des variables, il ny a rien de sorcier derrire les pointeurs. Cependant, les pointeurs ont un domaine dapplication trs vaste. Les rfrences sont des identicateurs synonymes dautres identicateurs, qui permettent de manipuler certaines notions introduites avec les pointeurs plus souplement. Elles nexistent quen C++.

4.1. Notion dadresse


Tout objet manipul par lordinateur est stock dans sa mmoire. On peut considrer que cette mmoire est constitue dune srie de cases , cases dans lesquelles sont stockes les valeurs des variables ou les instructions du programme. Pour pouvoir accder un objet (la valeur dune variable ou les instructions excuter par exemple), cest--dire au contenu de la case mmoire dans laquelle cet objet est enregistr, il faut connatre le numro de cette case. Autrement dit, il faut connatre lemplacement en mmoire de lobjet manipuler. Cet emplacement est appel ladresse de la case mmoire, et par extension, ladresse de la variable ou ladresse de la fonction stocke dans cette case et celles qui la suivent. Toute case mmoire a une adresse unique. Lorsquon utilise une variable ou une fonction, le compilateur manipule ladresse de cette dernire pour y accder. Cest lui qui connat cette adresse, le programmeur na pas sen soucier.

4.2. Notion de pointeur


Une adresse est une valeur. On peut donc stocker cette valeur dans une variable. Les pointeurs sont justement des variables qui contiennent ladresse dautres objets, par exemple ladresse dune autre variable. On dit que le pointeur pointe sur la variable pointe. Ici, pointer signie faire rfrence . Les adresses sont gnralement des valeurs constantes, car en gnral un objet ne se dplace pas en mmoire. Toutefois, la valeur dun pointeur peut changer. Cela ne signie pas que la variable pointe est dplace en mmoire, mais plutt que le pointeur pointe sur autre chose. An de savoir ce qui est point par un pointeur, les pointeurs disposent dun type. Ce type est construit partir du type de lobjet point. Cela permet au compilateur de vrier que les manipulations ralises en mmoire par lintermdiaire du pointeur sont valides. Le type des pointeur se lit pointeur de ... , o les points de suspension reprsentent le nom du type de lobjet point. Les pointeurs se dclarent en donnant le type de lobjet quils devront pointer, suivi de leur identicateur prcd dune toile :
int *pi; // pi est un pointeur dentier.

Note : Si plusieurs pointeurs doivent tre dclars, ltoile doit tre rpte :
int *pi1, *pi2, j, *pi3;

Ici, pi1, pi2 et pi3 sont des pointeurs dentiers et j est un entier.

61

Chapitre 4. Les pointeurs et rfrences Figure 4-1. Notion de pointeur et dadresse

Il est possible de faire un pointeur sur une structure dans une structure en indiquant le nom de la structure comme type du pointeur :
typedef struct nom { struct nom *pointeur; ... } MaStructure;

/* Pointeur sur une structure "nom". */

Ce type de construction permet de crer des listes de structures, dans lesquelles chaque structure contient ladresse de la structure suivante dans la liste. Nous verrons plus loin un exemple dutilisation de ce genre de structure. Il est galement possible de crer des pointeurs sur des fonctions, et dutiliser ces pointeurs pour paramtrer un algorithme, dont le comportement dpendra des fonctions ainsi pointes. Nous dtaillerons plus loin ce type dutilisation des pointeurs.

4.3. Drfrencement, indirection


Un pointeur ne servirait strictement rien sil ny avait pas de possibilit daccder ladresse dune variable ou dune fonction (on ne pourrait alors pas linitialiser) ou sil ny avait pas moyen daccder lobjet rfrenc par le pointeur (la variable pointe ne pourrait pas tre manipule ou la fonction pointe ne pourrait pas tre appele). Ces deux oprations sont respectivement appeles indirection et drfrencement. Il existe deux oprateurs permettant de rcuprer ladresse dun objet et daccder lobjet point. Ces oprateurs sont respectivement & et *. Il est trs important de sassurer que les pointeurs que lon manipule sont tous initialiss (cest-dire contiennent ladresse dun objet valide, et pas nimporte quoi). En effet, accder un pointeur

62

Chapitre 4. Les pointeurs et rfrences non initialis revient lire ou, plus grave encore, crire dans la mmoire un endroit compltement alatoire (selon la valeur initiale du pointeur lors de sa cration). En gnral, on initialise les pointeurs ds leur cration, ou, sils doivent tre utiliss ultrieurement, on les initialise avec le pointeur nul. Cela permettra de faire ultrieurement des tests sur la validit du pointeur ou au moins de dtecter les erreurs. En effet, lutilisation dun pointeur initialis avec le pointeur nul gnre souvent une faute de protection du programme, que tout bon dbogueur est capable de dtecter. Le pointeur nul se note NULL.
Note : NULL est une macro dnie dans le chier den-tte stdlib.h. En C, elle reprsente la valeur dune adresse invalide. Malheureusement, cette valeur peut ne pas tre gale ladresse 0 (certains compilateurs utilisent la valeur -1 pour NULL par exemple). Cest pour cela que cette macro a t dnie, an de reprsenter, selon le compilateur, la bonne valeur. Voir le Chapitre 5 pour plus de dtails sur les macros et sur les chiers den-tte. La norme du C++ xe la valeur nulle des pointeurs 0. Par consquent, les compilateurs C/C++ qui dnissent NULL comme tant gal -1 posent un problme de portabilit certain, puisque un programme C qui utilise NULL nest plus valide en C++. Par ailleurs, un morceau de programme C++ compilable en C qui utiliserait la valeur 0 ne serait pas correct en C. Il faut donc faire un choix : soit utiliser NULL en C et 0 en C++, soit utiliser NULL partout, quitte rednir la macro NULL pour les programmes C++ (solution qui me semble plus pratique).

Exemple 4-1. Dclaration de pointeurs


int i=0; int *pi; pi=&i; *pi = *pi+1; /* Dclare une variable entire. */ /* Dclare un pointeur sur un entier. */ /* Initialise le pointeur avec ladresse de cette variable. */ /* Effectue un calcul sur la variable pointe par pi, cest--dire sur i lui-mme, puisque pi contient ladresse de i. */ /* ce stade, i ne vaut plus 0, mais 1. */

Il est prsent facile de comprendre pourquoi il faut rpter ltoile dans la dclaration de plusieurs pointeurs :
int *p1, *p2, *p3;

signie syntaxiquement : p1, p2 et p3 sont des pointeurs dentiers, mais aussi *p1, *p2 et *p3 sont des entiers. Si lon avait crit :
int *p1, p2, p3;

seul p1 serait un pointeur dentier. p2 et p3 seraient des entiers. Laccs aux champs dune structure par le pointeur sur cette structure se fera avec loprateur ->, qui remplace (*).. Exemple 4-2. Utilisation de pointeurs de structures
struct Client { int Age;

63

Chapitre 4. Les pointeurs et rfrences


}; Client structure1; Client *pstr = &structure1; pstr->Age = 35; /* On aurait pu crire (*pstr).Age=35; */

4.4. Notion de rfrence


En plus des pointeurs, le C++ permet de crer des rfrences. Les rfrences sont des synonymes didenticateurs. Elles permettent de manipuler une variable sous un autre nom que celui sous laquelle cette dernire a t dclare.
Note : Les rfrences nexistent quen C++. Le C ne permet pas de crer des rfrences.

Par exemple, si id est le nom dune variable, il est possible de crer une rfrence ref de cette variable. Les deux identicateurs id et ref reprsentent alors la mme variable, et celle-ci peut tre accde et modie laide de ces deux identicateurs indistinctement. Toute rfrence doit se rfrer un identicateur : il est donc impossible de dclarer une rfrence sans linitialiser. De plus, la dclaration dune rfrence ne cre pas un nouvel objet comme cest le cas pour la dclaration dune variable par exemple. En effet, les rfrences se rapportent des identicateurs dj existants. La syntaxe de la dclaration dune rfrence est la suivante :
type &rfrence = identificateur;

Aprs cette dclaration, rfrence peut tre utilis partout o identicateur peut ltre. Ce sont des synonymes. Exemple 4-3. Dclaration de rfrences
int i=0; int &ri=i; ri=ri+i;

// Rfrence sur la variable i. // Double la valeur de i (et de ri).

Il est possible de faire des rfrences sur des valeurs numriques. Dans ce cas, les rfrences doivent tre dclares comme tant constantes, puisquune valeur est une constante :
const int &ri=3; int &error=4; // Rfrence sur 3. // Erreur ! La rfrence nest pas constante.

4.5. Lien entre les pointeurs et les rfrences


Les rfrences et les pointeurs sont troitement lis. En effet, une variable et ses diffrentes rfrences ont la mme adresse, puisquelles permettent daccder un mme objet. Utiliser une rfrence pour

64

Chapitre 4. Les pointeurs et rfrences manipuler un objet revient donc exactement au mme que de manipuler un pointeur constant contenant ladresse de cet objet. Les rfrences permettent simplement dobtenir le mme rsultat que les pointeurs, mais avec une plus grande facilit dcriture. Cette similitude entre les pointeurs et les rfrences se retrouve au niveau syntaxique. Par exemple, considrons le morceau de code suivant :
int i=0; int *pi=&i; *pi=*pi+1;

// Manipulation de i via pi.

et faisons passer loprateur & de la deuxime ligne gauche de loprateur daffectation :


int i=0; int &*pi=i; *pi=*pi+1;

// Cela gnre une erreur de syntaxe mais nous // lignorons pour les besoins de lexplication.

Maintenant, comparons avec le morceau de code quivalent suivant :


int i=0; int &ri=i; ri=ri+1;

// Manipulation de i via ri.

Nous constatons que la rfrence ri peut tre identie avec lexpression *pi, qui reprsente bel et bien la variable i. Ainsi, la rfrence ri encapsule la manipulation de ladresse de la variable i et sutilise comme lexpression *pi. Cela permet de comprendre lorigine de la syntaxe de dclaration des rfrences. La diffrence se trouve ici dans le fait que les rfrences doivent tre initialises dune part, et que lon na pas effectuer le drfrencement dautre part. Les rfrences sont donc beaucoup plus faciles manipuler que les pointeurs, et permettent de faire du code beaucoup plus sr.

4.6. Passage de paramtres par variable ou par valeur


Il y a deux mthodes pour passer des variables en paramtre dans une fonction : le passage par valeur et le passage par variable. Ces mthodes sont dcrites ci-dessous.

4.6.1. Passage par valeur


La valeur de lexpression passe en paramtre est copie dans une variable locale. Cest cette variable qui est utilise pour faire les calculs dans la fonction appele. Si lexpression passe en paramtre est une variable, son contenu est copi dans la variable locale. Aucune modication de la variable locale dans la fonction appele ne modie la variable passe en paramtre, parce que ces modications ne sappliquent qu une copie de cette dernire. Le C ne permet de faire que des passages par valeur.

65

Chapitre 4. Les pointeurs et rfrences Exemple 4-4. Passage de paramtre par valeur
#include <stdlib.h> void test(int j) { j=3; return; } int main(void) { int i=2; test(i); /* Modifie j, mais pas la variable fournie par lappelant. */ /* j est la copie de la valeur passe en paramtre */

/* Le contenu de i est copi dans j. i nest pas modifi. Il vaut toujours 2. */ test(2); /* La valeur 2 est copie dans j. */ return EXIT_SUCCESS;

4.6.2. Passage par variable


La deuxime technique consiste passer non plus la valeur des variables comme paramtre, mais passer les variables elles-mmes. Il ny a donc plus de copie, plus de variable locale. Toute modication du paramtre dans la fonction appele entrane la modication de la variable passe en paramtre. Le C ne permet pas de faire ce type de passage de paramtres (le C++ le permet en revanche). Exemple 4-5. Passage de paramtre par variable en Pascal
Var i : integer; Procedure test(Var j : integer) Begin {La variable j est strictement gale la variable passe en paramtre.} j:=2; {Ici, cette variable est modifie.} End; Begin i:=3; {Initialise i 3} test(i); {Appelle la fonction. La variable i est passe en paramtres, pas sa valeur. Elle est modifie par la fonction test.} {Ici, i vaut 2.} End.

Puisque la fonction attend une variable en paramtre, on ne peut plus appeler test avec une valeur (test(3) est maintenant interdit, car 3 nest pas une variable : on ne peut pas le modier).

66

Chapitre 4. Les pointeurs et rfrences

4.6.3. Avantages et inconvnients des deux mthodes


Les passages par variables sont plus rapides et plus conomes en mmoire que les passages par valeur, puisque les tapes de la cration de la variable locale et la copie de la valeur ne sont pas faites. Il faut donc viter les passages par valeur dans les cas dappels rcursifs de fonction ou de fonctions travaillant avec des grandes structures de donnes (matrices par exemple). Les passages par valeurs permettent dviter de dtruire par mgarde les variables passes en paramtre. Si lon veut se prvenir de la destruction accidentelle des paramtres passs par variable, il faut utiliser le mot cl const. Le compilateur interdira alors toute modication de la variable dans la fonction appele, ce qui peut parfois obliger cette fonction raliser des copies de travail en local.

4.6.4. Comment passer les paramtres par variable en C ?


Il ny a quune solution : passer ladresse de la variable. Cela constitue donc une application des pointeurs. Voici comment lExemple 4-5 serait programm en C : Exemple 4-6. Passage de paramtre par variable en C
#include <stdlib.h> void test(int *pj) { *pj=2; return; } /* test attend ladresse dun entier... */ /* ... pour le modifier. */

int main(void) { int i=3; test(&i); /* On passe ladresse de i en paramtre. */ /* Ici, i vaut 2. */ return EXIT_SUCCESS; }

prsent, il est facile de comprendre la signication de & dans lappel de scanf : les variables entrer sont passes par variable.

4.6.5. Passage de paramtres par rfrence


La solution du C est exactement la mme que celle du Pascal du point de vue smantique. En fait, le Pascal procde exactement de la mme manire en interne, mais la manipulation des pointeurs est masque par le langage. Cependant, plusieurs problmes se posent au niveau syntaxique :

la syntaxe est lourde dans la fonction, cause de lemploi de loprateur * devant les paramtres ; la syntaxe est dangereuse lors de lappel de la fonction, puisquil faut systmatiquement penser utiliser loprateur & devant les paramtres. Un oubli devant une variable de type entier et la valeur de lentier est utilise la place de son adresse dans la fonction appele (plantage assur, essayez avec scanf).

67

Chapitre 4. Les pointeurs et rfrences Le C++ permet de rsoudre tous ces problmes laide des rfrences. Au lieu de passer les adresses des variables, il suft de passer les variables elles-mmes en utilisant des paramtres sous la forme de rfrences. La syntaxe des paramtres devient alors :
type &identificateur [, type &identificateur [...]]

Exemple 4-7. Passage de paramtre par rfrence en C++


#include <stdlib.h> void test(int &i) { i = 2; return; } // i est une rfrence du paramtre constant. // Modifie le paramtre pass en rfrence.

int main(void) { int i=3; test(i); // Aprs lappel de test, i vaut 2. // Loprateur & nest pas ncessaire pour appeler // test. return EXIT_SUCCESS; }

Il est recommand, pour des raisons de performances, de passer par rfrence tous les paramtres dont la copie peut prendre beaucoup de temps (en pratique, seuls les types de base du langage pourront tre passs par valeur). Bien entendu, il faut utiliser des rfrences constantes au maximum an dviter les modications accidentelles des variables de la fonction appelante dans la fonction appele. En revanche, les paramtres de retour des fonctions ne devront pas tre dclars comme des rfrences constantes, car on ne pourrait pas les crire si ctait le cas. Exemple 4-8. Passage de paramtres constant par rfrence
typedef struct { ... } structure; void ma_fonction(const structure & s) { ... return ; }

Dans cet exemple, s est une rfrence sur une structure constante. Le code se trouvant lintrieur de la fonction ne peut donc pas utiliser la rfrence s pour modier la structure (on notera cependant que cest la fonction elle-mme qui sinterdit lcriture dans la variable s. const est donc un mot cl coopratif . Il nest pas possible un programmeur dempcher ses collgues dcrire dans ses variables avec le mot cl const. Nous verrons dans le Chapitre 7 que le C++ permet de pallier ce problme grce une technique appele lencapsulation.).

68

Chapitre 4. Les pointeurs et rfrences Un autre avantage des rfrences constantes pour les passages par variables est que si le paramtre nest pas une variable ou, sil nest pas du bon type, une variable locale du type du paramtre est cre et initialise avec la valeur du paramtre transtyp. Exemple 4-9. Cration dun objet temporaire lors dun passage par rfrence
#include <stdlib.h> void test(const int &i) { ... // Utilisation de la variable i // dans la fonction test. La variable // i est cre si ncessaire. return ; } int main(void) { test(3); // Appel de test avec une constante. return EXIT_SUCCESS; }

Au cours de cet appel, une variable locale est cre (la variable i de la fonction test), et 3 lui est affecte.

4.7. Rfrences et pointeurs constants et volatiles


Lutilisation des mots cls const et volatile avec les pointeurs et les rfrences est un peu plus complique quavec les types simples. En effet, il est possible de dclarer des pointeurs sur des variables, des pointeurs constants sur des variables, des pointeurs sur des variables constantes et des pointeurs constants sur des variables constantes (bien entendu, il en est de mme avec les rfrences). La position des mots cls const et volatile dans les dclarations des types complexes est donc extrmement importante. En gnral, les mots cls const et volatile caractrisent ce qui les prcde dans la dclaration, si lon adopte comme rgle de toujours les placer aprs les types de base. Par exemple, lexpression suivante :
const int *pi;

peut tre rcrite de la manire suivante :


int const *pi;

puisque le mot cl const est interchangeable avec le type le plus simple dans une dclaration. Ce mot cl caractrise donc le type int, et pi est un pointeur sur un entier constant. En revanche, dans lexemple suivant :
int j; int * const pi=&j; pi est dclar comme tant constant, et de type pointeur dentier. Il sagit donc dun pointeur constant sur un entier non constant, que lon initialise pour rfrencer la variable j.

69

Chapitre 4. Les pointeurs et rfrences


Note : Les dclarations C++ peuvent devenir trs compliques et difciles lire. Il existe une astuce qui permet de les interprter facilement. Lors de lanalyse de la dclaration dun identicateur X, il faut toujours commencer par une phrase du type X est un ... . Pour trouver la suite de la phrase, il suft de lire la dclaration en partant de lidenticateur et de suivre lordre impos par les priorits des oprateurs. Cet ordre peut tre modi par la prsence de parenthses. Vous trouverez en annexe les priorits de tous les oprateurs du C++. Ainsi, dans lexemple suivant :
const int *pi[12]; void (*pf)(int * const pi);

la premire dclaration se lit de la manire suivante : pi (pi) est un tableau ([]) de 12 (12) pointeurs (*) dentiers (int) constants (const) . La deuxime dclaration se lit : pf (pf) est un pointeur (*) de fonction (()) de pi (pi), qui est lui-mme une constante (const) de type pointeur (*) dentier (int). Cette fonction ne renvoie rien (void) .

Le C et le C++ nautorisent que les critures qui conservent ou augmentent les proprits de constance et de volatilit. Par exemple, le code suivant est correct :
char *pc; const char *cpc; cpc=pc; /* Le passage de pc cpc augmente la constance. */

parce quelle signie que si lon peut crire dans une variable par lintermdiaire du pointeur pc, on peut sinterdire de le faire en utilisant cpc la place de pc. En revanche, si lon na pas le droit dcrire dans une variable, on ne peut en aucun cas se le donner. Cependant, les rgles du langage relatives la modication des variables peuvent parfois paratre tranges. Par exemple, le langage interdit une criture telle que celle-ci :
char *pc; const char **ppc; ppc = &pc; /* Interdit ! */

Pourtant, cet exemple ressemble beaucoup lexemple prcdent. On pourrait penser que le fait daffecter un pointeur de pointeur de variable un pointeur de pointeur de variable constante revient sinterdire dcrire dans une variable quon a le droit de modier. Mais en ralit, cette criture va contre les rgles de constances, parce quelle permettrait de modier une variable constante. Pour sen convaincre, il faut regarder lexemple suivant :
const char c=a; char *pc; const char **ppc=&pc; *ppc=&c; *pc=b; /* La variable constante. */ /* Pointeur par lintermdiaire duquel nous allons modifier c. */ /* Interdit, mais supposons que ce ne le soit pas. */ /* Parfaitement lgal. */ /* Modifie la variable c. */

70

Chapitre 4. Les pointeurs et rfrences Que sest-il pass ? Nous avons, par lintermdiaire de ppc, affect ladresse de la constante c au pointeur pc. Malheureusement, pc nest pas un pointeur de constante, et cela nous a permis de modier la constante c. An de grer correctement cette situation (et les situations plus complexes qui utilisent des triples pointeurs ou encore plus dindirection), le C et le C++ interdisent laffectation de tout pointeur dont les proprits de constance et de volatilit sont moindres que celles du pointeur cible. La rgle exacte est la suivante : 1. On note cv les diffrentes qualications de constance et de volatilit possibles ( savoir : const volatile, const, volatile ou aucune classe de stockage). 2. Si le pointeur source est un pointeur cvs,0 de pointeur cvs,1 de pointeur ... de pointeur cvs,n-1 de type Ts cvs,n, et que le pointeur destination est un pointeur cvd,0 de pointeur cvd,1 de pointeur ... de pointeur cvd,n-1 de type Td cvs,n, alors laffectation de la source la destination nest lgale que si :

les types source Ts et destination Td sont compatibles ; il existe un nombre entier strictement positif N tel que, quel que soit j suprieur ou gal N, on ait :

si const apparat dans cvs,j, alors const apparat dans cvd,j ; si volatile apparat dans cvs,j, alors volatile apparat dans cvd,j ; et tel que, quel que soit 0<k<N, const apparaisse dans cvd,k.

Ces rgles sont sufsamment compliques pour ne pas tre apprises. Les compilateurs se chargeront de signaler les erreurs sil y en a en pratique. Par exemple :
const char c=a; const char *pc; const char **ppc=&pc; *ppc=&c; *pc=b;

/* Lgal prsent. */ /* Illgal (pc a chang de type). */

Laffectation de double pointeur est prsent lgale, parce que le pointeur source a chang de type (on ne peut cependant toujours pas modier le caractre c). Il existe une exception notable ces rgles : linitialisation des chanes de caractres. Les chanes de caractres telles que :
"Bonjour tout le monde !"

sont des chanes de caractres constantes. Par consquent, on ne peut thoriquement affecter leur adresse qu des pointeurs de caractres constants :
const char *pc="Coucou !"; /* Code correct. */

Cependant, il a toujours t dusage de raliser linitialisation des chanes de caractres de la manire suivante :

71

Chapitre 4. Les pointeurs et rfrences


char *pc="Coucou !"; /* Thoriquement illgal, mais tolr par compatibilit avec le C. */

Par compatibilit, le langage fournit donc une conversion implicite entre const char * et char * . Cette facilit ne doit pas pour autant vous inciter transgresser les rgles de constance : utilisez les pointeurs sur les chanes de caractres constants autant que vous le pourrez (quitte raliser quelques copies de chanes lorsquun pointeur de caractre simple doit tre utilis). Sur certains systmes, lcriture dans une chane de caractres constante peut provoquer un plantage immdiat du programme.

4.8. Arithmtique des pointeurs


Il est possible deffectuer des oprations arithmtiques sur les pointeurs. Les seules oprations valides sont les oprations externes (addition et soustraction des entiers) et la soustraction de pointeurs. Elles sont dnies comme suit (la soustraction dun entier est considre comme laddition dun entier ngatif) :
p + i = adresse contenue dans p + i*taille(lment point par p)

et :
p2 - p1 = (adresse contenue dans p2 - adresse contenue dans p1) / taille(lments points par p1 et p2)

Si p est un pointeur dentier, p+1 est donc le pointeur sur lentier qui suit immdiatement celui point par p. On retiendra surtout que lentier quon additionne au pointeur est multipli par la taille de llment point pour obtenir la nouvelle adresse. Le type du rsultat de la soustraction de deux pointeurs est trs dpendant de la machine cible et du modle mmoire du programme. En gnral, on ne pourra jamais supposer que la soustraction de deux pointeurs est un entier (que les chevronns du C me pardonnent, mais cest une erreur trs grave). En effet, ce type peut tre insufsant pour stocker des adresses (une machine peut avoir des adresses sur 64 bits et des donnes sur 32 bits). Pour rsoudre ce problme, le chier den-tte stdlib.h contient la dnition du type utiliser pour la diffrence de deux pointeurs. Ce type est nomm ptrdiff_t. Exemple 4-10. Arithmtique des pointeurs
int i, j; ptrdiff_t delta = &i - &j; int error = &i - &j;

/* Correct */ /* Peut marcher, mais par chance. */

Il est possible de connatre la taille dun lment en caractres en utilisant loprateur sizeof. Il a la syntaxe dune fonction :
sizeof(type|expression)

Il attend soit un type, soit une expression. La valeur retourne est soit la taille du type en caractres, soit celle du type de lexpression. Dans le cas des tableaux, il renvoie la taille totale du tableau. Si son

72

Chapitre 4. Les pointeurs et rfrences argument est une expression, celle-ci nest pas value (donc si il contient un appel une fonction, celle-ci nest pas appele). Par exemple :
sizeof(int)

renvoie la taille dun entier en caractres, et :


sizeof(2+3)

renvoie la mme taille, car 2+3 est de type entier. 2+3 nest pas calcul.
Note : Loprateur sizeof renvoie la taille des types en tenant compte de leur alignement. Cela signie par exemple que mme si un compilateur espace les lments dun tableau an de les aligner sur des mots mmoire de la machine, la taille des lments du tableau sera celle des objets de mme type qui ne se trouvent pas dans ce tableau (ils devront donc tre aligns eux aussi). On a donc toujours lgalit suivante :
sizeof(tableau) = sizeof(lment) * nombre dlments

4.9. Utilisation des pointeurs avec les tableaux


Les tableaux sont troitement lis aux pointeurs parce que, de manire interne, laccs aux lments des tableaux se fait par manipulation de leur adresse de base, de la taille des lments et de leurs indices. En fait, ladresse du n-ime lment dun tableau est calcule avec la formule :
Adresse_n = Adresse_Base + n*taille(lment)

o taille(lment) reprsente la taille de chaque lment du tableau et Adresse_Base ladresse de base du tableau. Cette adresse de base est ladresse du dbut du tableau, cest donc la fois ladresse du tableau et ladresse de son premier lment. Ce lien apparat au niveau du langage dans les conversions implicites de tableaux en pointeurs, et dans le passage des tableaux en paramtre des fonctions. An de pouvoir utiliser larithmtique des pointeurs pour manipuler les lments des tableaux, le C++ effectue les conversions implicites suivantes lorsque ncessaire :

tableau vers pointeur dlment ; pointeur dlment vers tableau.

Cela permet de considrer les expressions suivantes comme quivalentes :


identificateur[n]

et :
*(identificateur + n)

si identificateur est soit un identicateur de tableau, soit celui dun pointeur.

73

Chapitre 4. Les pointeurs et rfrences Exemple 4-11. Accs aux lments dun tableau par pointeurs
int tableau[100]; int *pi=tableau; tableau[3]=5; /* Le 4me lment est initialis 5 */ *(tableau+2)=4; /* Le 3me lment est initialis 4 */ pi[5]=1; /* Le 6me lment est initialis 1 */ Note : Le langage C++ impose que ladresse suivant le dernier lment dun tableau doit toujours tre valide. Cela ne signie absolument pas que la zone mmoire rfrence par cette adresse est valide, bien au contraire, mais plutt que cette adresse est valide. Il est donc garantit que cette adresse ne sera pas le pointeur NULL par exemple, ni toute autre valeur spciale quun pointeur ne peut pas stocker. Il sera donc possible de faire des calculs darithmtique des pointeurs avec cette adresse, mme si elle ne devra jamais tre drfrence, sous peine de voir le programme planter. On prendra garde certaines subtilits. Les conversions implicites sont une facilit introduite par le compilateur, mais en ralit, les tableaux ne sont pas des pointeurs, ce sont des variables comme les autres, ceci prs : leur type est convertible en pointeur sur le type de leurs lments. Il en rsulte parfois quelques ambiguts lorsquon manipule les adresses des tableaux. En particulier, on a lgalit suivante :
&tableau == tableau

en raison du fait que ladresse du tableau est la mme que celle de son premier lment. Il faut bien comprendre que dans cette expression, une conversion a lieu. Cette galit nest donc pas exacte en thorie. En effet, si ctait le cas, on pourrait crire :
*&tableau == tableau

puisque les oprateurs * et & sont conjugus, do :


tableau == *&tableau = *(&tableau) == *(tableau) == t[0]

ce qui est faux (le type du premier lment nest en gnral pas convertible en type pointeur.).

La consquence la plus importante de la conversion tableau vers pointeur se trouve dans le passage par variable des tableaux dans une fonction. Lors du passage dun tableau en paramtre dune fonction, la conversion implicite a lieu, les tableaux sont donc toujours passs par variable, jamais par valeur. Il est donc faux dutiliser des pointeurs pour les passer en paramtre, car le paramtre aurait le type pointeur de tableau. On ne modierait pas le tableau, mais bel et bien le pointeur du tableau. Le programme aurait donc de fortes chances de planter. Si un passage par valeur du tableau est dsir, il faut lencapsuler dans une structure.

4.10. Les chanes de caractres : pointeurs et tableaux la fois !


On a vu dans le premier chapitre que les chanes de caractres nexistaient pas en C/C++. Ce sont en ralit des tableaux de caractres dont le dernier caractre est le caractre nul.

74

Chapitre 4. Les pointeurs et rfrences Cela a plusieurs consquences. La premire, cest que les chanes de caractres sont aussi des pointeurs sur des caractres, ce qui se traduit dans la syntaxe de la dclaration dune chane de caractres constante :
const char *identificateur = "chane";

identificateur est dclar ici comme tant un pointeur de caractre, puis il est initialis avec ladresse de la chane de caractres constante "chane".

La deuxime est le fait quon ne peut pas faire, comme en Pascal, des affectations de chanes de caractres, ni des comparaisons. Par exemple, si nom1 et nom2 sont des chanes de caractres, lopration :
nom1=nom2;

nest pas laffectation du contenu de nom2 nom1. Cest une affectation de pointeur : le pointeur nom1 est gal au pointeur nom2 et pointent sur la mme chane ! Une modication de la chane pointe par nom1 entrane donc la modication de la chane pointe par nom2... De mme, le test nom1==nom2 est un test entre pointeurs, pas entre chanes de caractres. Mme si deux chanes sont gales, le test sera faux si elles ne sont pas au mme emplacement mmoire. Il existe dans la bibliothque C de nombreuses fonctions permettant de manipuler les chanes de caractres. Par exemple, la copie dune chane de caractres dans une autre se fera avec les fonctions strcpy et strncpy, la comparaison de deux chanes de caractres pourra tre ralise laide des fonctions strcmp et strncmp, et la dtermination de la longueur dune chane de caractres laide de la fonction strlen. Je vous invite consulter la documentation de votre environnement de dveloppement ou la bibliographie pour dcouvrir toutes les fonctions de manipulation des chanes de caractres. Nous en verrons un exemple dutilisation dans la section suivante.

4.11. Allocation dynamique de mmoire


Les pointeurs sont surtout utiliss pour crer un nombre quelconque de variables, ou des variables de taille quelconque, en cours dexcution du programme. En temps normal, les variables sont cres automatiquement lors de leur dnition. Cela est faisable parce que les variables crer ainsi que leurs tailles sont connues au moment de la compilation (cest le but des dclarations que dindiquer la structure et la taille des objets, et plus gnralement de donner les informations ncessaires leur utilisation). Par exemple, une ligne comme :
int tableau[10000];

signale au compilateur quune variable tableau de 10000 entiers doit tre cre. Le programme sen chargera donc automatiquement lors de lexcution. Mais supposons que le programme gre une liste de personnes. On ne peut pas savoir lavance combien de personnes seront entres, le compilateur ne peut donc pas faire la rservation de lespace mmoire automatiquement. Cest au programmeur de le faire. Cette rservation de mmoire (appele encore allocation) doit tre faite pendant lexcution du programme. La diffrence avec la dclaration de tableau prcdente, cest que le nombre de personnes et donc la quantit de mmoire allouer, est variable. Il faut donc faire ce quon appelle une allocation dynamique de mmoire.

75

Chapitre 4. Les pointeurs et rfrences

4.11.1. Allocation dynamique de mmoire en C


Il existe deux principales fonctions C permettant de demander de la mmoire au systme dexploitation et de la lui restituer. Elles utilisent toutes les deux les pointeurs, parce quune variable alloue dynamiquement na pas didenticateur tant donn quelle ntait a priori pas connue la compilation, et na donc pas pu tre dclare. Les pointeurs utiliss par ces fonctions C nont pas de type. On les rfrence donc avec des pointeurs non typs. Leur syntaxe est la suivante :
malloc(taille) free(pointeur)

malloc (abrviation de Memory ALLOCation ) alloue de la mmoire. Elle attend comme para-

mtre la taille de la zone de mmoire allouer et renvoie un pointeur non typ (void *).
free (pour FREE memory ) libre la mmoire alloue. Elle attend comme paramtre le pointeur sur la zone librer et ne renvoie rien.

Lorsquon alloue une variable type, on doit faire un transtypage du pointeur renvoy par malloc en pointeur de ce type de variable. Pour utiliser les fonctions malloc et free, vous devez mettre au dbut de votre programme la ligne :
#include <stdlib.h>

Son rle est similaire celui de la ligne #include <stdio.h>. Vous verrez sa signication dans le chapitre concernant le prprocesseur. Lexemple suivant va vous prsenter un programme C classique qui manipule des pointeurs. Ce programme ralise des allocations dynamiques de mmoire et manipule une liste de structures dynamiquement, en fonction des entres que fait lutilisateur. Les techniques de saisies de paramtres prsentes dans le premier chapitre sont galement revues. Ce programme vous prsente aussi comment passer des paramtres par variable, soit pour optimiser le programme, soit pour les modier au sein des fonctions appeles. Enn, lutilisation du mot clef const avec les pointeurs est galement illustre. Exemple 4-12. Allocation dynamique de mmoire en C
#include <stdio.h> #include <stdlib.h> #include <string.h>

/* Fichier den-tte pour malloc et free. */ /* Fichier den-tte pour strcpy, strlen et de strcmp. */ de liste de personne. */

/* Type de base dun lment typedef struct person { char *name; /* char *address; /* struct person *next; /* } Person; typedef Person *People;

Nom de la personne. */ Adresse de la personne. */ Pointeur sur llment suivant. */

/* Type de liste de personnes. */

/* Fonctions de gestion des listes de personnes : */ /* Fonction dinitialisation dune liste de personne. La liste est passe par variable pour permettre son initialisation. */

76

Chapitre 4. Les pointeurs et rfrences


void init_list(People *lst) { *lst = NULL; } /* Fonction dajout dune personne. Les paramtres de la personne sont passs par variables, mais ne peuvent tre modifis car ils sont constants. Ce sont des chanes de caractres C, qui sont donc assimiles des pointeurs de caractres constants. */ int add_person(People *lst, const char *name, const char *address) { /* Cre un nouvel lment : */ Person *p = (Person *) malloc(sizeof(Person)); if (p != NULL) { /* Alloue la mmoire pour le nom et ladresse. Attention, il faut compter le caractre nul terminal des chanes : */ p->name = (char *) malloc((strlen(name) + 1) * sizeof(char)); p->address = (char *) malloc((strlen(address) + 1) * sizeof(char)); if (p->name != NULL && p->address != NULL) { /* Copie le nom et ladresse : */ strcpy(p->name, name); strcpy(p->address, address); p->next = *lst; *lst = p; } else { free(p); p = NULL; } } return (p != NULL); } /* Fonction de suppression dune personne. La structure de la liste est modifie par la suppression de llment de cette personne. Cela peut impliquer la modification du chanage de llment prcdent, ou la modification de la tte de liste elle-mme. */ int remove_person(People *lst, const char *name) { /* Recherche la personne et son antcdant : */ Person *prev = NULL; Person *p = *lst; while (p != NULL) { /* On sort si llment courant est la personne recherche : */ if (strcmp(p->name, name) == 0) break; /* On passe llment suivant sinon : */ prev = p; p = p->next; } if (p != NULL) {

77

Chapitre 4. Les pointeurs et rfrences


/* La personne a t trouve, on la supprime de la liste : */ if (prev == NULL) { /* La personne est en tte de liste, on met jour le pointeur de tte de liste : */ lst = p->next; * } else { /* On met jour le lien de llment prcdent : */ prev->next = p->next; } /* et on la dtruit : */ free(p->name); free(p->address); free(p); } return (p != NULL); } /* Simple fonction daffichage. */ void print_list(People const *lst) { Person const *p = *lst; int i = 1; while (p != NULL) { printf("Personne %d : %s (%s)\n", i, p->name, p->address); p = p->next; ++i; } } /* Fonction de destruction et de libration de la mmoire. */ void destroy_list(People *lst) { while (*lst != NULL) { Person *p = *lst; *lst = p->next; free(p->name); free(p->address); free(p); } return ; } int main(void) { int op = 0; size_t s; char buffer[16]; char name[256]; char address[256]; /* Cre une liste de personne : */ People p; init_list(&p);

78

Chapitre 4. Les pointeurs et rfrences


/* Utilise la liste : */ do { printf("Opration (0 = quitter, 1 = ajouter, 2 = supprimer) ?"); fgets(buffer, 16, stdin); buffer[15] = 0; op = 3; sscanf(buffer, "%d", &op); switch (op) { case 0: break; case 1: printf("Nom : "); fgets(name, 256, stdin); /* Lit le nom. */ name[255] = 0; /* Assure que le caractre nul terminal est crit. */ s = strlen(name); /* Supprime lventuel saut de ligne. */ if (name[s - 1] == \n) name[s - 1] = 0; /* Mme opration pour ladresse : */ printf("Adresse : "); fgets(address, 256, stdin); name[255] = 0; s = strlen(address); if (address[s - 1] == \n) address[s - 1] = 0; add_person(&p, name, address); break; case 2: printf("Nom : "); fgets(name, 256, stdin); name[255] = 0; s = strlen(name); if (name[s - 1] == \n) name[s - 1] = 0; if (remove_person(&p, name) == 0) { printf("Personne inconnue.\n"); } break; default: printf("Opration invalide\n"); break; } if (op != 0) print_list(&p); } while (op != 0); /* Dtruit la liste : */ destroy_list(&p); return EXIT_SUCCESS; }

Note : Comme vous pouvez le constater, la lecture des chanes de caractres saisies par lutilisateur est ralise au moyen de la fonction fgets de la bibliothque C standard. Cette fonction permet de lire une ligne complte sur le ux spci en troisime paramtre, et de stocker le rsultat dans la chane de caractres fournie en premier paramtre. Elle ne lira pas plus de caractres que le nombre indiqu en deuxime paramtre, ce qui permet de contrler la taille des lignes saisies par lutilisateur. La fonction fgets ncessite malheureusement quelques traitements supplmentaires avant de pouvoir utiliser la chane de caractres lue, car elle ncrit pas le caractre nul terminal de la chane C si le nombre maximal de caractres lire est atteint,

79

Chapitre 4. Les pointeurs et rfrences


et elle stocke le caractre de saut de ligne en n de ligne si ce nombre nest pas atteint. Il est donc ncessaire de sassurer que la ligne se termine bien par un caractre nul terminal dune part, et de supprimer le caractre de saut de ligne sil nest pas essentiel dautre part. Ces traitements constituent galement un bon exemple de manipulation des pointeurs et des chanes de caractres. Ce programme ninterdit pas les dnitions multiples de personnes ayant le mme nom. Il ninterdit pas non plus la dnition de personnes anonymes. Le lecteur pourra essayer de corriger ces petits dfauts titre dexercice, an de sassurer que les notions de pointeur sont bien assimiles. Rappelons que les pointeurs sont une notion essentielle en C et quil faut tre donc parfaitement familiaris avec eux.

4.11.2. Allocation dynamique en C++


En plus des fonctions malloc et free du C, le C++ fournit dautres moyens pour allouer et restituer la mmoire. Pour cela, il dispose doprateurs spciques : new, delete, new[] et delete[]. La syntaxe de ces oprateurs est respectivement la suivante :
new type delete pointeur new type[taille] delete[] pointeur

Les deux oprateurs new et new[] permettent dallouer de la mmoire, et les deux oprateurs delete et delete[] de la restituer. La syntaxe de new est trs simple, il suft de faire suivre le mot cl new du type de la variable allouer, et loprateur renvoie directement un pointeur sur cette variable avec le bon type. Il nest donc plus ncessaire deffectuer un transtypage aprs lallocation, comme ctait le cas pour la fonction malloc. Par exemple, lallocation dun entier se fait comme suit :
int *pi = new int; // quivalent (int *) malloc(sizeof(int)).

La syntaxe de delete est encore plus simple, puisquil suft de faire suivre le mot cl delete du pointeur sur la zone mmoire librer :
delete pi; // quivalent free(pi);

Les oprateurs new[] et delete[] sont utiliss pour allouer et restituer la mmoire pour les types tableaux. Ce ne sont pas les mmes oprateurs que new et delete, et la mmoire alloue par les uns ne peut pas tre libre par les autres. Si la syntaxe de delete[] est la mme que celle de delete, lemploi de loprateur new[] ncessite de donner la taille du tableau allouer. Ainsi, on pourra crer un tableau de 10000 entiers de la manire suivante :
int *Tableau=new int[10000];

et dtruire ce tableau de la manire suivante :

80

Chapitre 4. Les pointeurs et rfrences


delete[] Tableau;

Loprateur new[] permet galement dallouer des tableaux plusieurs dimensions. Pour cela, il suft de spcier les tailles des diffrentes dimensions la suite du type de donne des lements du tableau, exactement comme lorsque lon cre un tableau statiquement. Toutefois, seule la premire dimension du tableau peut tre variable, et les dimensions deux et suivantes doivent avoir une taille entire positive et constante. Par exemple, seule la deuxime ligne de lexemple qui suit est une allocation dynamique de tableau valide :
int i=5, j=3; int (*pi1)[3] = new int[i][3]; // Alloue un tableau de i lignes de trois entiers. int (*pi2)[3] = new int[i][j]; // Illgal, j nest pas constant.

Si lon dsire rellement avoir des tableaux dont plusieurs dimensions sont de taille variable, on devra allouer un tableau de pointeurs et, pour chaque ligne de ce tableau, allouer un autre tableau la main.
Note : Il est important dutiliser loprateur delete[] avec les pointeurs renvoys par loprateur new[] et loprateur delete avec les pointeurs renvoys par new. De plus, on ne devra pas non plus mlanger les mcanismes dallocation mmoire du C et du C++ (utiliser delete sur un pointeur renvoy par malloc par exemple). En effet, le compilateur peut allouer une quantit de mmoire suprieure celle demande par le programme an de stocker des donnes qui lui permettent de grer la mmoire. Ces donnes peuvent tre interprtes diffremment pour chacune des mthodes dallocation, si bien quune utilisation errone peut entraner soit la perte des blocs de mmoire, soit une erreur, soit un plantage.

Loprateur new[] alloue la mmoire et cre les objets dans lordre croissant des adresses. Inversement, loprateur delete[] dtruit les objets du tableau dans lordre dcroissant des adresses avant de librer la mmoire. La manire dont les objets sont construits et dtruits par les oprateurs new et new[] dpend de leur nature. Sil sagit de types de base du langage ou de structures simples, aucune initialisation particulire nest faite. La valeur des objets ainsi crs est donc indnie, et il faudra raliser linitialisation soi-mme. Si, en revanche, les objets crs sont des instances de classes C++, le constructeur de ces classes sera automatiquement appel lors de leur initialisation. Cest pour cette raison que lon devra, de manire gnrale, prfrer les oprateurs C++ dallocation et de dsallocation de la mmoire aux fonctions malloc et free du C. Ces oprateurs ont de plus lavantage de permettre un meilleur contrle des types de donnes et dviter un transtypage. Les notions de classe et de constructeur seront prsentes en dtail dans le chapitre traitant de la couche objet du C++. Lorsquil ny a pas assez de mmoire disponible, les oprateurs new et new[] peuvent se comporter de deux manires selon limplmentation. Le comportement le plus rpandu est de renvoyer un pointeur nul. Cependant, la norme C++ indique un comportement diffrent : si loprateur new manque de mmoire, il doit appeler un gestionnaire derreur. Ce gestionnaire ne prend aucun paramtre et ne renvoie rien. Selon le comportement de ce gestionnaire derreur, plusieurs actions peuvent tre faites :

soit ce gestionnaire peut corriger lerreur dallocation et rendre la main loprateur new ( le programme nest donc pas termin), qui effectue une nouvelle tentative pour allouer la mmoire demande ; soit il ne peut rien faire. Dans ce cas, il peut mettre n lexcution du programme ou lancer une exception std::bad_alloc, qui remonte alors jusqu la fonction appelant loprateur new.

81

Chapitre 4. Les pointeurs et rfrences Cest le comportement du gestionnaire install par dfaut dans les implmentations conformes la norme.

Loprateur new est donc susceptible de lancer une exception std::bad_alloc. Voir le Chapitre 8 pour plus de dtails ce sujet. Il est possible de remplacer le gestionnaire derreur appel par loprateur new laide de la fonction std::set_new_handler, dclare dans le chier den-tte new. Cette fonction attend en paramtre un pointeur sur une fonction qui ne prend aucun paramtre et ne renvoie rien. Elle renvoie ladresse du gestionnaire derreur prcdent.
Note : La fonction std::set_new_handler et la classe std::bad_alloc font partie de la bibliothque standard C++. Comme leurs noms lindiquent, ils sont dclars dans lespace de nommage std::, qui est rserv pour les fonctions et les classes de la bibliothque standard. Voyez aussi le Chapitre 10 pour plus de dtails sur les espaces de nommages. Si vous ne dsirez pas utiliser les mcanismes des espaces de nommage, vous devrez inclure le chier den-tte new.h au lieu de new. Attendez vous ce quun jour, tous les compilateurs C++ lancent une exception en cas de manque de mmoire lors de lappel loprateur new, car cest ce quimpose la norme. Si vous ne dsirez pas avoir grer les exceptions dans votre programme et continuer recevoir un pointeur nul en cas de manque de mmoire, vous pouvez fournir un deuxime paramtre de type std::nothrow_t loprateur new. La bibliothque standard dnit lobjet constant std::nothrow cet usage.

Les oprateurs delete et delete[] peuvent parfaitement tre appels avec un pointeur nul en paramtre. Dans ce cas, ils ne font rien et redonnent la main immdiatement lappelant. Il nest donc pas ncessaire de tester la non nullit des pointeurs sur les objets que lon dsire dtruire avant dappeler les oprateurs delete et delete[].

4.12. Pointeurs et rfrences de fonctions


4.12.1. Pointeurs de fonctions
Il est possible de faire des pointeurs de fonctions. Un pointeur de fonction contient ladresse du dbut du code binaire constituant la fonction. Il est possible dappeler une fonction dont ladresse est contenue dans un pointeur de fonction avec loprateur dindirection *. Pour dclarer un pointeur de fonction, il suft de considrer les fonctions comme des variables. Leur dclaration est identique celle des tableaux, en remplaant les crochets par des parenthses :
type (*identificateur)(paramtres);

o type est le type de la valeur renvoye par la fonction, identificateur est le nom du pointeur de la fonction et paramtres est la liste des types des variables que la fonction attend comme paramtres, spars par des virgules. Exemple 4-13. Dclaration de pointeur de fonction
int (*pf)(int, int); /* Dclare un pointeur de fonction. */

82

Chapitre 4. Les pointeurs et rfrences


pf est un pointeur de fonction attendant comme paramtres deux entiers et renvoyant un entier.

Il est possible dutiliser typedef pour crer un alias du type pointeur de fonction :
typedef int (*PtrFonct)(int, int); PtrFonct pf;

PtrFonct est le type des pointeurs de fonctions.

Si f est une fonction rpondant ces critres, on peut alors initialiser pf avec ladresse de f. De mme, on peut appeler la fonction pointe par pf avec loprateur dindirection. Exemple 4-14. Drfrencement de pointeur de fonction
#include <stdlib.h> #include <stdio.h> int f(int i, int j) { return i+j; } int (*pf)(int, int); /* Dfinit une fonction. */

/* Dclare un pointeur de fonction. */

int main(void) { int l, m; /* Dclare deux entiers. */ pf = &f; /* Initialise pf avec ladresse de la fonction f. */ printf("Entrez le premier entier : "); scanf("%u",&l); /* Initialise les deux entiers. */ printf("\nEntrez le deuxime entier : "); scanf("%u",&m); /* Utilise le pointeur pf pour appeler la fonction f et affiche le rsultat : */ printf("\nLeur somme est de : %u\n", (*pf)(l,m)); return EXIT_SUCCESS; }

Lintrt des pointeurs de fonction est de permettre lappel dune fonction parmi un ventail de fonctions au choix. Par exemple, il est possible de faire un tableau de pointeurs de fonctions et dappeler la fonction dont on connat lindice de son pointeur dans le tableau. Exemple 4-15. Application des pointeurs de fonctions
#include <stdlib.h> #include <stdio.h> /* Dfinit plusieurs fonctions travaillant sur des entiers : */ int somme(int i, int j) { return i+j;

83

Chapitre 4. Les pointeurs et rfrences


} int multiplication(int i, int j) { return i*j; } int quotient(int i, int j) { return i/j; } int modulo(int i, int j) { return i%j; } typedef int (*fptr)(int, int); fptr ftab[4]; int main(void) { int i,j,n; ftab[0]=&somme; /* Initialise le tableau de pointeur */ ftab[1]=&multiplication; /* de fonctions. */ ftab[2]=&quotient; ftab[3]=&modulo; printf("Entrez le premier entier : "); scanf("%u",&i); /* Demande les deux entiers i et j. */ printf("\nEntrez le deuxime entier : "); scanf("%u",&j); printf("\nEntrez la fonction : "); scanf("%u",&n); /* Demande la fonction appeler. */ if (n < 4) printf("\nRsultat : %u.\n", (*(ftab[n]))(i,j) ); else printf("\nMauvais numro de fonction.\n"); return EXIT_SUCCESS; }

4.12.2. Rfrences de fonctions


Les rfrences de fonctions sont acceptes en C++. Cependant, leur usage est assez limit. Elles permettent parfois de simplier les critures dans les manipulations de pointeurs de fonctions. Mais comme il nest pas possible de dnir des tableaux de rfrences, le programme dexemple donn ci-dessus ne peut pas tre rcrit avec des rfrences. Les rfrences de fonctions peuvent malgr tout tre utilises prot dans le passage des fonctions en paramtre dans une autre fonction. Par exemple :
#include <stdio.h> // Fonction de comparaison de deux entiers : int compare(int i, int j)

84

Chapitre 4. Les pointeurs et rfrences


{ if (i<j) return -1; else if (i>j) return 1; else return 0; } // Fonction utilisant une fonction en tant que paramtre : void trie(int tableau[], int taille, int (&fcomp)(int, int)) { // Effectue le tri de tableau avec la fonction fcomp. // Cette fonction peut tre appele comme toute les autres // fonctions : printf("%d", fcomp(2,3)); . . . return ; } int main(void) { int t[3]={1,5,2}; trie(t, 3, compare); return 0; }

// Passage de compare() en paramtre.

4.13. Paramtres de la fonction main - ligne de commande


Lappel dun programme se fait normalement avec la syntaxe suivante :
nom param1 param2 [...]

o nom est le nom du programme appeler et param1, etc. sont les paramtres de la ligne de commande. De plus, le programme appel peut renvoyer un code derreur au programme appelant (soit le systme dexploitation, soit un autre programme). Ce code derreur est en gnral 0 quand le programme sest droul correctement. Toute autre valeur indique quune erreur sest produite en cours dexcution. La valeur du code derreur est renvoye par la fonction main. Le code derreur doit toujours tre un entier. La fonction main peut donc (et mme normalement doit) tre de type entier :
int main(void) ...

Les paramtres de la ligne de commandes peuvent tre rcuprs par la fonction main. Si vous dsirez les rcuprer, la fonction main doit attendre deux paramtres :

le premier est un entier, qui reprsente le nombre de paramtres ;

85

Chapitre 4. Les pointeurs et rfrences

le deuxime est un tableau de chanes de caractres (donc en fait un tableau de pointeurs, ou encore un pointeur de pointeurs de caractres).

Les paramtres se rcuprent avec ce tableau. Le premier lment pointe toujours sur la chane donnant le nom du programme. Les autres lments pointent sur les paramtres de la ligne de commande. Exemple 4-16. Rcupration de la ligne de commande
#include <stdlib.h> #include <stdio.h> int main(int n, char *params[]) { int i; /* Fonction principale. */

/* Affiche le nom du programme : */ printf("Nom du programme : %s.\n",params[0]); /* Affiche la ligne de commande : */ for (i=1; i<n; ++i) printf("Argument %d : %s.\n",i, params[i]); return EXIT_SUCCESS; /* Tout sest bien pass : on renvoie 0 ! */ }

4.14. DANGER
Les pointeurs sont, comme on la vu, trs utiliss en C/C++. Il faut donc bien savoir les manipuler. Mais ils sont trs dangereux, car ils permettent daccder nimporte quelle zone mmoire, sils ne sont pas correctement initialiss. Dans ce cas, ils pointent nimporte o. Accder la mmoire avec un pointeur non initialis peut altrer soit les donnes du programme, soit le code du programme lui-mme, soit le code dun autre programme ou celui du systme dexploitation. Cela conduit dans la majorit des cas au plantage du programme, et parfois au plantage de lordinateur si le systme ne dispose pas de mcanismes de protection efcaces.
VEILLEZ TOUJOURS INITIALISER LES POINTEURS QUE VOUS UTILISEZ.

Pour initialiser un pointeur qui ne pointe sur rien (cest le cas lorsque la variable pointe nest pas encore cre ou lorsquelle est inconnue lors de la dclaration du pointeur), on utilisera le pointeur prdni NULL.
VRIFIEZ QUE TOUTE DEMANDE DALLOCATION MMOIRE A T SATISFAITE.

La fonction malloc renvoie le pointeur NULL lorsquil ny a plus ou pas assez de mmoire. Le comportement des oprateurs new et new[] est diffrent. Thoriquement, ils doivent lancer une exception

86

Chapitre 4. Les pointeurs et rfrences si la demande dallocation mmoire na pas pu tre satisfaite. Cependant, certains compilateurs font en sorte quils renvoient le pointeur nul du type de lobjet crer. Sils renvoient une exception, le programme sera arrt si aucun traitement particulier nest fait. Bien entendu, le programme peut traiter cette exception sil le dsire, mais en gnral, il ny a pas grand chose faire en cas de manque de mmoire. Vous pouvez consulter le chapitre traitant des exceptions pour plus de dtails ce sujet. Dans tous les cas,
LORSQUON UTILISE UN POINTEUR, IL FAUT VRIFIER SIL EST VALIDE

(par un test avec NULL ou le pointeur nul, ou en analysant lalgorithme). Cette vrication inclut le test de dbordement lors des accs aux chanes de caractres et aux tableaux. Cela est extrmement important lorsque lon manipule des donnes provenant de lextrieur du programme, car on ne peut dans ce cas pas supposer que ces donnes sont valides.

87

Chapitre 4. Les pointeurs et rfrences

88

Chapitre 5. Le prprocesseur C
5.1. Dnition
Le prprocesseur est un programme qui analyse un chier texte et qui lui fait subir certaines transformations. Ces transformations peuvent tre linclusion dun chier, la suppression dune zone de texte ou le remplacement dune zone de texte. Le prprocesseur effectue ces oprations en suivant des ordres quil lit dans le chier en cours danalyse. Il est appel automatiquement par le compilateur, avant la compilation, pour traiter les chiers compiler.

5.2. Les directives du prprocesseur


Une directive est une commande pour le prprocesseur. Toutes les directives du prprocesseur commencent :

en dbut de ligne ; par un signe dise (#).

Le prprocesseur dispose de directives permettant dinclure des chiers, de dnir des constantes de compilation, de supprimer conditionnellement des blocs de texte, et de gnrer des erreurs ou de modier lenvironnement de compilation.

5.2.1. Inclusion de chier


Linclusion de chier permet de factoriser du texte commun plusieurs autres chiers (par exemple des dclarations de type, de constante, de fonction, etc.). Les dclarations des fonctions et des constantes sont ainsi gnralement factorises dans un chier den-tte portant lextension .h pour header , chier den-tte de programme). Par exemple, nous avons dj vu les chiers den-tte stdlib.h, stdio.h et string.h dans les chapitres prcdents. Ce sont vraissemblablement les chiers den-tte de la bibliothque C les plus couramment utiliss. Si vous ouvrez le chier stdio.h, vous y verrez la dclaration de toutes les fonctions et de tous les types de la bibliothque dentre - sortie standard (notez quelles sont peut-tre dnies dans dautres chiers den-tte, eux-mmes inclus par ce chier). De mme, vous trouverez sans doute les dclarations des fonctions malloc et free dans le chier den-tte stdlib.h. La syntaxe gnrale pour la directive dinclusion de chier est la suivante :
#include "fichier"

ou :
#include <fichier>

89

Chapitre 5. Le prprocesseur C
fichier est le nom du chier inclure. Lorsque son nom est entre guillemets, le chier spci est recherch dans le rpertoire courant (normalement le rpertoire du programme). Sil est encadr de crochets, il est recherch dabord dans les rpertoires spcis en ligne de commande avec loption -I, puis dans les rpertoires du chemin de recherche des en-ttes du systme (ces rgles ne sont pas xes, elles ne sont pas normalises).

Le chier inclus est trait lui aussi par le prprocesseur.

5.2.2. Constantes de compilation et remplacement de texte


Le prprocesseur permet de dnir des identicateurs qui, utiliss dans le programme, seront remplacs textuellement par leur valeur. La dnition de ces identicateurs suit la syntaxe suivante :
#define identificateur texte

o identificateur est lidenticateur qui sera utilis dans la suite du programme, et texte sera le texte de remplacement que le prprocesseur utilisera. Le texte de remplacement est facultatif (dans ce cas, cest le texte vide). chaque fois que lidenticateur identificateur sera rencontr par le prprocesseur, il sera remplac par le texte texte dans toute la suite du programme. Cette commande est couramment utilise pour dnir des constantes de compilation, cest--dire des constantes qui dcrivent les paramtres de la plateforme pour laquelle le programme est compil. Ces constantes permettent de raliser des compilations conditionnelles, cest--dire de modier le comportement du programme en fonction de paramtres dnis lors de sa compilation. Elle est galement utilise pour remplacer des identicateurs du programme par dautres identicateurs, par exemple an de tester plusieurs versions dune mme fonction sans modier tout le programme. Exemple 5-1. Dnition de constantes de compilation
#define UNIX_SOURCE #define POSIX_VERSION 1001

Dans cet exemple, lidenticateur UNIX_SOURCE sera dni dans toute la suite du programme, et la constante de compilation POSIX_VERSION sera remplace par 1001 partout o elle apparatra.
Note : On fera une distinction bien nette entre les constantes de compilation dnies avec la directive #define du prprocesseur et les constantes dnies avec le mot cl const. En effet, les constantes littrales ne rservent pas de mmoire. Ce sont des valeurs immdiates, dnies par le compilateur. En revanche, les variables de classe de stockage const peuvent malgr tout avoir une place mmoire rserve. Ce peut par exemple tre le cas si lon manipule leur adresse ou sil ne sagit pas de vraies constantes, par exemple si elles peuvent tre modies par lenvironnement (dans ce cas, elles doivent tre dclares avec la classe de stockage volatile). Ce sont donc plus des variables accessibles en lecture seule que des constantes. On ne pourra jamais supposer quune variable ne change pas de valeur sous prtexte quelle a la classe de stockage const, alors quvidemment, une constante littrale dclare avec la directive #define du prprocesseur conservera toujours sa valeur (pourvu quon ne la rednisse pas). Par ailleurs, les constantes littrales nont pas de type, ce qui peut tre trs gnant et source derreur. On rservera donc leur emploi uniquement pour les constantes de compilation, et on prfrera le mot cl const pour toutes les autres constantes du programme.

Le prprocesseur dnit un certain nombre de constantes de compilation automatiquement. Ce sont les suivantes :

90

Chapitre 5. Le prprocesseur C
__LINE__ __FILE__ __DATE__ __TIME__

: donne le numro de la ligne courante ; : donne le nom du chier courant ; : renvoie la date du traitement du chier par le prprocesseur ; : renvoie lheure du traitement du chier par le prprocesseur ; : dnie uniquement dans le cas dune compilation C++. Sa valeur doit tre

__cplusplus

199711L pour les compilateurs compatibles avec le projet de norme du 2 dcembre 1996. En

pratique, sa valeur est dpendante de limplmentation utilise, mais on pourra utiliser cette chane de remplacement pour distinguer les parties de code crites en C++ de celles crites en C.

Note : Si __FILE__, __DATE__, __TIME__ et __cplusplus sont bien des constantes pour un chier donn, ce nest pas le cas de __LINE__. En effet, cette dernire constante change bien videmment de valeur chaque ligne. On peut considrer quelle est rednie automatiquement par le prprocesseur chaque dbut de ligne.

5.2.3. Compilation conditionnelle


La dnition des identicateurs et des constantes de compilation est trs utilise pour effectuer ce que lon appelle la compilation conditionnelle. La compilation conditionnelle consiste remplacer certaines portions de code source par dautres, en fonction de la prsence ou de la valeur de constantes de compilation. Cela est ralisable laide des directives de compilation conditionnelle, dont la plus courante est sans doute #ifdef :
#ifdef identificateur . . . #endif

Dans lexemple prcdent, le texte compris entre le #ifdef (cest--dire if dened ) et le #endif est laiss tel quel si lidenticateur identificateur est connu du prprocesseur. Sinon, il est supprim. Lidenticateur peut tre dclar en utilisant simplement la commande #define vue prcdemment. Il existe dautres directives de compilation conditionnelle :
#ifndef #elif #if (if not defined ...) (sinon, si ... ) (si ... )

La directive #if attend en paramtre une expression constante. Le texte qui la suit est inclus dans le chier si et seulement si cette expression est non nulle. Par exemple :
#if (__cplusplus==199711L) . . . #endif

permet dinclure un morceau de code C++ strictement conforme la norme dcrite dans le projet de norme du 2 dcembre 1996.

91

Chapitre 5. Le prprocesseur C Une autre application courante des directives de compilation est la protection des chiers den-tte contre les inclusions multiples :
#ifndef DejaLa #define DejaLa Texte ninclure quune seule fois au plus. #endif

Cela permet dviter que le texte soit inclus plusieurs fois, la suite de plusieurs appels de #include. En effet, au premier appel, DejaLa nest pas connu du prprocesseur. Il est donc dclar et le texte est inclus. Lors de tout autre appel ultrieur, DejaLa existe, et le texte nest pas inclus. Ce genre dcriture se rencontre dans les chiers den-tte, pour lesquels en gnral on ne veut pas quune inclusion multiple ait lieu.

5.2.4. Autres directives


Le prprocesseur est capable deffectuer dautres actions que linclusion et la suppression de texte. Les directives qui permettent deffectuer ces actions sont indiques ci-dessous :

: ne fait rien (directive nulle) ; : permet de stopper la compilation en afchant le message derreur donn en

#error message

paramtre ;
#line numro [fichier]

: permet de changer le numro de ligne courant et le nom du chier courant lors de la compilation ; tout en conservant la portabilit du programme. Toute implmentation qui ne reconnat pas un ordre donn dans une directive #pragma doit lignorer pour viter des messages derreurs. Le format des ordres que lon peut spcier laide de la directive #pragma nest pas normalis et dpend de chaque compilateur.

#pragma texte : permet de donner des ordres spciques une limplmentation du compilateur

5.3. Les macros


Le prprocesseur peut, lors du mcanisme de remplacement de texte, utiliser des paramtres fournis lidenticateur remplacer. Ces paramtres sont alors replacs sans modication dans le texte de remplacement. Le texte de remplacement est alors appel macro. La syntaxe des macros est la suivante :
#define macro(paramtre[, paramtre [...]]) dfinition

Exemple 5-2. Macros MIN et MAX


#define MAX(x,y) ((x)>(y)?(x):(y)) #define MIN(x,y) ((x)<(y)?(x):(y))

92

Chapitre 5. Le prprocesseur C
Note : Pour poursuivre une dnition sur la ligne suivante, terminez la ligne courante par le signe \.

Le mcanisme des macros permet de faire lquivalent de fonctions gnrales, qui fonctionnent pour tous les types. Ainsi, la macro MAX renvoie le maximum de ses deux paramtres, quils soient entiers, longs ou rels. Cependant, on prendra garde au fait que les paramtres passs une macro sont valus par celle-ci chaque fois quils sont utiliss dans la dnition de la macro. Cela peut poser des problmes de performances ou, pire, provoquer des effets de bords indsirables. Par exemple, lutilisation suivante de la macro MIN :
MIN(f(3), 5)

provoque le remplacement suivant :


((f(3))<(5))?(f(3)):(5))

soit deux appels de la fonction f si f(3) est infrieur 5, et un seul appel sinon. Si la fonction f ainsi appele modie des variables globales, le rsultat de la macro ne sera certainement pas celui attendu, puisque le nombre dappels est variable pour une mme expression. On vitera donc, autant que faire se peut, dutiliser des expressions ayant des effets de bords en paramtres dune macro. Les critures du type :
MIN(++i, j)

sont donc prohiber. Il est possible de faire des macros nombre darguments variables depuis la norme C99. Ces macros se dclarent comme des fonctions nombre darguments variables, ces arguments pouvant tre rcuprs en blocs grce la macro prfnie __VA_ARGS__ et utiliss en lieu et place de ces arguments dans une fonction nombre darguments variable classique. Exemple 5-3. Macro nombre darguments variable
#include <stdlib.h> #include <stdio.h> /* Dfinition dune macro dcriture formate sur le flux derreur standard : */ #define printerrf(...) \ { \ fprintf(stderr, __VA_ARGS__); \ } int main(void) { printerrf("Erreur %d (%s)\n", 5, "erreur dentre / sortie"); return EXIT_SUCCESS; }

On mettra toujours des parenthses autour des paramtres de la macro. En effet, ces paramtres peuvent tre des expressions composes, qui doivent tre calcules compltement avant dtre utilises dans la macro. Les parenthses forcent ce calcul. Si on ne les met pas, les rgles de priorits

93

Chapitre 5. Le prprocesseur C peuvent gnrer une erreur de logique dans la macro elle-mme. De mme, on entourera de parenthses les macros renvoyant une valeur, an de forcer leur valuation complte avant toute utilisation dans une autre expression. Par exemple :
#define mul(x,y) x*y

est une macro fausse. La ligne :


mul(2+3,5+9)

sera remplace par :


2+3*5+9

ce qui vaut 26, et non pas 70 comme on laurait attendu. La bonne macro est :
#define mul(x,y) ((x)*(y))

car elle donne le texte suivant :


((2+3)*(5+9))

et le rsultat est correct. De mme, la macro :


#define add(x,y) (x)+(y)

est fausse, car lexpression suivante :


add(2,3)*5

est remplace textuellement par :


(2)+(3)*5

dont le rsultat est 17 et non 25 comme on laurait espr. Cette macro doit donc se dclarer comme suit :
#define add(x,y) ((x)+(y))

Ainsi, les parenthses assurent un comportement cohrent de la macro. Comme on le voit, les parenthses peuvent alourdir les dnitions des macros, mais elles sont absolument ncessaires. Le rsultat du remplacement dune macro par sa dnition est, lui aussi, soumis au prprocesseur. Par consquent, une macro peut utiliser une autre macro ou une constante dnie avec #define. Cependant, ce mcanisme est limit aux macros qui nont pas encore t remplaces an dviter une rcursion innie du prprocesseur. Par exemple :
#define toto(x) toto((x)+1)

dnit la macro toto. Si plus loin on utilise toto(3) , le texte de remplacement nal sera toto((3)+1) et non pas lexpression innie (...(((3)+1)+1...)+1 . Le prprocesseur dnit automatiquement la macro defined, qui permet de tester si un identicateur est connu du prprocesseur. Sa syntaxe est la suivante :

94

Chapitre 5. Le prprocesseur C
defined(identificateur)

La valeur de cette macro est 1 si lidenticateur existe, 0 sinon. Elle est utilise principalement avec la directive #if. Il est donc quivalent dcrire :
#if defined(identificateur) . . . #endif

et :
#ifdef identificateur . . . #endif

Cependant, defined permet lcriture dexpressions plus complexes que la directive #if.

5.4. Manipulation de chanes de caractres dans les macros


Le prprocesseur permet deffectuer des oprations sur les chanes de caractres. Tout argument de macro peut tre transform en chane de caractres dans la dnition de la macro sil est prcd du signe #. Par exemple, la macro suivante :
#define CHAINE(s) #s

transforme son argument en chane de caractres. Par exemple :


CHAINE(2+3)

devient :
"2+3"

Lors de la transformation de largument, toute occurrence des caractres " et \ est transforme respectivement en \" et \\ pour conserver ces caractres dans la chane de caractres de remplacement. Le prprocesseur permet galement la concatnation de texte grce loprateur ##. Les arguments de la macro qui sont spars par cet oprateur sont concatns (sans tre transforms en chanes de caractres cependant). Par exemple, la macro suivante :
#define NOMBRE(chiffre1,chiffre2) chiffre1##chiffre2

permet de construire un nombre deux chiffres :


NOMBRE(2,3)

95

Chapitre 5. Le prprocesseur C est remplac par le nombre dcimal 23. Le rsultat de la concatnation est ensuite analys pour dventuels remplacements additionnels par le prprocesseur.

5.5. Les trigraphes


Le jeu de caractres utilis par le langage C++ comprend toutes les lettres en majuscules et en minuscules, tous les chiffres et les caractres suivants :
. , ; : ! ? " + - ^ * % = & | ~ _ # / \ { } [ ] () < >

Malheureusement, certains environnements sont incapables de grer quelques-uns de ces caractres. Cest pour rsoudre ce problme que les trigraphes ont t crs. Les trigraphes sont des squences de trois caractres commenant par deux points dinterrogations. Ils permettent de remplacer les caractres qui ne sont pas accessibles sur tous les environnements. Vous nutiliserez donc sans doute jamais les trigraphes, moins dy tre forc. Les trigraphes disponibles sont dnis ci-dessous : Tableau 5-1. Trigraphes Trigraphe
??= ??/ ?? ??( ??) ??! ??< ??> ??-

Caractre de remplacement
# \ ^ [ ] | { } ~

96

Chapitre 6. Modularit des programmes et gnration des binaires


La modularit est le fait, pour un programme, dtre crit en plusieurs morceaux relativement indpendants les uns des autres. La modularit a dnormes avantages lors du dveloppement dun programme. Cependant, elle implique un processus de gnration de lexcutable assez complexe. Dans ce chapitre, nous allons voir lintrt de la modularit, les diffrentes tapes qui permettent la gnration de lexcutable et linuence de ces tapes sur la syntaxe du langage.

6.1. Pourquoi faire une programmation modulaire ?


Ce qui cote le plus cher en informatique, cest le dveloppement de logiciel, pas le matriel. En effet, dvelopper un logiciel demande du temps, de la main duvre qualie, et nest pas facile (il y a toujours des erreurs). Les logiciels dvelopps sont souvent spciques un type de problme donn, alors que le matriel, conu pour tre gnrique, est utilisable dans de nombreuses situations diverses et bncie dconomies dchelle qui amortissent les frais de recherche et de production. Autrement dit, les cots spciques ont t dplacs, tord ou raison, du matriel vers le logiciel. Donc pour chaque problme, il faut faire un logiciel qui le rsoud. Les cots de ralisation dun logiciel se situent certainement majoritairement dans les tches de spcications et de conception. Les tches de plus bas niveau, tels que le codage et la programmation, sont gnralement plus techniques et sont plus faciles raliser. Quasiment tout le monde cherche donc optimiser chacune de ces tches et en diminuer les cots. Pour ce faire, une branche de linformatique a t dveloppe : le gnie logiciel. Le gnie logiciel donne les grands principes appliquer lors de la ralisation dun programme, de la conception la distribution, et sur toute la dure de vie du projet. Ce sujet dpasse largement le cadre de ce cours, aussi je ne parlerais que de laspect codage seul, qui est bien entendu plus technique, et qui concerne le programmeur C/C++. Au niveau du codage, le plus important est la programmation modulaire. Les ides qui en sont la base sont les suivantes :

diviser le travail en plusieurs quipes ; crer des morceaux de programme indpendants de la problmatique globale, donc rutilisables pour dautres logiciels ; supprimer les risques derreurs quon avait en reprogrammant ces morceaux chaque fois.

Je tiens prciser que les principes de la programmation modulaire ne sappliquent pas quaux programmes dvelopps par des quipes de programmeurs, bien au contraire ! Ils sappliquent aussi aux programmeurs individuels. En effet, comme la plupart des programmeurs individuels attaquent gnralement les problmes directement au niveau codage (alors quils devraient le faire au niveau conception, voire de spcication des besoins et de dlimitation du primtre fonctionnel), les seules techniques de gnie logiciel quils peuvent appliquer sont les techniques de codage. De plus, il est plus facile de dcomposer un problme en ses lments, forcment plus simples, que de le traiter dans sa totalit (dixit Descartes), et une conception modulaire devient ainsi une aide. Pour parvenir ce but, il est indispensable de pouvoir dcouper un programme en sous-programmes indpendants, ou presque indpendants. Pour que chacun puisse travailler sur sa partie de programme

97

Chapitre 6. Modularit des programmes et gnration des binaires et que les problmes rsolus soient indpendants et rutilisables, il faut que ces morceaux de programme soient dans des chiers spars. Pour pouvoir vrier ces morceaux de programme, il faut que les compilateurs puissent les compiler indpendamment, sans avoir les autres chiers du programme. Ainsi, le dveloppement de chaque chier peut se faire relativement indpendamment de celui des autres. Cependant, cette division du travail implique des oprations assez complexes pour gnrer lexcutable.

6.2. Les diffrentes phases du processus de gnration des excutables


Les phases du processus qui conduisent lexcutable partir des chiers sources dun programme sont dcrites ci-dessous. Ces phases ne sont en gnral pas spciques au C++ et peuvent souvent tre gnralises tout langage compil. Mme si les diffrents outils de programmation peuvent les cacher, le processus de gnration des excutables se droule toujours selon les principes qui suivent. Au dbut de la gnration de lexcutable, on ne dispose que des chiers sources du programme, crit en C, C++ ou tout autre langage (ce qui suit nest pas spcique au C/C++). En gnral, la premire tape est le traitement des chiers sources avant compilation. Dans le cas du C et du C++, il sagit des oprations effectues par le prprocesseur (remplacement de macros, suppression de texte, inclusion de chiers...). Vient ensuite la compilation spare, qui est le fait de compiler sparment les chiers sources. Le rsultat de la compilation dun chier source est gnralement un chier en assembleur, cest--dire le langage dcrivant les instructions du microprocesseur de la machine cible pour laquelle le programme est destin. Les chiers en assembleur peuvent tre traduits directement en ce que lon appelle des chiers objets. Les chiers objets contiennent la traduction du code assembleur en langage machine. Ils contiennent aussi dautres informations, par exemple les donnes initialises et les informations qui seront utilises lors de la cration du chier excutable partir de tous les chiers objets gnrs. Les chiers objets peuvent tre regroups en bibliothques statiques, an de rassembler un certain nombre de fonctionnalits qui seront utilises ultrieurement. Enn, ltape nale du processus de compilation est le regroupement de toutes les donnes et de tout le code des chiers objets du programme et des bibliothques (fonctions de la bibliothque C standard et des autres bibliothques complmentaires), ainsi que la rsolution des rfrences inter-chiers. Cette tape est appele dition de liens ( linking en anglais). Le rsultat de ldition de liens est le chier image, qui pourra tre charg en mmoire par le systme dexploitation. Les chiers excutables et les bibliothques dynamiques sont des exemples de chiers image.

98

Chapitre 6. Modularit des programmes et gnration des binaires Figure 6-1. Processus de gnration des binaires

Toutes ces oprations peuvent tre rgroupes en une seule tape par les outils utiliss. Ainsi, les compilateurs appellent gnralement le prprocesseur et lassembleur automatiquement, et ralisent parfois mme ldition de liens eux-mmes. Toutefois, il reste gnralement possible, laide doptions spciques chaque outil de dveloppement, de dcomposer les diffrentes tapes et dobtenir les chiers intermdiaires. En raison du nombre de chiers important et des dpendances qui peuvent exister entre eux, le processus de gnration dun programme prend trs vite une certaine ampleur. Les deux problmes les plus courants sont de dterminer lordre dans lequel les chiers et les bibliothques doivent tre compils, ainsi que les dpendances entre chiers sources et les chiers produits an de pouvoir regnrer

99

Chapitre 6. Modularit des programmes et gnration des binaires correctement les chiers images aprs une modication des sources. Tous ces problmes peuvent tre rsolus laide dun programme appel make. Le principe de make est toujours le mme, mme si aucune norme na t dnie en ce qui le concerne. make lit un chier (le chier ( makefile ), dans lequel se trouvent toutes les oprations ncessaires pour compiler un programme. Puis, il les excute si cest ncessaire. Par exemple, un chier qui a dj t compil et qui na pas t modi depuis ne sera pas recompil. Cest plus rapide. make se base sur les dates de dernire modication des chiers pour savoir sils ont t modis (il compare les dates des chiers sources et des chiers produits). La date des chiers est gre par le systme dexploitation : il est donc important que lordinateur soit lheure.

6.3. Compilation spare en C/C++


La compilation spare en C/C++ se fait au niveau du chier. Il existe trois grands types de chiers sources en C/C++ :

les chiers den-tte, qui contiennent toutes les dclarations communes plusieurs chiers sources. Ce sont les chiers den-ttes qui, en sparant la dclaration de la dnition des symboles du programme, permettent de dcouper lensemble des sources en chiers compilables sparment ; les chiers C, qui contiennent les dnitions des symboles en langage C ; les chiers C++, qui contiennent les dnitions des symboles en langage C++.

Les programmes modulaires C/C++ auront donc typiquement la structure suivante :

Note : Il faudra bien faire la distinction entre les chiers sources compils sparment et les chiers inclus par le prprocesseur. Ces derniers sont en effet compils avec les chiers dans

100

Chapitre 6. Modularit des programmes et gnration des binaires


lesquels ils sont inclus. Il nest donc pas recommand dinclure des dnitions de symboles dans les chiers den-tte, car ces symboles risquent dapparatre dans plusieurs chiers objets aprs la compilation. Cela provoque gnralement une erreur ldition de liens, parce que lditeur de liens ne peut pas dterminer quelle dnition prendre parmi celles qui se trouvent dans les diffrents chiers objets.

6.4. Syntaxe des outils de compilation


Il existe videmment un grand nombre de compilateurs C/C++ pour chaque plateforme. Ils ne sont malheureusement pas compatibles au niveau de la ligne de commande. Le mme problme apparat pour les diteurs de liens ( linker en anglais) et pour make. Cependant, quelques principes gnraux peuvent tre tablis. Dans la suite, je supposerai que le nom du compilateur est cc , que celui du prprocesseur est cpp , celui de lditeur de liens est ld et que celui de make est make . En gnral, les diffrentes tapes de la compilation et de ldition de liens sont regroupes au niveau du compilateur, ce qui permet de faire les phases de traitement du prprocesseur, de compilation et ddition de liens en une seule commande. Les lignes de commandes des compilateurs sont donc souvent compliques et trs peu portable. En revanche, la syntaxe de make est un peu plus portable.

6.4.1. Syntaxe des compilateurs


Le compilateur demande en gnral les noms des chiers sources compiler et les noms des chiers objets utiliser lors de la phase ddition de liens. Lorsque lon spcie un chier source, le compilateur utilisera le chier objet quil aura cr pour ce chier source en plus des chiers objets donns dans la ligne de commande. Le compilateur peut aussi accepter en ligne de commande le chemin de recherche des bibliothques du langage et des chiers den-tte. Enn, diffrentes options doptimisation sont disponibles (mais trs peu portables). La syntaxe (simplie) des compilateurs est souvent la suivante :
cc [fichier.o [...]] [[-c] fichier.c [...]] [-o excutable] [-Lchemin_bibliothques] [-lbibliothque [...]] [-Ichemin_include] fichier.c est le nom du chier compiler. Si loption -c le prcde, le chier sera compil, mais

lditeur de liens ne sera pas appel. Si cette option nest pas prsente, lditeur de liens est appel, et le programme excutable form est enregistr dans le chier a.out. Pour donner un autre nom ce programme, il faut utiliser loption -o, suivie du nom de lexcutable. Il est possible de donner le nom des chiers objets dj compils ( fichier.o ) pour que lditeur de liens les lie avec le programme compil. Loption -L permet dindiquer le chemin du rpertoire des bibliothques de fonctions prdnies. Ce rpertoire sera ajout la liste des rpertoires indiqus dans la variable denvironnement LIBRARY_PATH. Loption -l demande au compilateur dutiliser la bibliothque spcie, si elle ne fait pas partie des bibliothques utilises par dfaut. De mme, loption -I permet de donner le chemin daccs au rpertoire des chiers inclure (lors de lutilisation du prprocesseur). Les chemins ajouts avec cette option viennent sajouter aux chemins indiqus dans les variables denvironnement C_INCLUDE_PATH et CPLUS_INCLUDE_PATH pour les programmes compils respectivement en C et en C++. Lordre des paramtres sur la ligne de commande est signicatif. La ligne de commande est excute de gauche droite.

101

Chapitre 6. Modularit des programmes et gnration des binaires Exemple 6-1. Compilation dun chier et dition de liens
cc -c fichier1.c cc fichier1.o programme.cc -o lancez_moi

Dans cet exemple, le chier C fichier1.c est compil en fichier1.o, puis le chier C++ programme.cc est compil et li au fichier1.o pour former lexcutable lancez_moi.

6.4.2. Syntaxe de make


La syntaxe de make est trs simple :
make

En revanche, la syntaxe du chier makefile est un peu plus complique et peu portable. Cependant, les fonctionnalits de base sont gres de la mme manire par la plupart des programme make. Le chier makefile est constitu dune srie de lignes dinformation et de lignes de commande (de linterprteur de commandes UNIX ou DOS). Les commandes doivent toujours tre prcdes dun caractre de tabulation horizontale. Les lignes dinformation donnent des renseignements sur les dpendances des chiers (en particulier, les chiers objets qui doivent tre utiliss pour crer lexcutable). Les lignes dinformation permettent donc make didentier les chiers sources compiler an de gnrer lexcutable. Les lignes de commande indiquent comment effectuer cette compilation (et ventuellement dautres tches). La syntaxe des lignes dinformation est la suivante :
nom:dpendance

o nom est le nom de la cible (gnralement, il sagit du nom du chier destination), et dpendance est la liste des noms des chiers dont dpend cette cible, spars par des espaces. La syntaxe des lignes de commande utilise est celle de linterprteur du systme hte. Enn, les commentaires dans un chier makele se font avec le signe dise (#). Exemple 6-2. Fichier makele sans dpendances
# Compilation du fichier fichier1.c : cc - c fichier1.c # Compilation du programme principal : cc -o Lancez_moi fichier1.o programme.c

Exemple 6-3. Fichier makele avec dpendances


# Indique les dpendances : Lancez_moi: fichier1.o programme.o # Indique comment compiler le programme : # (le symbole $@ reprsente le nom de la cible, ici, Lancez_moi) cc -o $@ fichier1.o programme.o #compile les dpendances :

102

Chapitre 6. Modularit des programmes et gnration des binaires


fichier1.o: fichier1.c cc -c fichier1.c programme.o: programme1.c cc -c programme.c

6.5. Problmes syntaxiques relatifs la compilation spare


Pour que le compilateur puisse compiler les chiers sparment, il faut que vous respectiez les conditions suivantes :

chaque type ou variable utilis doit tre dclar ; toute fonction non dclare doit renvoyer un entier (en C seulement, en C++, lutilisation dune fonction non dclare gnre une erreur).

Ces conditions ont des rpercussions sur la syntaxe des programmes. Elles seront vues dans les paragraphes suivants.

6.5.1. Dclaration des types


Les types doivent toujours tre dnis avant toute utilisation dans un chier source. Par exemple, il est interdit dutiliser une structure client sans lavoir dnie avant sa premire utilisation. Toutefois, il est possible dutiliser un pointeur sur un type de donne sans lavoir compltement dni. Une simple dclaration du type de base du pointeur suft en effet dans ce cas l. De mme, un simple class MaClasse suft en C++ pour dclarer une classe sans la dnir compltement.

6.5.2. Dclaration des variables


Les variables qui sont dnies dans un autre chier doivent tre dclares avant leur premire utilisation. Pour cela, on les spcie comme tant des variables externes, avec le mot cl extern :
extern int i; /* i est un entier qui est dclar et cr dans un autre fichier. Ici, il est simplement dclar. */

Inversement, si une variable ne doit pas tre accde par un autre module, il faut dclarer cette variable statique. Ainsi, mme si un autre chier utilise le mot cl extern, il ne pourra pas y accder.

103

Chapitre 6. Modularit des programmes et gnration des binaires

6.5.3. Dclaration des fonctions


Lorsquune fonction se trouve dnie dans un autre chier, il est ncessaire de la dclarer. Pour cela, il suft de donner sa dclaration (le mot cl extern est galement utilisable, mais facultatif dans ce cas) :
int factorielle(int); /* factorielle est une fonction attendant comme paramtre un entier et renvoyant une valeur entire. Elle est dfinie dans un autre fichier. / *

Les fonctions inline doivent imprativement tre dnies dans les chiers o elles sont utilises, puisquen thorie, elles sont recopies dans les fonctions qui les utilisent. Cela implique de placer leur dnition dans les chiers den-tte .h ou .hpp. Comme le code des fonctions inline est normalement inclus dans le code des fonctions qui les utilisent, les chiers den-tte contenant du code inline peuvent tre compils sparment sans que ces fonctions ne soient dnies plusieurs fois. Par consquent, lditeur de liens ne gnrera pas derreur (alors quil laurait fait si on avait plac le code dune fonction non inline dans un chier den-tte inclus dans plusieurs chiers sources .c ou .cpp). Certains programmeurs considrent quil nest pas bon de placer des dnitions de fonctions dans des chiers den-tte, il placent donc toutes leurs fonctions inline dans des chiers portant lextension .inl. Ces chiers sont ensuite inclus soit dans les chiers den-tte .h, soit dans les chiers .c ou .cpp qui utilisent les fonctions inline.

6.5.4. Directives ddition de liens


Le langage C++ donne la possibilit dappeler des fonctions et dutiliser des variables qui proviennent dun module crit dans un autre langage. Pour permettre cela, il dispose de directives permettant dindiquer comment ldition de liens doit tre faite. La syntaxe permettant de raliser cela utilise le mot cl extern, avec le nom du langage entre guillemets. Cette directive ddition de liens doit prcder les dclarations de variables et de donnes concernes. Si plusieurs variables ou fonctions utilisent la mme directive, elles peuvent tre regroupes dans un bloc dlimit par des accolades, avec la directive ddition de liens place juste avant ce bloc. La syntaxe est donc la suivante :
extern "langage" [dclaration | { dclaration [...] }]

Cependant, les seuls langages quune implmentation doit obligatoirement supporter sont les langages C et C++ . Pour les autres langages, aucune norme nest dnie et les directives ddition de liens sont dpendantes de limplmentation. Exemple 6-4. Dclarations utilisables en C et en C++
#ifdef __cplusplus extern "C" { #endif

104

Chapitre 6. Modularit des programmes et gnration des binaires


extern int EntierC; int FonctionC(void); #ifdef __cplusplus } #endif

Dans lexemple prcdent, la compilation conditionnelle est utilise pour nutiliser la directive ddition de liens que si le code est compil en C++. Si cest le cas, la variable EntierC et la fonction FonctionC sont dclares au compilateur C++ comme tant des objets provenant dun module C.

105

Chapitre 6. Modularit des programmes et gnration des binaires

106

Chapitre 7. C++ : la couche objet


La couche objet constitue sans doute la plus grande innovation du C++ par rapport au C. Le but de la programmation objet est de permettre une abstraction entre limplmentation des modules et leur utilisation, apportant ainsi un plus grand confort dans la programmation. Elle sintgre donc parfaitement dans le cadre de la modularit. Enn, lencapsulation des donnes permet une meilleure protection et donc une plus grande abilit des programmes.

7.1. Gnralits
Thoriquement, il y a une nette distinction entre les donnes et les oprations qui leur sont appliques. En tout cas, les donnes et le code ne se mlangent pas dans la mmoire de lordinateur, sauf cas trs particuliers (autoprogrammation, alias pour le chargement des programmes ou des overlays, dbogueurs, virus). Cependant, lanalyse des problmes traiter se prsente dune manire plus naturelle si lon considre les donnes avec leurs proprits. Les donnes constituent les variables, et les proprits les oprations quon peut leur appliquer. De ce point de vue, les donnes et le code sont logiquement insparables, mme sils sont placs en diffrents endroits de la mmoire de lordinateur. Ces considrations conduisent la notion dobjet. Un objet est un ensemble de donnes sur lesquelles des procdures peuvent tre appliques. Ces procdures ou fonctions applicables aux donnes sont appeles mthodes. La programmation dun objet se fait donc en indiquant les donnes de lobjet et en dnissant les procdures qui peuvent lui tre appliques. Il se peut quil y ait plusieurs objets identiques, dont les donnes ont bien entendu des valeurs diffrentes, mais qui utilisent le mme jeu de mthodes. On dit que ces diffrents objets appartiennent la mme classe dobjets. Une classe constitue donc une sorte de type, et les objets de cette classe en sont des instances. La classe dnit donc la structure des donnes, alors appeles champs ou variables dinstances, que les objets correspondants auront, ainsi que les mthodes de lobjet. chaque instanciation, une allocation de mmoire est faite pour les donnes du nouvel objet cr. Linitialisation de lobjet nouvellement cr est faite par une mthode spciale, le constructeur. Lorsque lobjet est dtruit, une autre mthode est appele : le destructeur. Lutilisateur peut dnir ses propres constructeurs et destructeurs dobjets si ncessaire. Comme seules les valeurs des donnes des diffrents objets dune classe diffrent, les mthodes sont mises en commun pour tous les objets dune mme classe (cest--dire que les mthodes ne sont pas recopies). Pour que les mthodes appeles pour un objet sachent sur quelles donnes elles doivent travailler, un pointeur sur lobjet contenant ces donnes leur est pass en paramtre. Ce mcanisme est compltement transparent pour le programmeur en C++. Nous voyons donc que non seulement la programmation oriente objet est plus logique, mais elle est galement plus efcace (les mthodes sont mises en commun, les donnes sont spares). Enn, les donnes des objets peuvent tre protges : cest--dire que seules les mthodes de lobjet peuvent y accder. Ce nest pas une obligation, mais cela accrot la abilit des programmes. Si une erreur se produit, seules les mthodes de lobjet doivent tre vries. De plus, les mthodes constituent ainsi une interface entre les donnes de lobjet et lutilisateur de lobjet (un autre programmeur). Cet utilisateur na donc pas savoir comment les donnes sont gres dans lobjet, il ne doit utiliser que les mthodes. Les avantages sont immdiats : il ne risque pas de faire des erreurs de programmation en modiant les donnes lui-mme, lobjet est rutilisable dans un autre programme parce quil a une interface standardise, et on peut modier limplmentation interne de lobjet sans avoir refaire tout le programme, pourvu que les mthodes gardent le mme nom, les mmes paramtres et la mme

107

Chapitre 7. C++ : la couche objet smantique. Cette notion de protection des donnes et de masquage de limplmentation interne aux utilisateurs de lobjet constitue ce que lon appelle lencapsulation. Les avantages de lencapsulation seront souvent mis en valeur dans la suite au travers dexemples. Nous allons entrer maintenant dans le vif du sujet. Cela permettra de comprendre ces gnralits.

7.2. Extension de la notion de type du C


Il faut avant tout savoir que la couche objet nest pas un simple ajout au langage C, cest une vritable extension. En effet, les notions quelle a apportes ont t intgres au C tel point que le typage des donnes de C a fusionn avec la notion de classe. Ainsi, les types prdnis char, int, double, etc. reprsentent prsent lensemble des proprits des variables ayant ce type. Ces proprits constituent la classe de ces variables, et elles sont accessibles par les oprateurs. Par exemple, laddition est une opration pouvant porter sur des entiers (entre autres) qui renvoie un objet de la classe entier. Par consquent, les types de base se manipuleront exactement comme des objets. Du point de vue du C++, les utiliser revient dj faire de la programmation oriente objet. De mme, le programmeur peut, laide de la notion de classe dobjets, dnir de nouveaux types. Ces types comprennent la structure des donnes reprsentes par ces types et les oprations qui peuvent leur tre appliques. En fait, le C++ assimile compltement les classes avec les types, et la dnition dun nouveau type se fait donc en dnissant la classe des variables de ce type.

7.3. Dclaration de classes en C++


An de permettre la dnition des mthodes qui peuvent tre appliques aux structures des classes C++, la syntaxe des structures C a t tendue (et simplie). Il est prsent possible de dnir compltement des mthodes dans la dnition de la structure. Cependant il est prfrable de la reporter et de ne laisser que leur dclaration dans la structure. En effet, cela accrot la lisibilit et permet de masquer limplmentation de la classe ses utilisateurs en ne leur montrant que sa dclaration dans un chier den-tte. Ils ne peuvent donc ni la voir, ni la modier (en revanche, ils peuvent toujours voir la structure de donnes utilise par son implmentation). La syntaxe est la suivante :
struct Nom { [type champs; [type champs; [...]]] [mthode; [mthode; [...]]] };

o Nom est le nom de la classe. Elle peut contenir divers champs de divers types. Les mthodes peuvent tre des dnitions de fonctions, ou seulement leurs dclarations. Si on ne donne que leurs dclarations, on devra les dnir plus loin. Pour cela, il faudra spcier la classe laquelle elles appartiennent avec la syntaxe suivante :
type classe::nom(paramtres) {

108

Chapitre 7. C++ : la couche objet


/* Dfinition de la mthode. */ }

La syntaxe est donc identique la dnition dune fonction normale, la diffrence prs que leur nom est prcd du nom de la classe laquelle elles appartiennent et de deux deux-points (::). Cet oprateur :: est appel loprateur de rsolution de porte. Il permet, dune manire gnrale, de spcier le bloc auquel lobjet qui le suit appartient. Ainsi, le fait de prcder le nom de la mthode par le nom de la classe permet au compilateur de savoir de quelle classe cette mthode fait partie. Rien ninterdit, en effet, davoir des mthodes de mme signature, pourvu quelles soient dans des classes diffrentes. Exemple 7-1. Dclaration de mthodes de classe
struct Entier { int i;

// Donne membre de type entier.

// Fonction dfinie lintrieur de la classe : int lit_i(void) { return i; } // Fonction dfinie lextrieur de la classe : void ecrit_i(int valeur); }; void Entier::ecrit_i(int valeur) { i=valeur; return ; }

Note : Si la liste des paramtres de la dnition de la fonction contient des initialisations supplmentaires celles qui ont t spcies dans la dclaration de la fonction, les deux jeux dinitialisations sont fusionnes et utilises dans le chier o la dnition de la fonction est place. Si les initialisations sont redondantes ou contradictoires, le compilateur gnre une erreur.

Note : Loprateur de rsolution de porte permet aussi de spcier le bloc dinstructions dun objet qui nappartient aucune classe. Pour cela, on ne mettra aucun nom avant loprateur de rsolution de porte. Ainsi, pour accder une fonction globale lintrieur dune classe contenant une fonction de mme signature, on fera prcder le nom de la fonction globale de cet oprateur. Exemple 7-2. Oprateur de rsolution de porte
int valeur(void) { return 0; } struct A { // Fonction globale.

109

Chapitre 7. C++ : la couche objet


int i; void fixe(int a) { i=a; return; } int valeur(void) { return i; } // Mme signature que la fonction globale.

int global_valeur(void) { return ::valeur(); // Accde la fonction globale. } };

De mme, loprateur de rsolution de porte permettra daccder une variable globale lorsquune autre variable homonyme aura t dnie dans le bloc en cours. Par exemple :

#include <stdlib.h> int i=1; int main(void) { if (test()) { int i=3; int j=2*::i; /* Suite ... */ } /* Suite ... */ return EXIT_SUCCESS; } // Premire variable de porte globale

// Variable homonyme de porte locale. // j vaut prsent 2, et non pas 6.

Les champs dune classe peuvent tre accds comme des variables normales dans les mthodes de cette classe. Exemple 7-3. Utilisation des champs dune classe dans une de ses mthodes
struct client { char Nom[21], Prenom[21]; unsigned int Date_Entree; int Solde; bool dans_le_rouge(void) { return (Solde<0); }

// Dfinit le client. // Date dentre du client // dans la base de donnes.

110

Chapitre 7. C++ : la couche objet

bool bon_client(void) {

// Le bon client est // un ancien client.

return (Date_Entree<1993); // Date limite : 1993. } };

Dans cet exemple, le client est dni par certaines donnes. Plusieurs mthodes sont dnies dans la classe mme. Linstanciation dun objet se fait comme celle dune simple variable :
classe objet;

Par exemple, si on a une base de donnes devant contenir 100 clients, on peut faire :
client clientele[100]; /* Instancie 100 clients. */

On remarquera quil est prsent inutile dutiliser le mot cl struct pour dclarer une variable, contrairement ce que la syntaxe du C exigeait. Laccs aux mthodes de la classe se fait comme pour accder aux champs des structures. On donne le nom de lobjet et le nom du champ ou de la mthode, spars par un point. Par exemple :
/* Relance de tous les mauvais payeurs. */ int i; for (i=0; i<100; ++i) if (clientele[i].dans_le_rouge()) relance(clientele[i]);

Lorsque les fonctions membres dune classe sont dnies dans la dclaration de cette classe, le compilateur les implmente en inline ( moins quelles ne soient rcursives ou quil existe un pointeur sur elles). Si les mthodes ne sont pas dnies dans la classe, la dclaration de la classe sera mise dans un chier den-tte, et la dnition des mthodes sera reporte dans un chier C++, qui sera compil et li aux autres chiers utilisant la classe client. Bien entendu, il est toujours possible de dclarer les fonctions membres comme tant des fonctions inline mme lorsquelles sont dnies en dehors de la dclaration de la classe. Pour cela, il faut utiliser le mot cl inline, et placer le code de ces fonctions dans le chier den-tte ou dans un chier .inl. Sans fonctions inline, notre exemple devient : Fichier client.h :
struct client { char Nom[21], Prenom[21]; unsigned int Date_Entree; int Solde; bool dans_le_rouge(void); bool bon_client(void);

111

Chapitre 7. C++ : la couche objet


}; /* Attention ne pas oublier le ; la fin de la classe dans un fichier .h ! Lerreur apparatrait dans tous les fichiers ayant une ligne #include "client.h" , parce que la compilation a lieu aprs lappel au prprocesseur. */

Fichier client.cc :
/* Inclut la dclaration de la classe : */ #include "client.h" /* Dfinit les mthodes de la classe : */ bool client::dans_le_rouge(void) { return (Solde<0); } bool client::bon_client(void) { return (Date_Entree<1993); }

7.4. Encapsulation des donnes


Les divers champs dune structure sont accessibles en nimporte quel endroit du programme. Une opration telle que celle-ci est donc faisable :
clientele[0].Solde = 25000;

Le solde dun client peut donc tre modi sans passer par une mthode dont ce serait le but. Elle pourrait par exemple vrier que lon naffecte pas un solde suprieur au solde maximal autoris par le programme (la borne suprieure des valeurs des entiers signs). Par exemple, si les entiers sont cods sur 16 bits, cette borne maximum est 32767. Un programme qui ferait :
clientele[0].Solde = 32800;

obtiendrait donc un solde de -12 (valeur en nombre sign du nombre non sign 32800), alors quil esprerait obtenir un solde positif ! Il est possible dempcher laccs des champs ou de certaines mthodes toute fonction autre que celles de la classe. Cette opration sappelle lencapsulation. Pour la raliser, il faut utiliser les mots cls suivants :
public

: les accs sont libres ; : les accs sont autoriss dans les fonctions de la classe seulement ;

private

112

Chapitre 7. C++ : la couche objet


protected

: les accs sont autoriss dans les fonctions de la classe et de ses descendantes (voir la section suivante) seulement. Le mot cl protected nest utilis que dans le cadre de lhritage des classes. La section suivante dtaillera ce point.

Pour changer les droits daccs des champs et des mthodes dune classe, il faut faire prcder ceuxci du mot cl indiquant les droits daccs suivi de deux points (:). Par exemple, pour protger les donnes relatives au client, on changera simplement la dclaration de la classe en :
struct client { private: // Donnes prives : char Nom[21], Prenom[21]; unsigned int Date_Entree; int Solde; // Il ny a pas de mthode prive. public: // Les donnes et les mthodes publiques :

// Il ny a pas de donne publique. bool dans_le_rouge(void); bool bon_client(void) };

Outre la vrication de la validit des oprations, lencapsulation a comme intrt fondamental de dnir une interface stable pour la classe au niveau des mthodes et donnes membres publiques et protges. Limplmentation de cette interface, ralise en priv, peut tre modie loisir sans pour autant perturber les utilisateurs de cette classe, tant que cette interface nest pas elle-mme modie. Par dfaut, les classes construites avec struct ont tous leurs membres publics. Il est possible de dclarer une classe dont tous les lments sont par dfaut privs. Pour cela, il suft dutiliser le mot cl class la place du mot cl struct. Exemple 7-4. Utilisation du mot cl class
class client { // private est prsent inutile. char Nom[21], Prenom[21]; unsigned int Date_Entree; int Solde; public: // Les donnes et les mthodes publiques.

bool dans_le_rouge(void); bool bon_client(void); };

Enn, il existe un dernier type de classe, que je me contenterai de mentionner : les classes union. Elles se dclarent comme les classes struct et class, mais avec le mot cl union. Les donnes sont, comme pour les unions du C, situes toutes au mme emplacement, ce qui fait qucrire dans lune

113

Chapitre 7. C++ : la couche objet dentre elle provoque la destruction des autres. Les unions sont trs souvent utilises en programmation systme, lorsquun polymorphisme physique des donnes est ncessaire (cest--dire lorsquelles doivent tre interprtes de diffrentes faons selon le contexte).
Note : Les classes de type union ne peuvent pas avoir de mthodes virtuelles et de membres statiques. Elles ne peuvent pas avoir de classes de base, ni servir de classe de base. Enn, les unions ne peuvent pas contenir des rfrences, ni des objets dont la classe a un constructeur non trivial, un constructeur de copie non trivial ou un destructeur non trivial. Pour toutes ces notions, voir la suite du chapitre. Les classes dnies au sein dune autre classe nont pas de droits particuliers sur leur classe hte. De mme, la classe hte na pas plus de droits spciques sur les membres de ses sousclasses. Notez que nombre de compilateurs ne respectent pas scrupuleusement ces rgles, et donnent parfois des droits aux classes htes. Pour autant, le paragraphe 11.8 de la norme C++ est trs clair ce sujet. Il vous faudra donc dclarer amie la classe hte dans les classes qui sont dnies en son sein si vous voulez accder leurs membres librement. La manire de procder sera dcrite dans la Section 7.7.2.

7.5. Hritage
Lhritage permet de donner une classe toutes les caractristiques dune ou de plusieurs autres classes. Les classes dont elle hrite sont appeles classes mres, classes de base ou classes antcdentes. La classe elle-mme est appele classe lle, classe drive ou classe descendante. Les proprits hrites sont les champs et les mthodes des classes de base. Pour faire un hritage en C++, il faut faire suivre le nom de la classe lle par la liste des classes mres dans la dclaration avec les restrictions daccs aux donnes, chaque lment tant spar des autres par une virgule. La syntaxe (donne pour class, identique pour struct) est la suivante :
class Classe_mere1 { /* Contenu de la classe mre 1. */ }; [class Classe_mere2 { /* Contenu de la classe mre 2. */ };] [...] class Classe_fille : public|protected|private Classe_mere1 [, public|protected|private Classe_mere2 [...]] { /* Dfinition de la classe fille. */ };

Dans cette syntaxe, Classe_fille hrite de la Classe_mere1, et des Classe_mere2, etc. si elles sont prsentes. La signication des mots cls private, protected et public dans lhritage est rcapitule dans le tableau suivant :

114

Chapitre 7. C++ : la couche objet Tableau 7-1. Droits daccs sur les membres hrits Accs aux donnes
public

mot cl utilis pour lhritage


protected public protected interdit private protected protected interdit private private interdit

mot cl utilis pour les champs et les mthodes

public protected private

Ainsi, les donnes publiques dune classe mre deviennent soit publiques, soit protges, soit prives selon que la classe lle hrite en public, protg ou en priv. Les donnes prives de la classe mre sont toujours inaccessibles, et les donnes protges deviennent soit protges, soit prives. Il est possible domettre les mots cls public, protected et private dans la syntaxe de lhritage. Le compilateur utilise un type dhritage par dfaut dans ce cas. Les classes de type struct utilisent lhritage public par dfaut et les classes de type class utilisent le mot cl private par dfaut. Exemple 7-5. Hritage public, priv et protg
class Emplacement { protected: int x, y;

// Donnes ne pouvant tre accdes // que par les classes filles.

public: void Change(int, int); // Mthode toujours accessible. }; void Emplacement::Change(int i, int j) { x = i; y = j; return; } class Point : public Emplacement { protected: unsigned int couleur; // Donne accessible // aux classes filles. public: void SetColor(unsigned int); }; void Point::SetColor(unsigned int NewColor) { couleur = NewColor; // Dfinit la couleur. return; }

Si une classe Cercle doit hriter de deux classes mres, par exemple Emplacement et Forme, sa dclaration aura la forme suivante :

115

Chapitre 7. C++ : la couche objet


class Cercle : public Emplacement, public Forme { /* Dfinition de la classe Cercle. Cette classe hrite des donnes publiques et protges des classes Emplacement et Forme. / * };

Les membres des classes de base auxquels les classes drives ont accs peuvent tre redclars avec de nouveaux droits daccs par celles-ci. Par exemple, une donne membre publique hrite publiquement peut tre protge unitairement. Inversement, une donne membre protge peut tre rendue publique dans la classe drive. Cela se fait via une simple redclaration laide du mot cl using, avec des droits daccs diffrents. Ce mot cl semploie comme suit :
using Base::membre;

o membre est le nom du membre de la classe de base que lon veut redclarer. Nous verrons plus en dtail les diverses utilisation de ce mot cl dans la Section 10.2. Il est possible de rednir les fonctions et les donnes des classes de base dans une classe drive. Par exemple, si une classe B drive de la classe A, et que toutes deux contiennent une donne d , les instances de la classe B utiliseront la donne d de la classe B et les instances de la classe A utiliseront la donne d de la classe A. Cependant, les objets de classe B contiendront galement un sous-objet, lui-mme instance de la classe de base A. Par consquent, ils contiendront la donne d de la classe A, mais cette dernire sera cache par la donne d de la classe la plus drive, savoir la classe B. Ce mcanisme est gnral : quand une classe drive rednit un membre dune classe de base, ce membre est cach et on ne peut plus accder directement quau membre redni (celui de la classe drive). Cependant, il est possible daccder aux donnes caches si lon connat leur classe, pour cela, il faut nommer le membre compltement laide de loprateur de rsolution de porte (::). Le nom complet dun membre est constitu du nom de sa classe suivi de loprateur de rsolution de porte, suivis du nom du membre :
classe::membre

Exemple 7-6. Oprateur de rsolution de porte et membre de classes de base


#include <stdlib.h> struct Base { int i; }; struct Derivee : public Base { int i; int LitBase(void); }; int Derivee::LitBase(void) {

116

Chapitre 7. C++ : la couche objet


return Base::i; // Renvoie la valeur i de la classe de base. } int main(void) { Derivee D; D.i=1; // Accde lentier i de la classe Derivee. D.Base::i=2; // Accde lentier i de la classe Base. return EXIT_SUCCESS; }

7.6. Classes virtuelles


Supposons prsent quune classe D hrite de deux classes mres, les classes B et C. Supposons galement que ces deux classes hritent dune classe mre commune appele classe A. On a larbre gnalogique suivant :

On sait que B et C hritent des donnes et des mthodes publiques et protges de A. De mme, D hrite des donnes de B et C, et par leur intermdiaire des donnes de A. Il se pose donc le problme suivant : quelles sont les donnes que lon doit utiliser quand on rfrence les champs de A ? Celles de B ou celles de C ? On peut accder aux deux sous-objets de classe A en spciant le chemin suivre dans larbre gnalogique laide de loprateur de rsolution de porte. Cependant, cela nest ni pratique ni efcace, et en gnral, on sattend ce quune seule copie de A apparaisse dans D. Le problme est rsolu en dclarant virtuelle la classe de base commune dans la spcication de lhritage pour les classes lles. Les donnes de la classe de base ne seront alors plus dupliques. Pour dclarer une classe mre comme une classe virtuelle, il faut faire prcder son nom du mot cl virtual dans lhritage des classes lles. Exemple 7-7. Classes virtuelles
class A {

117

Chapitre 7. C++ : la couche objet


protected: int Donnee; };

// La donne de la classe de base.

// Hritage de la classe A, virtuelle : class B : virtual public A { protected: int Valeur_B; // Autre donne que "Donnee" (hrite). }; // A est toujours virtuelle : class C : virtual public A { protected: int valeur_C; // Autre donne // ("Donnee" est acquise par hritage). }; class D : public B, public C // Ici, Donnee nest pas dupliqu. { /* Dfinition de la classe D. */ };

Note : Normalement, lhritage est ralis par le compilateur par aggrgation de la structure de donnes des classes de base dans la structure de donnes de la classe drive. Pour les classes virtuelles, ce nest en gnral pas le cas, puisque le compilateur doit assurer lunicit des donnes hrites de ces classes, mme en cas dhritage multiple. Par consquent, certaines restrictions dusage sappliquent sur les classes virtuelles. Premirement, il est impossible de transtyper directement un pointeur sur un objet dune classe de base virtuelle en un pointeur sur un objet dune de ses classes drives. Il faut imprativement utiliser loprateur de transtypage dynamique dynamic_cast. Cet oprateur sera dcrit dans le Chapitre 9. Deuximement, chaque classe drive directement ou indirectement dune classe virtuelle doit en appeler le constructeur explicitement dans son constructeur si celui-ci prend des paramtres. En effet, elle ne peut pas se er au fait quune autre de ses classes de base, elle-mme drive de la classe de base virtuelle, appelle un constructeur spcique, car il est possible que plusieurs classes de base cherchent initialiser diffremment chacune un objet commun hrit de la classe virtuelle. Pour reprendre lexemple donn ci-dessus, si les classes B et C appellaient toutes les deux un constructeur non trivial de la classe virtuelle A, et que la classe D appellait elle-mme les constructeurs de B et C, le sous-objet hrit de A serait construit plusieurs fois. Pour viter cela, le compilateur ignore purement et simplement les appels au constructeur des classes de bases virtuelles dans les classes de base drives. Il faut donc systmatiquement le spcier, chaque niveau de la hirarchie de classe. La notion de constructeur sera vue dans la Section 7.8.

7.7. Fonctions et classes amies


Il est parfois ncessaire davoir des fonctions qui ont un accs illimit aux champs dune classe. En gnral, lemploi de telles fonctions traduit un manque danalyse dans la hirarchie des classes, mais pas toujours. Elles restent donc ncessaires malgr tout.

118

Chapitre 7. C++ : la couche objet De telles fonctions sont appeles des fonctions amies. Pour quune fonction soit amie dune classe, il faut quelle soit dclare dans la classe avec le mot cl friend. Il est galement possible de faire une classe amie dune autre classe, mais dans ce cas, cette classe devrait peut-tre tre une classe lle. Lutilisation des classes amies peut traduire un dfaut de conception.

7.7.1. Fonctions amies


Les fonctions amies se dclarent en faisant prcder la dclaration classique de la fonction du mot cl friend lintrieur de la dclaration de la classe cible. Les fonctions amies ne sont pas des mthodes de la classe cependant (cela naurait pas de sens puisque les mthodes ont dj accs aux membres de la classe). Exemple 7-8. Fonctions amies
class A { int a; friend void ecrit_a(int i); }; A essai; void ecrit_a(int i) { essai.a=i; return; }

// Une donne prive. // Une fonction amie.

// Initialise a.

Il est possible de dclarer amie une fonction dune autre classe, en prcisant son nom complet laide de loprateur de rsolution de porte.

7.7.2. Classes amies


Pour rendre toutes les mthodes dune classe amies dune autre classe, il suft de dclarer la classe complte comme tant amie. Pour cela, il faut encore une fois utiliser le mot cl friend avant la dclaration de la classe, lintrieur de la classe cible. Cette fois encore, la classe amie dclare ne sera pas une sous-classe de la classe cible, mais bien une classe de porte globale.
Note : Le fait, pour une classe, dappartenir une autre classe lui donne le droit daccder aux membres de sa classe hte. Il nest donc pas ncessaire de dclarer amies dune classe les classes dnies au sein de celle-ci. Remarquez que cette rgle a t rcemment modie dans la norme C++, et que la plupart des compilateurs refuseront aux classes incluses daccder aux membres non publics de leur conteneur.

Exemple 7-9. Classe amie


#include <stdlib.h> #include <stdio.h>

119

Chapitre 7. C++ : la couche objet


class Hote { friend class Amie; int i; public: Hote(void) { i=0; return ; } }; Hote h; class Amie { public: void print_hote(void) { printf("%d\n", h.i); // Accde la donne prive de h. return ; } }; int main(void) { Amie a; a.print_hote(); return EXIT_SUCCESS; }

// Toutes les mthodes de Amie sont amies. // Donne prive de la classe Hote.

On remarquera plusieurs choses importantes. Premirement, lamiti nest pas transitive. Cela signie que les amis des amis ne sont pas des amis. Une classe A amie dune classe B, elle-mme amie dune classe C, nest pas amie de la classe C par dfaut. Il faut la dclarer amie explicitement si on dsire quelle le soit. Deuximement, les amis ne sont pas hrits. Ainsi, si une classe A est amie dune classe B et que la classe C est une classe lle de la classe B, alors A nest pas amie de la classe C par dfaut. Encore une fois, il faut la dclarer amie explicitement. Ces remarques sappliquent galement aux fonctions amies (une fonction amie dune classe A amie dune classe B nest pas amie de la classe B, ni des classes drives de A).

7.8. Constructeurs et destructeurs


Le constructeur et le destructeur sont deux mthodes particulires qui sont appeles respectivement la cration et la destruction dun objet. Toute classe a un constructeur et un destructeur par dfaut, fournis par le compilateur. Ces constructeurs et destructeurs appellent les constructeurs par dfaut et les destructeurs des classes de base et des donnes membres de la classe, mais en dehors de cela, ils ne font absolument rien. Il est donc souvent ncessaire de les rednir an de grer certaines actions qui doivent avoir lieu lors de la cration dun objet et de leur destruction. Par exemple, si lobjet doit contenir des variables alloues dynamiquement, il faut leur rserver de la mmoire la cration de lobjet ou au moins mettre les pointeurs correspondants NULL. la destruction de lobjet, il convient

120

Chapitre 7. C++ : la couche objet de restituer la mmoire alloue, sil en a t allou. On peut trouver bien dautres situations o une phase dinitialisation et une phase de terminaison sont ncessaires. Ds quun constructeur ou un destructeur a t dni par lutilisateur, le compilateur ne dnit plus automatiquement le constructeur ou le destructeur par dfaut correspondant. En particulier, si lutilisateur dnit un constructeur prenant des paramtres, il ne sera plus possible de construire un objet simplement, sans fournir les paramtres ce constructeur, moins bien entendu de dnir galement un constructeur qui ne prenne pas de paramtres.

7.8.1. Dnition des constructeurs et des destructeurs


Le constructeur se dnit comme une mthode normale. Cependant, pour que le compilateur puisse la reconnatre en tant que constructeur, les deux conditions suivantes doivent tre vries :

elle doit porter le mme nom que la classe ; elle ne doit avoir aucun type, pas mme le type void.

Le destructeur doit galement respecter ces rgles. Pour le diffrencier du constructeur, son nom sera toujours prcd du signe tilde (~). Un constructeur est appel automatiquement lors de linstanciation de lobjet. Le destructeur est appel automatiquement lors de sa destruction. Cette destruction a lieu lors de la sortie du bloc de porte courante pour les objets de classe de stockage auto. Pour les objets allous dynamiquement, le constructeur et le destructeur sont appels automatiquement par les expressions qui utilisent les oprateurs new, new[], delete et delete[]. Cest pour cela quil est recommand de les utiliser la place des fonctions malloc et free du C pour crer dynamiquement des objets. De plus, il ne faut pas utiliser delete ou delete[] sur des pointeurs de type void, car il nexiste pas dobjets de type void. Le compilateur ne peut donc pas dterminer quel est le destructeur appeler avec ce type de pointeur. Le constructeur est appel aprs lallocation de la mmoire de lobjet et le destructeur est appel avant la libration de cette mmoire. La gestion de lallocation dynamique de mmoire avec les classes est ainsi simplie. Dans le cas des tableaux, lordre de construction est celui des adresses croissantes, et lordre de destruction est celui des adresses dcroissantes. Cest dans cet ordre que les constructeurs et destructeurs de chaque lment du tableau sont appels. Les constructeurs pourront avoir des paramtres. Ils peuvent donc tre surchargs, mais pas les destructeurs. Cela est d a fait quen gnral on connat le contexte dans lequel un objet est cr, mais quon ne peut pas connatre le contexte dans lequel il est dtruit : il ne peut donc y avoir quun seul destructeur. Les constructeurs qui ne prennent pas de paramtre ou dont tous les paramtres ont une valeur par dfaut, remplacent automatiquement les constructeurs par dfaut dnis par le compilateur lorsquil ny a aucun constructeur dans les classes. Cela signie que ce sont ces constructeurs qui seront appels automatiquement par les constructeurs par dfaut des classes drives. Exemple 7-10. Constructeurs et destructeurs
class chaine { char * s; // Implmente une chane de caractres. // Le pointeur sur la chane de caractres.

public: chaine(void);

// Le constructeur par dfaut.

121

Chapitre 7. C++ : la couche objet


chaine(unsigned int); ~chaine(void); }; chaine::chaine(void) { s=NULL; return ; } // Le constructeur. Il na pas de type. // Le destructeur.

// La chane est initialise avec // le pointeur nul.

chaine::chaine(unsigned int Taille) { s = new char[Taille+1]; // Alloue de la mmoire pour la chane. s[0]=\0; // Initialise la chane "". return; } chaine::~chaine(void) { if (s!=NULL) delete[] s; // Restitue la mmoire utilise si // ncessaire. return; }

Pour passer les paramtres au constructeur, on donne la liste des paramtres entre parenthses juste aprs le nom de lobjet lors de son instanciation :
chaine s1; chaine s2(200); // // // // Instancie une chane de caractres non initialise. Instancie une chane de caractres de 200 caractres.

Les constructeurs devront parfois effectuer des tches plus compliques que celles donnes dans cet exemple. En gnral, ils peuvent faire toutes les oprations faisables dans une mthode normale, sauf utiliser les donnes non initialises bien entendu. En particulier, les donnes des sous-objets dun objet ne sont pas initialises tant que les constructeurs des classes de base ne sont pas appels. Cest pour cela quil faut toujours appeler les constructeurs des classes de base avant dexcuter le constructeur de la classe en cours dinstanciation. Si les constructeurs des classes de base ne sont pas appels explicitement, le compilateur appellera, par dfaut, les constructeurs des classes mres qui ne prennent pas de paramtre ou dont tous les paramtres ont une valeur par dfaut (et, si aucun constructeur nest dni dans les classe mres, il appellera les constructeurs par dfaut de ces classes). Comment appeler les constructeurs et les destructeurs des classes mres lors de linstanciation et de la destruction dune classe drive ? Le compilateur ne peut en effet pas savoir quel constructeur il faut appeler parmi les diffrents constructeurs surchargs potentiellement prsents... Pour appeler un autre constructeur dune classe de base que le constructeur ne prenant pas de paramtre, il faut spcier explicitement ce constructeur avec ses paramtres aprs le nom du constructeur de la classe lle, en les sparant de deux points (:). En revanche, il est inutile de prciser le destructeur appeler, puisque celui-ci est unique. Le programmeur ne doit donc pas appeler lui-mme les destructeurs des classes mres, le langage sen charge.

122

Chapitre 7. C++ : la couche objet Exemple 7-11. Appel du constructeur des classes de base
/* Dclaration de la classe mre. */ class Mere { int m_i; public: Mere(int); ~Mere(void); }; /* Dfinition du constructeur de la classe mre. */ Mere::Mere(int i) { m_i=i; printf("Excution du constructeur de la classe mre.\n"); return; } /* Dfinition du destructeur de la classe mre. */ Mere::~Mere(void) { printf("Excution du destructeur de la classe mre.\n"); return; } /* Dclaration de la classe fille. */ class Fille : public Mere { public: Fille(void); ~Fille(void); }; /* Dfinition du constructeur de la classe fille avec appel du constructeur de la classe mre. */ Fille::Fille(void) : Mere(2) { printf("Excution du constructeur de la classe fille.\n"); return; } /* Dfinition du destructeur de la classe fille avec appel automatique du destructeur de la classe mre. */ Fille::~Fille(void) { printf("Excution du destructeur de la classe fille.\n"); return; }

123

Chapitre 7. C++ : la couche objet Lors de linstanciation dun objet de la classe lle, le programme afchera dans lordre les messages suivants :
Excution du constructeur de la classe mre. Excution du constructeur de la classe fille.

et lors de la destruction de lobjet :


Excution du destructeur de la classe fille. Excution du destructeur de la classe mre.

Si lon navait pas prcis que le constructeur appeler pour la classe Mere tait le constructeur prenant un entier en paramtre, le compilateur aurait essay dappeler le constructeur par dfaut de cette classe. Or, ce constructeur ntant plus gnr automatiquement par le compilateur ( cause de la dnition dun constructeur prenant un paramtre), il y aurait eu une erreur de compilation. Il est possible dappeler plusieurs constructeurs si la classe drive de plusieurs classes de base. Pour cela, il suft de lister les constructeurs un un, en sparant leurs appels par des virgules. On notera cependant que lordre dans lequel les constructeurs sont appels nest pas forcment lordre dans lequel ils sont lists dans la dnition du constructeur de la classe lle. En effet, le C++ appelle toujours les constructeurs dans lordre dapparition de leurs classes dans la liste des classes de base de la classe drive.
Note : An dviter lutilisation des donnes non initialises de lobjet le plus driv dans une hirarchie pendant la construction de ses sous-objets par lintermdiaire des fonctions virtuelles, le mcanisme des fonctions virtuelles est dsactiv dans les constructeurs (voyez la Section 7.13 pour plus de dtails sur les fonctions virtuelles). Ce problme survient parce que pendant lexcution des constructeurs des classes de base, lobjet de la classe en cours dinstanciation na pas encore t initialis, et malgr cela, une fonction virtuelle aurait pu utiliser une donne de cet objet. Une fonction virtuelle peut donc toujours tre appele dans un constructeur, mais la fonction effectivement appele est celle de la classe du sous-objet en cours de construction : pas celle de la classe de lobjet complet. Ainsi, si une classe A hrite dune classe B et quelles ont toutes les deux une fonction virtuelle f, lappel de f dans le constructeur de B utilisera la fonction f de B, pas celle de A (mme si lobjet que lon instancie est de classe A).

La syntaxe utilise pour appeler les constructeurs des classes de base peut galement tre utilise pour initialiser les donnes membres de la classe. En particulier, cette syntaxe est obligatoire pour les donnes membres constantes et pour les rfrences, car le C++ ne permet pas laffectation dune valeur des variables de ce type. Encore une fois, lordre dappel des constructeurs des donnes membres ainsi initialises nest pas forcment lordre dans lequel ils sont lists dans le constructeur de la classe. En effet, le C++ utilise cette fois lordre de dclaration de chaque donne membre. Exemple 7-12. Initialisation de donnes membres constantes
class tableau { const int m_iTailleMax; const int *m_pDonnees; public: tableau(int iTailleMax); ~tableau(); };

124

Chapitre 7. C++ : la couche objet

tableau::tableau(int iTailleMax) : m_iTailleMax(iTailleMax) // Initialise la donne membre constante. { // Allocation dun tableau de m_iTailleMax entres : m_pDonnees = new int[m_iTailleMax]; } tableau::~tableau() { // Destruction des donnes : delete[] m_pDonnees; }

Note : Les constructeurs des classes de base virtuelles prenant des paramtres doivent tre appels par chaque classe qui en drive, que cette drivation soit directe ou indirecte. En effet, les classes de base virtuelles subissent un traitement particulier qui assure lunicit de leurs donnes dans toutes leurs classes drives. Les classes drives ne peuvent donc pas se reposer sur leurs classes de base pour appeler le constructeur des classes virtuelles, car il peut y avoir plusieurs classes de bases qui drivent dune mme classe virtuelle, et cela supposerait que le constructeur de cette dernire classe serait appel plusieurs fois, ventuellement avec des valeurs de paramtres diffrentes. Chaque classe doit donc prendre en charge la construction des sous-objets des classes de base virtuelles dont il hrite dans ce cas.

7.8.2. Constructeurs de copie


Il faudra parfois crer un constructeur de copie. Le but de ce type de constructeur est dinitialiser un objet lors de son instanciation partir dun autre objet. Toute classe dispose dun constructeur de copie par dfaut gnr automatiquement par le compilateur, dont le seul but est de recopier les champs de lobjet recopier un un dans les champs de lobjet instancier. Toutefois, ce constructeur par dfaut ne sufra pas toujours, et le programmeur devra parfois en fournir un explicitement. Ce sera notamment le cas lorsque certaines donnes des objets auront t alloues dynamiquement. Une copie brutale des champs dun objet dans un autre ne ferait que recopier les pointeurs, pas les donnes pointes. Ainsi, la modication de ces donnes pour un objet entranerait la modication des donnes de lautre objet, ce qui ne serait sans doute pas leffet dsir. La dnition des constructeurs de copie se fait comme celle des constructeurs normaux. Le nom doit tre celui de la classe, et il ne doit y avoir aucun type. Dans la liste des paramtres cependant, il devra toujours y avoir une rfrence sur lobjet copier. Pour la classe chaine dnie ci-dessus, il faut un constructeur de copie. Celui-ci peut tre dclar de la faon suivante :
chaine(const chaine &Source);

o Source est lobjet copier. Si lon rajoute la donne membre Taille dans la dclaration de la classe, la dnition de ce constructeur peut tre :
chaine::chaine(const chaine &Source) {

125

Chapitre 7. C++ : la couche objet


int i = 0; Taille = Source.Taille; s = new char[Taille + 1]; strcpy(s, Source.s); return; } // Compteur de caractres. // Effectue lallocation. // Recopie la chane de caractres source.

Le constructeur de copie est appel dans toute instanciation avec initialisation, comme celles qui suivent :
chaine s2(s1); chaine s2 = s1;

Dans les deux exemples, cest le constructeur de copie qui est appel. En particulier, la deuxime ligne, le constructeur normal nest pas appel et aucune affectation entre objets na lieu.
Note : Le fait de dnir un constructeur de copie pour une classe signie gnralement que le constructeur de copie, le destructeur et loprateur daffectation fournis par dfaut par le compilateur ne conviennent pas pour cette classe. Par consquent, ces mthodes devront systmatiquement tre rednies toutes les trois ds que lune dentre elle le sera. Cette rgle, que lon appelle la rgle des trois, vous permettra dviter des bogues facilement. Vous trouverez de plus amples dtails sur la manire de rednir loprateur daffectation dans la Section 7.11.3. Les constructeurs de copie et les destructeurs ne devront pas avoir dautres effets que ceux dicts par leur smantique. En effet, les compilateurs peuvent parfaitement effectuer des optimisations et ne pas appeler ces mthodes dans certaines situations (gnralement, pour viter des copies dobjets temporaires inutiles). Par consquent, les constructeurs et les destructeurs ne devront gnralement pas avoir deffets de bord.

7.8.3. Utilisation des constructeurs dans les transtypages


Les constructeurs sont utiliss dans les conversions de type dans lesquelles le type cible est celui de la classe du constructeur. Ces conversions peuvent tre soit implicites (dans une expression), soit explicite ( laide dun transtypage). Par dfaut, les conversions implicites sont lgales, pourvu quil existe un constructeur dont le premier paramtre a le mme type que lobjet source. Par exemple, la classe Entier suivante :
class Entier { int i; public: Entier(int j) { i=j; return ; } };

dispose dun constructeur de transtypage pour les entiers. Les expressions suivantes :

126

Chapitre 7. C++ : la couche objet


int j=2; Entier e1, e2=j; e1=j;

sont donc lgales, la valeur entire situe la droite de lexpression tant convertie implicitement en un objet du type de la classe Entier. Si, pour une raison quelconque, ce comportement nest pas souhaitable, on peut forcer le compilateur naccepter que les conversions explicites ( laide de transtypage). Pour cela, il suft de placer le mot cl explicit avant la dclaration du constructeur. Par exemple, le constructeur de la classe chaine vue ci-dessus prenant un entier en paramtre risque dtre utilis dans des conversions implicites. Or ce constructeur ne permet pas de construire une chane de caractres partir dun entier, et ne doit donc pas tre utilis dans les oprations de transtypage. Ce constructeur doit donc tre dclar explicit :
class chaine { size_t Taille; char * s; public: chaine(void); // Ce constructeur permet de prciser la taille de la chane // sa cration : explicit chaine(unsigned int); ~chaine(void); };

Avec cette dclaration, lexpression suivante :


int j=2; chaine s = j;

nest plus valide, alors quelle ltait lorsque le constructeur ntait pas dclar explicit.
Note : On prendra garde au fait que le mot cl explicit nempche lutilisation du constructeur dans les oprations de transtypage que dans les conversions implicites. Si le transtypage est explicitement demand, le constructeur sera malgr tout utilis. Ainsi, le code suivant sera accept :

int j=2; chaine s = (chaine) j;

Bien entendu, cela na pas beaucoup de signication et ne devrait jamais tre effectu.

7.9. Pointeur this


Nous allons prsent voir comment les fonctions membres, qui appartiennent la classe, peuvent accder aux donnes dun objet, qui est une instance de cette classe. Cela est indispensable pour bien comprendre les paragraphes suivants.

127

Chapitre 7. C++ : la couche objet chaque appel dune fonction membre, le compilateur passe implicitement un pointeur sur les donnes de lobjet en paramtre. Ce paramtre est le premier paramtre de la fonction. Ce mcanisme est compltement invisible au programmeur, et nous ne nous attarderons pas dessus. En revanche, il faut savoir que le pointeur sur lobjet est accessible lintrieur de la fonction membre. Il porte le nom this . Par consquent, *this reprsente lobjet lui-mme. Nous verrons une utilisation de this dans le paragraphe suivant (surcharge des oprateurs).
this est un pointeur constant, cest--dire quon ne peut pas le modier (il est donc impossible de

faire des oprations arithmtiques dessus). Cela est tout fait normal, puisque le faire reviendrait sortir de lobjet en cours (celui pour lequel la mthode en cours dexcution travaille). Il est possible de transformer ce pointeur constant en un pointeur constant sur des donnes constantes pour chaque fonction membre. Le pointeur ne peut toujours pas tre modi, et les donnes de lobjet ne peuvent pas tre modies non plus. Lobjet est donc considr par la fonction membre concerne comme un objet constant. Cela revient dire que la fonction membre sinterdit la modication des donnes de lobjet. On parvient ce rsultat en ajoutant le mot cl const la suite de len-tte de la fonction membre. Par exemple :
class Entier { int i; public: int lit(void) const; }; int Entier::lit(void) const { return i; }

Dans la fonction membre lit, il est impossible de modier lobjet. On ne peut donc accder quen lecture seule i. Nous verrons une application de cette possibilit dans la Section 7.15. Il est noter quune mthode qui nest pas dclare comme tant const modie a priori les donnes de lobjet sur lequel elle travaille. Donc, si elle est appele sur un objet dclar const, une erreur de compilation se produit. Ce comportement est normal. On devra donc toujours dclarer const une mthode qui ne modie pas rellement lobjet, an de laisser lutilisateur le choix de dclarer const ou non les objets de sa classe.
Note : Le mot cl const nintervient pas dans la signature des fonctions en gnral lorsquil sapplique aux paramtres (tout paramtre dclar const perd sa qualication dans la signature). En revanche, il intervient dans la signature dune fonction membre quand il sapplique cette fonction (ou, plus prcisment, lobjet point par this). Il est donc possible de dclarer deux fonctions membres acceptant les mmes paramtres, dont une seule est const. Lors de lappel, la dtermination de la fonction utiliser dpendra de la nature de lobjet sur lequel elle doit sappliquer. Si lobjet est const, la mthode appele sera celle qui est const.

128

Chapitre 7. C++ : la couche objet

7.10. Donnes et fonctions membres statiques


Nous allons voir dans ce paragraphe lemploi du mot cl static dans les classes. Ce mot cl intervient pour caractriser les donnes membres statiques des classes, les fonctions membres statiques des classes, et les donnes statiques des fonctions membres.

7.10.1. Donnes membres statiques


Une classe peut contenir des donnes membres statiques. Ces donnes sont soit des donnes membres propres la classe, soit des donnes locales statiques des fonctions membres de la classe. Dans tous les cas, elles appartiennent la classe, et non pas aux objets de cette classe. Elles sont donc communes tous ces objets. Il est impossible dinitialiser les donnes dune classe dans le constructeur de la classe, car le constructeur ninitialise que les donnes des nouveaux objets. Les donnes statiques ne sont pas spciques un objet particulier et ne peuvent donc pas tre initialises dans le constructeur. En fait, leur initialisation doit se faire lors de leur dnition, en dehors de la dclaration de la classe. Pour prciser la classe laquelle les donnes ainsi dnies appartiennent, on devra utiliser loprateur de rsolution de porte (::). Exemple 7-13. Donne membre statique
class test { static int i; ... }; int test::i=3;

// Dclaration dans la classe.

// Initialisation en dehors de la classe.

La variable test::i sera partage par tous les objets de classe test, et sa valeur initiale est 3.
Note : La dnition des donnes membres statiques suit les mmes rgles que la dnition des variables globales. Autrement dit, elles se comportent comme des variables dclares externes. Elles sont donc accessibles dans tous les chiers du programme (pourvu, bien entendu, quelles soient dclares en zone publique dans la classe). De mme, elles ne doivent tre dnies quune seule fois dans tout le programme. Il ne faut donc pas les dnir dans un chier den-tte qui peut tre inclus plusieurs fois dans des chiers sources, mme si lon protge ce chier den-tte contre les inclusions multiples.

Les variables statiques des fonctions membres doivent tre initialises lintrieur des fonctions membres. Elles appartiennent galement la classe, et non pas aux objets. De plus, leur porte est rduite celle du bloc dans lequel elles ont t dclares. Ainsi, le code suivant :
#include <stdlib.h> #include <stdio.h> class test { public: int n(void); };

129

Chapitre 7. C++ : la couche objet

int test::n(void) { static int compte=0; return compte++; } int main(void) { test objet1, objet2; printf("%d ", objet1.n()); printf("%d\n", objet2.n()); return EXIT_SUCCESS; }

// Affiche 0 // Affiche 1

afchera 0 et 1, parce que la variable statique compte est la mme pour les deux objets.

7.10.2. Fonctions membres statiques


Les classes peuvent galement contenir des fonctions membres statiques. Cela peut surprendre premire vue, puisque les fonctions membres appartiennent dj la classe, cest--dire tous les objets. En fait, cela signie que ces fonctions membres ne recevront pas le pointeur sur lobjet this, comme cest le cas pour les autres fonctions membres. Par consquent, elles ne pourront accder quaux donnes statiques de lobjet. Exemple 7-14. Fonction membre statique
class Entier { int i; static int j; public: static int get_value(void); }; int Entier::j=0; int Entier::get_value(void) { j=1; // Lgal. return i; // ERREUR ! get_value ne peut pas accder i. }

La fonction get_value de lexemple ci-dessus ne peut pas accder la donne membre non statique i, parce quelle ne travaille sur aucun objet. Son champ daction est uniquement la classe Entier. En revanche, elle peut modier la variable statique j, puisque celle-ci appartient la classe Entier et non aux objets de cette classe. Lappel des fonctions membre statiques se fait exactement comme celui des fonctions membres non statiques, en spciant lidenticateur dun des objets de la classe et le nom de la fonction membre, spars par un point. Cependant, comme les fonctions membres ne travaillent pas sur les objets des classes mais plutt sur les classes elles-mmes, la prsence de lobjet lors de lappel est facultatif. On peut donc se contenter dappeler une fonction statique en qualiant son nom du nom de la classe laquelle elle appartient laide de loprateur de rsolution de porte.

130

Chapitre 7. C++ : la couche objet Exemple 7-15. Appel de fonction membre statique
#include <stdlib.h> class Entier { static int i; public: static int get_value(void); }; int Entier::i=3; int Entier::get_value(void) { return i; } int main(void) { // Appelle la fonction statique get_value : int resultat=Entier::get_value(); return EXIT_SUCCESS; }

Les fonctions membres statiques sont souvent utilises an de regrouper un certain nombre de fonctionnalits en rapport avec leur classe. Ainsi, elles sont facilement localisable et les risques de conits de noms entre deux fonctions membres homonymes sont rduits. Nous verrons galement dans le Chapitre 10 comment viter les conits de noms globaux dans le cadre des espaces de nommage.

7.11. Surcharge des oprateurs


On a vu prcdemment que les oprateurs ne se diffrencient des fonctions que syntaxiquement, pas logiquement. Dailleurs, le compilateur traite un appel un oprateur comme un appel une fonction. Le C++ permet donc de surcharger les oprateurs pour les classes dnies par lutilisateur, en utilisant une syntaxe particulire calque sur la syntaxe utilise pour dnir des fonctions membres normales. En fait, il est mme possible de surcharger les oprateurs du langage pour les classes de lutilisateur en dehors de la dnition de ces classes. Le C++ dispose donc de deux mthodes diffrentes pour surcharger les oprateurs. Les seuls oprateurs qui ne peuvent pas tre surchargs sont les suivants :
:: . .* ?: sizeof typeid static_cast dynamic_cast const_cast reinterpret_cast

Tous les autres oprateurs sont surchargeables. Leur surcharge ne pose gnralement pas de problme et peut tre ralise soit dans la classe des objets sur lesquels ils sappliquent, soit lextrieur de

131

Chapitre 7. C++ : la couche objet cette classe. Cependant, un certain nombre dentre eux demandent des explications complmentaires, que lon donnera la n de cette section.
Note : On prendra garde aux problmes de performances lors de la surcharge des oprateurs. Si la facilit dcriture des expressions utilisant des classes est grandement simplie grce la possibilit de surcharger les oprateurs pour ces classes, les performances du programme peuvent en tre gravement affectes. En effet, lutilisation inconsidre des oprateurs peut conduire un grand nombre de copies des objets, copies que lon pourrait viter en crivant le programme classiquement. Par exemple, la plupart des oprateurs renvoient un objet du type de la classe sur laquelle ils travaillent. Ces objets sont souvent crs localement dans la fonction de loprateur (cest--dire quils sont de porte auto). Par consquent, ces objets sont temporaires et sont dtruits la sortie de la fonction de loprateur. Cela impose donc au compilateur den faire une copie dans la valeur de retour de la fonction avant den sortir. Cette copie sera elle-mme dtruite par le compilateur une fois quelle aura t utilise par linstruction qui a appel la fonction. Si le rsultat doit tre affect un objet de lappelant, une deuxime copie inutile est ralise par rapport au cas o loprateur aurait travaill directement dans la variable rsultat. Si les bons compilateurs sont capables dviter ces copies, cela reste lexception et il vaut mieux tre averti lavance plutt que de devoir rcrire tout son programme a posteriori pour des problmes de performances.

Nous allons prsent voir dans les sections suivantes les deux syntaxes permettant de surcharger les oprateurs pour les types de lutilisateur, ainsi que les rgles spciques certains oprateurs particuliers.

7.11.1. Surcharge des oprateurs internes


Une premire mthode pour surcharger les oprateurs consiste les considrer comme des mthodes normales de la classe sur laquelle ils sappliquent. Le nom de ces mthodes est donn par le mot cl operator, suivi de loprateur surcharger. Le type de la fonction de loprateur est le type du rsultat donn par lopration, et les paramtres, donns entre parenthses, sont les oprandes. Les oprateurs de ce type sont appels oprateurs internes, parce quils sont dclars lintrieur de la classe. Voici la syntaxe :
type operatorOp(paramtres)

lcriture
A Op B

se traduisant par :
A.operatorOp(B)

Avec cette syntaxe, le premier oprande est toujours lobjet auquel cette fonction sapplique. Cette manire de surcharger les oprateurs est donc particulirement bien adapte pour les oprateurs qui modient lobjet sur lequel ils travaillent, comme par exemple les oprateurs =, +=, ++, etc. Les paramtres de la fonction oprateur sont alors le deuxime oprande et les suivants. Les oprateurs dnis en interne devront souvent renvoyer lobjet sur lequel ils travaillent (ce nest pas une ncessit cependant). Cela est faisable grce au pointeur this.

132

Chapitre 7. C++ : la couche objet Par exemple, la classe suivante implmente les nombres complexes avec quelques-unes de leurs oprations de base. Exemple 7-16. Surcharge des oprateurs internes
class complexe { double m_x, m_y; // Les parties relles et imaginaires. public: // Constructeurs et oprateur de copie : complexe(double x=0, double y=0); complexe(const complexe &); complexe &operator=(const complexe &); // Fonctions permettant de lire les parties relles // et imaginaires : double re(void) const; double im(void) const; // Les oprateurs de base: complexe &operator+=(const complexe &operator-=(const complexe &operator*=(const complexe &operator/=(const }; complexe::complexe(double x, double y) { m_x = x; m_y = y; return ; } complexe::complexe(const complexe &source) { m_x = source.m_x; m_y = source.m_y; return ; } complexe &complexe::operator=(const complexe &source) { m_x = source.m_x; m_y = source.m_y; return *this; } double complexe::re() const { return m_x; } double complexe::im() const { return m_y; }

complexe complexe complexe complexe

&); &); &); &);

133

Chapitre 7. C++ : la couche objet


complexe &complexe::operator+=(const complexe &c) { m_x += c.m_x; m_y += c.m_y; return *this; } complexe &complexe::operator-=(const complexe &c) { m_x -= c.m_x; m_y -= c.m_y; return *this; } complexe &complexe::operator*=(const complexe &c) { double temp = m_x*c.m_x -m_y*c.m_y; m_y = m_x*c.m_y + m_y*c.m_x; m_x = temp; return *this; } complexe &complexe::operator/=(const complexe &c) { double norm = c.m_x*c.m_x + c.m_y*c.m_y; double temp = (m_x*c.m_x + m_y*c.m_y) / norm; m_y = (-m_x*c.m_y + m_y*c.m_x) / norm; m_x = temp; return *this; }

Note : La bibliothque standard C++ fournit une classe traitant les nombres complexes de manire complte, la classe complex. Cette classe nest donc donne ici qu titre dexemple et ne devra videmment pas tre utilise. La dnition des nombres complexes et de leur principales proprits sera donne dans la Section 14.3.1, o la classe complex sera dcrite.

Les oprateurs daffectation fournissent un exemple dutilisation du pointeur this. Ces oprateurs renvoient en effet systmatiquement lobjet sur lequel ils travaillent, an de permettre des affectations multiples. Les oprateurs de ce type devront donc tous se terminer par :
return *this;

7.11.2. Surcharge des oprateurs externes


Une deuxime possibilit nous est offerte par le langage pour surcharger les oprateurs. La dnition de loprateur ne se fait plus dans la classe qui lutilise, mais en dehors de celle-ci, par surcharge dun oprateur de lespace de nommage global. Il sagit donc doprateurs externes cette fois. La surcharge des oprateurs externes se fait donc exactement comme on surcharge les fonctions normales. Dans ce cas, tous les oprandes de loprateur devront tre passs en paramtres : il ny aura pas de paramtre implicite (le pointeur this nest pas pass en paramtre).

134

Chapitre 7. C++ : la couche objet La syntaxe est la suivante :


type operatorOp(oprandes)

o oprandes est la liste complte des oprandes. Lavantage de cette syntaxe est que loprateur est rellement symtrique, contrairement ce qui se passe pour les oprateurs dnis lintrieur de la classe. Ainsi, si lutilisation de cet oprateur ncessite un transtypage sur lun des oprandes, il nest pas ncessaire que cet oprande soit obligatoirement le deuxime. Donc si la classe dispose de constructeurs permettant de convertir un type de donne en son prope type, ce type de donne peut tre utilis avec tous les oprateurs de la classe. Par exemple, les oprateurs daddition, de soustraction, de multiplication et de division de la classe complexe peuvent tre implments comme dans lexemple suivant. Exemple 7-17. Surcharge doprateurs externes
class complexe { friend complexe friend complexe friend complexe friend complexe

operator+(const operator-(const operator*(const operator/(const

complexe complexe complexe complexe

&, &, &, &,

const const const const

complexe complexe complexe complexe

&); &); &); &);

double m_x, m_y; // Les parties relles et imaginaires. public: // Constructeurs et oprateur de copie : complexe(double x=0, double y=0); complexe(const complexe &); complexe &operator=(const complexe &); // Fonctions permettant de lire les parties relles // et imaginaires : double re(void) const; double im(void) const; // Les oprateurs de base: complexe &operator+=(const complexe &operator-=(const complexe &operator*=(const complexe &operator/=(const }; // Les oprateurs de base ont t luds ici : ... complexe operator+(const complexe &c1, const complexe &c2) { complexe result = c1; return result += c2; } complexe operator-(const complexe &c1, const complexe &c2) { complexe result = c1; return result -= c2; }

complexe complexe complexe complexe

&); &); &); &);

135

Chapitre 7. C++ : la couche objet


complexe operator*(const complexe &c1, const complexe &c2) { complexe result = c1; return result *= c2; } complexe operator/(const complexe &c1, const complexe &c2) { complexe result = c1; return result /= c2; }

Avec ces dnitions, il est parfaitement possible deffectuer la multiplication dun objet de type complexe avec une valeur de type double. En effet, cette valeur sera automatiquement convertie en complexe grce au constructeur de la classe complexe, qui sera utilis ici comme constructeur de transtypage. Une fois cette conversion effectue, loprateur adquat est appliqu. On constatera que les oprateurs externes doivent tre dclars comme tant des fonctions amies de la classe sur laquelle ils travaillent, faute de quoi ils ne pourraient pas manipuler les donnes membres de leurs oprandes.
Note : Certains compilateurs peuvent supprimer la cration des variables temporaires lorsque celles-ci sont utilises en tant que valeur de retour des fonctions. Cela permet damliorer grandement lefcacit des programmes, en supprimant toutes les copies dobjets inutiles. Cependant ces compilateurs sont relativement rares et peuvent exiger une syntaxe particulire pour effectuer cette optimisation. Gnralement, les compilateurs C++ actuels suppriment la cration de variable temporaire dans les retours de fonctions si la valeur de retour est construite dans linstruction return elle-mme. Par exemple, loprateur daddition peut tre optimis ainsi :
complexe operator+(const complexe &c1, const complexe &c2) { return complexe(c1.m_x + c2.m_x, c1.m_y + c2.m_y); }

Cette criture nest cependant pas toujours utilisable, et loptimisation nest pas garantie.

La syntaxe des oprateurs externes permet galement dimplmenter les oprateurs pour lesquels le type de la valeur de retour est celui de loprande de gauche et que le type de cet oprande nest pas une classe dnie par lutilisateur (par exemple si cest un type prdni). En effet, on ne peut pas dnir loprateur lintrieur de la classe du premier oprande dans ce cas, puisque cette classe est dj dnie. De mme, cette syntaxe peut tre utile dans le cas de lcriture doprateurs optimiss pour certains types de donnes, pour lesquels les oprations ralises par loprateur sont plus simples que celles qui auraient t effectues aprs transtypage. Par exemple, si lon veut optimiser la multiplication gauche par un scalaire pour la classe complexe, on devra procder comme suit :
complexe operator*(double k, const complexe &c) { complexe result(c.re()*k,c.im()*k); return result; }

ce qui permettra dcrire des expressions du type :

136

Chapitre 7. C++ : la couche objet


complexe c1, c2; double r; ... c1 = r*c2;

La premire syntaxe naurait permis dcrire un tel oprateur que pour la multiplication droite par un double. En effet, pour crire un oprateur interne permettant de raliser cette optimisation, il aurait fallu surcharger loprateur de multiplication de la classe double pour lui faire accepter un objet de type complexe en second oprande...

7.11.3. Oprateurs daffectation


Nous avons dj vu un exemple doprateur daffectation avec la classe complexe ci-dessus. Cet oprateur tait trs simple, mais ce nest gnralement pas toujours le cas, et limplmentation des oprateurs daffectation peut parfois soulever quelques problmes. Premirement, comme nous lavons dit dans la Section 7.8.2, le fait de dnir un oprateur daffectation signale souvent que la classe na pas une structure simple et que, par consquent, le constructeur de copie et le destructeur fournis par dfaut par le compilateur ne sufsent pas. Il faut donc veiller respecter la rgle des trois, qui stipule que si lune de ces mthodes est rednie, il faut que les trois le soient. Par exemple, si vous ne rednissez pas le constructeur de copie, les critures telles que :
classe object = source;

ne fonctionneront pas correctement. En effet, cest le constructeur de copie qui est appel ici, et non loprateur daffectation comme on pourrait le penser premire vue. De mme, les traitements particuliers effectus lors de la copie ou de linitialisation dun objet devront tre effectus en ordre inverse dans le destructeur de lobjet. Les traitements de destruction consistent gnralement librer la mmoire et toutes les ressources alloues dynamiquement. Lorsque lon crit un oprateur daffectation, on a gnralement reproduire, peu de choses prs, le mme code que celui qui se trouve dans le constructeur de copie. Il arrive mme parfois que lon doive librer les ressources existantes avant de faire laffectation, et donc le code de loprateur daffectation ressemble souvent la concatnation du code du destructeur et du code du constructeur de copie. Bien entendu, cette duplication de code est gnante et peu lgante. Une solution simple est dimplmenter une fonction de duplication et une fonction de libration des donnes. Ces deux fonctions, par exemple reset et clone, pourront tre utilises dans le destructeur, le constructeur de copie et loprateur daffectation. Le programme devient ainsi beaucoup plus simple. Il ne faut gnralement pas utiliser loprateur daffectation dans le constructeur de copie, car cela peut poser des problmes complexes rsoudre. Par exemple, il faut sassurer que loprateur de copie ne cherche pas utiliser des donnes membres non initialises lors de son appel. Un autre problme important est celui de lautoaffectation. Non seulement affecter un objet luimme est inutile et consommateur de ressources, mais en plus cela peut tre dangereux. En effet, laffectation risque de dtruire les donnes membres de lobjet avant mme quelles ne soient copies, ce qui provoquerait en n de compte simplement la destruction de lobjet ! Une solution simple consiste ici ajouter un test sur lobjet source en dbut doprateur, comme dans lexemple suivant :
classe &classe::operator=(const classe &source) { if (&source != this)

137

Chapitre 7. C++ : la couche objet


{ // Traitement de copie des donnes : ... } return *this; }

Enn, la copie des donnes peut lancer une exception et laisser lobjet sur lequel laffectation se fait dans un tat indtermin. La solution la plus simple dans ce cas est encore de construire une copie de lobjet source en local, puis dchanger le contenu des donnes de lobjet avec cette copie. Ainsi, si la copie choue pour une raison ou une autre, lobjet source nest pas modi et reste dans un tat stable. Le pseudo-code permettant de raliser ceci est le suivant :
classe &classe::operator=(const classe &source) { // Construit une copie temporaire de la source : class Temp(source); // change le contenu de cette copie avec lobjet courant : swap(Temp, *this); // Renvoie lobjet courant (modifi) et dtruit les donnes // de la variable temporaire (contenant les anciennes donnes) : return *this; }

Note : Le problme de ltat des objets nest pas spcique loprateur daffectation, mais toutes les mthodes qui modient lobjet, donc, en pratique, toutes les mthodes non const. Lcriture de classes sres au niveau de la gestion des erreurs est donc relativement difcile. Vous trouverez de plus amples informations sur le mcanisme des exceptions en C++ dans le Chapitre 8.

7.11.4. Oprateurs de transtypage


Nous avons vu dans la Section 7.8.3 que les constructeurs peuvent tre utiliss pour convertir des objets du type de leur paramtre vers le type de leur classe. Ces conversions peuvent avoir lieu de manire implicite ou non, selon que le mot cl explicit est appliqu au constructeur en question. Cependant, il nest pas toujours faisable dcrire un tel constructeur. Par exemple, la classe cible peut parfaitement tre une des classes de la bibliothque standard, dont on ne doit videmment pas modier les chiers source, ou mme un des types de base du langage, pour lequel il ny a pas de dnition. Heureusement, les conversions peuvent malgr tout tre ralises dans ce cas, simplement en surchargeant les oprateurs de transtypage. Prenons lexemple de la classe chaine, qui permet de faire des chanes de caractres dynamiques (de longueur variable). Il est possible de les convertir en chane C classiques (cest--dire en tableau de caractres) si loprateur (char const *) a t surcharg :
chaine::operator char const *(void) const;

138

Chapitre 7. C++ : la couche objet On constatera que cet oprateur nattend aucun paramtre, puisquil sapplique lobjet qui lappelle, mais surtout il na pas de type. En effet, puisque cest un oprateur de transtypage, son type est ncessairement celui qui lui correspond (dans le cas prsent, char const *).
Note : Si un constructeur de transtypage est galement dni dans la classe du type cible de la conversion, il peut exister deux moyens de raliser le transtypage. Dans ce cas, le compilateur choisira toujours le constructeur de transtypage de la classe cible la place de loprateur de transtypage, sauf sil est dclar explicit. Ce mot cl peut donc tre utilis partout o lon veut viter que le compilateur nutilise le constructeur de transtypage. Cependant, cette technique ne fonctionne quavec les conversions implicites ralises par le compilateur. Si lutilisateur effectue un transtypage explicite, ce sera nouveau le constructeur qui sera appel. De plus, les conversions ralises par lintermdiaire dun constructeur sont souvent plus performantes que celles ralises par lintermdiaire dun oprateur de transtypage, en raison du fait que lon vite ainsi la copie de la variable temporaire dans le retour de loprateur de transtypage. On vitera donc de dnir les oprateurs de transtypage autant que faire se peut, et on crira de prfrence des constructeurs dans les classes des types cibles des conversions ralises.

7.11.5. Oprateurs de comparaison


Les oprateurs de comparaison sont trs simples surcharger. La seule chose essentielle retenir est quils renvoient une valeur boolenne. Ainsi, pour la classe chaine, on peut dclarer les oprateurs dgalit et dinfriorit (dans lordre lexicographique par exemple) de deux chanes de caractres comme suit :
bool chaine::operator==(const chaine &) const; bool chaine::operator<(const chaine &) const;

7.11.6. Oprateurs dincrmentation et de dcrmentation


Les oprateurs dincrmentation et de dcrmentation sont tous les deux doubles, cest--dire que la mme notation reprsente deux oprateurs en ralit. En effet, ils nont pas la mme signication, selon quils sont placs avant ou aprs leur oprande. Le problme est que comme ces oprateurs ne prennent pas de paramtres (ils ne travaillent que sur lobjet), il est impossible de les diffrencier par surcharge. La solution qui a t adopte est de les diffrencier en donnant un paramtre ctif de type int lun dentre eux. Ainsi, les oprateurs ++ et -- ne prennent pas de paramtre lorsquil sagit des oprateurs prxs, et ont un argument ctif (que lon ne doit pas utiliser) lorsquils sont sufxs. Les versions prxes des oprateurs doivent renvoyer une rfrence sur lobjet lui-mme, les versions sufxes en revanche peuvent se contenter de renvoyer la valeur de lobjet. Exemple 7-18. Oprateurs dincrmentation et de dcrmentation
class Entier { int i; public: Entier(int j)

139

Chapitre 7. C++ : la couche objet


{ i=j; return; } Entier operator++(int) { Entier tmp(i); ++i; return tmp; } // Oprateur suffixe : // retourne la valeur et incrmente // la variable.

Entier &operator++(void) // Oprateur prfixe : incrmente { // la variable et la retourne. ++i; return *this; } };

Note : Les oprateurs sufxs crant des objets temporaires, ils peuvent nuire gravement aux performances des programmes qui les utilisent de manire inconsidre. Par consquent, on ne les utilisera que lorsque cela est rellement ncessaire. En particulier, on vitera dutiliser ces oprateurs dans toutes les oprations dincrmentation des boucles ditration.

7.11.7. Oprateur fonctionnel


Loprateur dappel de fonctions () peut galement tre surcharg. Cet oprateur permet de raliser des objets qui se comportent comme des fonctions (ce que lon appelle des foncteurs). La bibliothque standard C++ en fait un usage intensif, comme nous pourrons le constater dans la deuxime partie de ce document. Loprateur fonctionnel est galement trs utile en raison de son n-arit (*, /, etc. sont des oprateurs binaires car ils ont deux oprandes, ?: est un oprateur ternaire car il a trois oprandes, () est n-aire car il peut avoir n oprandes). Il est donc utilis couramment pour les classes de gestion de matrices de nombres, an dautoriser lcriture matrice(i,j,k) . Exemple 7-19. Implmentation dune classe matrice
class matrice { typedef double *ligne; ligne *lignes; unsigned short int n; unsigned short int m;

// Nombre de lignes (1er paramtre). // Nombre de colonnes (2me paramtre).

public: matrice(unsigned short int nl, unsigned short int nc); matrice(const matrice &source); ~matrice(void); matrice &operator=(const matrice &m1); double &operator()(unsigned short int i, unsigned short int j); double operator()(unsigned short int i, unsigned short int j) const; };

140

Chapitre 7. C++ : la couche objet

// Le constructeur : matrice::matrice(unsigned short int nl, unsigned short int nc) { n = nl; m = nc; lignes = new ligne[n]; for (unsigned short int i=0; i<n; ++i) lignes[i] = new double[m]; return; } // Le constructeur de copie : matrice::matrice(const matrice &source) { m = source.m; n = source.n; lignes = new ligne[n]; // Alloue. for (unsigned short int i=0; i<n; ++i) { lignes[i] = new double[m]; for (unsigned short int j=0; j<m; ++j) // Copie. lignes[i][j] = source.lignes[i][j]; } return; } // Le destructeur : matrice::~matrice(void) { for (unsigned short int i=0; i<n; ++i) delete[] lignes[i]; delete[] lignes; return; } // Loprateur daffectation : matrice &matrice::operator=(const matrice &source) { if (&source != this) { if (source.n!=n || source.m!=m) // Vrifie les dimensions. { for (unsigned short int i=0; i<n; ++i) delete[] lignes[i]; delete[] lignes; // Dtruit... m = source.m; n = source.n; lignes = new ligne[n]; // et ralloue. for (i=0; i<n; ++i) lignes[i] = new double[m]; } for (unsigned short int i=0; i<n; ++i) // Copie. for (unsigned short int j=0; j<m; ++j) lignes[i][j] = source.lignes[i][j]; } return *this; }

141

Chapitre 7. C++ : la couche objet

// Oprateurs daccs : double &matrice::operator()(unsigned short int i, unsigned short int j) { return lignes[i][j]; } double matrice::operator()(unsigned short int i, unsigned short int j) const { return lignes[i][j]; }

Ainsi, on pourra effectuer la dclaration dune matrice avec :


matrice m(2,3);

et accder ses lments simplement avec :


m(i,j)=6;

On remarquera que lon a dni deux oprateurs fonctionnels dans lexemple donn ci-dessus. Le premier renvoie une rfrence et permet de modier la valeur dun des lments de la matrice. Cet oprateur ne peut bien entendu pas sappliquer une matrice constante, mme simplement pour lire un lment. Cest donc le deuxime oprateur qui sera utilis pour lire les lments des matrices constantes, car il renvoie une valeur et non plus une rfrence. Le choix de loprateur utiliser est dtermin par la prsence du mot cl const, qui indique que seul cet oprateur peut tre utilis pour une matrice constante.
Note : Les oprations de base sur les matrices (addition, soustraction, inversion, transposition, etc.) nont pas t reportes ici par souci de clart. La manire de dnir ces oprateurs a t prsente dans les sections prcdentes.

7.11.8. Oprateurs dindirection et de drfrencement


Loprateur de drfrencement * permet lcriture de classes dont les objets peuvent tre utiliss dans des expressions manipulant des pointeurs. Loprateur dindirection & quant lui, permet de renvoyer une adresse autre que celle de lobjet sur lequel il sapplique. Enn, loprateur de drfrencement et de slection de membres de structures -> permet de raliser des classes qui encapsulent dautres classes. Si les oprateurs de drfrencement et dindirection & et * peuvent renvoyer une valeur de type quelconque, ce nest pas le cas de loprateur de drfrencement et de slection de membre ->. Cet oprateur doit ncessairement renvoyer un type pour lequel il doit encore tre applicable. Ce type doit donc soit surcharger loprateur ->, soit tre un pointeur sur une structure, union ou classe.

142

Chapitre 7. C++ : la couche objet Exemple 7-20. Oprateur de drfrencement et dindirection


// Cette classe est encapsule par une autre classe : struct Encapsulee { int i; // Donne accder. }; Encapsulee o; // Objet manipuler.

// Cette classe est la classe encapsulante : struct Encapsulante { Encapsulee *operator->(void) const { return &o; } Encapsulee *operator&(void) const { return &o; } Encapsulee &operator*(void) const { return o; } }; // Exemple dutilisation : void f(int i) { Encapsulante e; e->i=2; // Enregistre 2 dans o.i. (*e).i = 3; // Enregistre 3 dans o.i. Encapsulee *p = &e; p->i = 4; // Enregistre 4 dans o.i. return ; }

7.11.9. Oprateurs dallocation dynamique de mmoire


Les oprateurs les plus difciles crire sont sans doute les oprateurs dallocation dynamique de mmoire. Ces oprateurs prennent un nombre variable de paramtres, parce quils sont compltement surchargeables (cest dire quil est possible de dnir plusieurs surcharges de ces oprateurs mme au sein dune mme classe, sils sont dnis de manire interne). Il est donc possible de dnir plusieurs oprateurs new ou new[], et plusieurs oprateurs delete ou delete[]. Cependant, les premiers paramtres de ces oprateurs doivent toujours tre la taille de la zone de la mmoire allouer dans le cas des oprateurs new et new[], et le pointeur sur la zone de la mmoire restituer dans le cas des oprateurs delete et delete[]. La forme la plus simple de new ne prend quun paramtre : le nombre doctets allouer, qui vaut toujours la taille de lobjet construire. Il doit renvoyer un pointeur du type void. Loprateur delete correspondant peut prendre, quant lui, soit un, soit deux paramtres. Comme on la dj dit, le premier paramtre est toujours un pointeur du type void sur lobjet dtruire. Le deuxime paramtre,

143

Chapitre 7. C++ : la couche objet sil existe, est du type size_t et contient la taille de lobjet dtruire. Les mmes rgles sappliquent pour les oprateurs new[] et delete[], utiliss pour les tableaux. Lorsque les oprateurs delete et delete[] prennent deux paramtres, le deuxime paramtre est la taille de la zone de la mmoire restituer. Cela signie que le compilateur se charge de mmoriser cette information. Pour les oprateurs new et delete, cela ne cause pas de problme, puisque la taille de cette zone est xe par le type de lobjet. En revanche, pour les tableaux, la taille du tableau doit tre stocke avec le tableau. En gnral, le compilateur utilise un en-tte devant le tableau dobjets. Cest pour cela que la taille allouer passe new[], qui est la mme que la taille dsallouer passe en paramtre delete[], nest pas gale la taille dun objet multiplie par le nombre dobjets du tableau. Le compilateur demande un peu plus de mmoire, pour mmoriser la taille du tableau. On ne peut donc pas, dans ce cas, faire dhypothses quant la structure que le compilateur donnera la mmoire alloue pour stocker le tableau. En revanche, si delete[] ne prend en paramtre que le pointeur sur le tableau, la mmorisation de la taille du tableau est la charge du programmeur. Dans ce cas, le compilateur donne new[] la valeur exacte de la taille du tableau, savoir la taille dun objet multiplie par le nombre dobjets dans le tableau. Exemple 7-21. Dtermination de la taille de len-tte des tableaux
#include <stdlib.h> #include <stdio.h> int buffer[256]; class Temp { char i[13]; // Buffer servant stocker le tableau.

// sizeof(Temp) doit tre premier.

public: static void *operator new[](size_t taille) { return buffer; } static void operator delete[](void *p, size_t taille) { printf("Taille de len-tte : %d\n", taille-(taille/sizeof(Temp))*sizeof(Temp)); return ; } }; int main(void) { delete[] new Temp[1]; return EXIT_SUCCESS; }

Il est noter quaucun des oprateurs new, delete, new[] et delete[] ne reoit le pointeur this en paramtre : ce sont des oprateurs statiques. Cela est normal puisque, lorsquils sexcutent, soit lobjet nest pas encore cr, soit il est dj dtruit. Le pointeur this nexiste donc pas encore (ou nest plus valide) lors de lappel de ces oprateurs. Les oprateurs new et new[] peuvent avoir une forme encore un peu plus complique, qui permet de leur passer des paramtres lors de lallocation de la mmoire. Les paramtres supplmentaires doivent

144

Chapitre 7. C++ : la couche objet imprativement tre les paramtres deux et suivants, puisque le premier paramtre indique toujours la taille de la zone de mmoire allouer. Comme le premier paramtre est calcul par le compilateur, il ny a pas de syntaxe permettant de le passer aux oprateurs new et new[]. En revanche, une syntaxe spciale est ncessaire pour passer les paramtres supplmentaires. Cette syntaxe est dtaille ci-dessous. Si loprateur new est dclar de la manire suivante dans la classe classe :
static void *operator new(size_t taille, paramtres);

o taille est la taille de la zone de mmoire allouer et paramtres la liste des paramtres additionnels, alors on doit lappeler avec la syntaxe suivante :
new(paramtres) classe;

Les paramtres sont donc passs entre parenthses comme pour une fonction normale. Le nom de la fonction est new, et le nom de la classe suit lexpression new comme dans la syntaxe sans paramtres. Cette utilisation de new est appele new avec placement. Le placement est souvent utilis an de raliser des rallocations de mmoire dun objet un autre. Par exemple, si lon doit dtruire un objet allou dynamiquement et en reconstruire immdiatement un autre du mme type, les oprations suivantes se droulent : 1. appel du destructeur de lobjet (ralis par lexpression delete) ; 2. appel de loprateur delete ; 3. appel de loprateur new ; 4. appel du constructeur du nouvel objet (ralis par lexpression new).

Cela nest pas trs efcace, puisque la mmoire est restitue pour tre alloue de nouveau immdiatement aprs. Il est beaucoup plus logique de rutiliser la mmoire de lobjet dtruire pour le nouvel objet, et de reconstruire ce dernier dans cette mmoire. Cela peut se faire comme suit : 1. appel explicite du destructeur de lobjet dtruire ; 2. appel de new avec comme paramtre supplmentaire le pointeur sur lobjet dtruit ; 3. appel du constructeur du deuxime objet (ralis par lexpression new).

Lappel de new ne fait alors aucune allocation : on gagne ainsi beaucoup de temps. Exemple 7-22. Oprateurs new avec placement
#include <stdlib.h> class A { public: A(void) { return ; }

// Constructeur.

145

Chapitre 7. C++ : la couche objet

~A(void) { return ; }

// Destructeur.

// Loprateur new suivant utilise le placement. // Il reoit en paramtre le pointeur sur le bloc // utiliser pour la requte dallocation dynamique // de mmoire. static void *operator new (size_t taille, A *bloc) { return (void *) bloc; } // Oprateur new normal : static void *operator new(size_t taille) { // Implmentation : return malloc(taille); } // Oprateur delete normal : static void operator delete(void *pBlock) { free(pBlock); return ; } }; int main(void) { A *pA=new A;

// // pA->~A(); // A *pB=new(pA) A; // delete pB; // return EXIT_SUCCESS;

Cration dun objet de classe A. Loprateur new global du C++ est utilis. Appel explicite du destructeur de A. Rutilisation de la mmoire de A. Destruction de lobjet.

Dans cet exemple, la gestion de la mmoire est ralise par les oprateurs new et delete normaux. Cependant, la rutilisation de la mmoire alloue se fait grce un oprateur new avec placement, dni pour loccasion. Ce dernier ne fait strictement rien dautre que de renvoyer le pointeur quon lui a pass en paramtre. On notera quil est ncessaire dappeler explicitement le destructeur de la classe A avant de rutiliser la mmoire de lobjet, car aucune expression delete ne sen charge avant la rutilisation de la mmoire.
Note : Les oprateurs new et delete avec placement prdnis par la bibliothque standard C++ effectuent exactement ce que les oprateurs de cet exemple font. Il nest donc pas ncessaire de les dnir, si on ne fait aucun autre traitement que de rutiliser le bloc mmoire que loprateur new reoit en paramtre.

Il est impossible de passer des paramtres loprateur delete dans une expression delete. Cela est d au fait quen gnral on ne connat pas le contexte de la destruction dun objet (alors qu lallocation, on connat le contexte de cration de lobjet). Normalement, il ne peut donc y avoir

146

Chapitre 7. C++ : la couche objet quun seul oprateur delete. Cependant, il existe un cas o lon connat le contexte de lappel de loprateur delete : cest le cas o le constructeur de la classe lance une exception (voir le Chapitre 8 pour plus de dtails ce sujet). Dans ce cas, la mmoire alloue par loprateur new doit tre restitue et loprateur delete est automatiquement appel, puisque lobjet na pas pu tre construit. An dobtenir un comportement symtrique, il est permis de donner des paramtres additionnels loprateur delete. Lorsquune exception est lance dans le constructeur de lobjet allou, loprateur delete appel est loprateur dont la liste des paramtres correspond celle de loprateur new qui a t utilis pour crer lobjet. Les paramtres passs loprateur delete prennent alors exactement les mmes valeurs que celles qui ont t donnes aux paramtres de loprateur new lors de lallocation de la mmoire de lobjet. Ainsi, si loprateur new a t utilis sans placement, loprateur delete sans placement sera appel. En revanche, si loprateur new a t appel avec des paramtres, loprateur delete qui a les mmes paramtres sera appel. Si aucun oprateur delete ne correspond, aucun oprateur delete nest appel (si loprateur new na pas allou de mmoire, cela nest pas grave, en revanche, si de la mmoire a t alloue, elle ne sera pas restitue). Il est donc important de dnir un oprateur delete avec placement pour chaque oprateur new avec placement dni. Lexemple prcdent doit donc tre rcrit de la manire suivante :
#include <stdlib.h> static bool bThrow = false; class A { public: A(void) // Constructeur. { // Le constructeur est susceptible // de lancer une exception : if (bThrow) throw 2; return ; } ~A(void) { return ; } // Destructeur.

// Loprateur new suivant utilise le placement. // Il reoit en paramtre le pointeur sur le bloc // utiliser pour la requte dallocation dynamique // de mmoire. static void *operator new (size_t taille, A *bloc) { return (void *) bloc; } // Loprateur delete suivant est utilis dans les expressions // qui utilisent loprateur new avec placement ci-dessus, // si une exception se produit dans le constructeur. static void operator delete(void *p, A *bloc) { // On ne fait rien, parce que loprateur new correspondant // na pas allou de mmoire. return ; }

147

Chapitre 7. C++ : la couche objet

// Oprateur new et delete normaux : static void *operator new(size_t taille) { return malloc(taille); } static void operator delete(void *pBlock) { free(pBlock); return ; } }; int main(void) { A *pA=new A; pA->~A(); bThrow = true; try { A *pB=new(pA) A; // // // // // Rutilisation de la mmoire de A. Si une exception a lieu, loprateur delete(void *, A *) avec placement est utilis. Destruction de lobjet.

// // // //

Cration dun objet de classe A. Appel explicite du destructeur de A. Maintenant, le constructeur de A lance une exception.

delete pB; } catch (...) { // Loprateur delete(void *, A *) ne libre pas la mmoire // alloue lors du premier new. Il faut donc quand mme // le faire, mais sans delete, car lobjet point par pA // est dj dtruit, et celui point par pB la t par // loprateur delete(void *, A *) : free(pA); } return EXIT_SUCCESS; }

Note : Il est possible dutiliser le placement avec les oprateurs new[] et delete[] exactement de la mme manire quavec les oprateurs new et delete. On notera que lorsque loprateur new est utilis avec placement, si le deuxime argument est de type size_t, loprateur delete deux arguments peut tre interprt soit comme un oprateur delete classique sans placement mais avec deux paramtres, soit comme loprateur delete avec placement correspondant loprateur new avec placement. An de rsoudre cette ambigut, le compilateur interprte systmatiquement loprateur delete avec un deuxime paramtre de type size_t comme tant loprateur deux paramtres sans placement. Il est donc impossible de dnir un oprateur delete avec placement sil a deux paramtres, le deuxime tant de type size_t. Il en est de mme avec les oprateurs new[] et delete[].

Quelle que soit la syntaxe que vous dsirez utiliser, les oprateurs new, new[], delete et delete[] doivent avoir un comportement bien dtermin. En particulier, les oprateurs delete et delete[]

148

Chapitre 7. C++ : la couche objet doivent pouvoir accepter un pointeur nul en paramtre. Lorsquun tel pointeur est utilis dans une expression delete, aucun traitement ne doit tre fait. Enn, vos oprateurs new et new[] doivent, en cas de manque de mmoire, appeler un gestionnaire derreur. Le gestionnaire derreur fourni par dfaut lance une exception de classe std::bad_alloc (voir le Chapitre 8 pour plus de dtails sur les exceptions). Cette classe est dnie comme suit dans le chier den-tte new :
class bad_alloc : public exception { public: bad_alloc(void) throw(); bad_alloc(const bad_alloc &) throw(); bad_alloc &operator=(const bad_alloc &) throw(); virtual ~bad_alloc(void) throw(); virtual const char *what(void) const throw(); };

Note : Comme son nom lindique, cette classe est dnie dans lespace de nommage std::. Si vous ne voulez pas utiliser les notions des espaces de nommage, vous devrez inclure le chier den-tte new.h au lieu de new. Vous obtiendrez de plus amples renseignements sur les espaces de nommage dans le Chapitre 10.

La classe exception dont bad_alloc hrite est dclare comme suit dans le chier den-tte exception :
class exception { public: exception (void) throw(); exception(const exception &) throw(); exception &operator=(const exception &) throw(); virtual ~exception(void) throw(); virtual const char *what(void) const throw(); };

Note : Contrairement aux en-ttes de la bibliothque C standard, les noms des en-ttes de la bibliothque standard C++ nont pas dextension. Cela permet notamment dviter des conits de noms avec les en-ttes existants. Vous trouverez plus dinformations sur les exceptions dans le Chapitre 8.

Si vous dsirez remplacer le gestionnaire par dfaut, vous pouvez utiliser la fonction std::set_new_handler. Cette fonction attend en paramtre le pointeur sur le gestionnaire derreur installer et renvoie le pointeur sur le gestionnaire derreur prcdemment install. Les gestionnaires derreurs ne prennent aucun paramtre et ne renvoient aucune valeur. Leur comportement doit tre le suivant :

149

Chapitre 7. C++ : la couche objet

soit ils prennent les mesures ncessaires pour permettre lallocation du bloc de mmoire demand et rendent la main loprateur new. Ce dernier refait alors une tentative pour allouer le bloc de mmoire. Si cette tentative choue nouveau, le gestionnaire derreur est rappel. Cette boucle se poursuit jusqu ce que lopration se droule correctement ou quune exception std::bad_alloc soit lance ; soit ils lancent une exception de classe std::bad_alloc ; soit ils terminent lexcution du programme en cours.

La bibliothque standard dnit une version avec placement des oprateurs new et new[], qui renvoient le pointeur nul au lieu de lancer une exception en cas de manque de mmoire. Ces oprateurs prennent un deuxime paramtre, de type std::nothrow_t, qui doit tre spci lors de lappel. La bibliothque standard dnit un objet constant de ce type an que les programmes puissent lutiliser sans avoir le dnir eux-mme. Cet objet se nomme std::nothrow Exemple 7-23. Utilisation de new sans exception
char *data = new(std::nothrow) char[25]; if (data == NULL) { // Traitement de lerreur... . . . }

Note : La plupart des compilateurs ne respectent pas les rgles dictes par la norme C++. En effet, ils prfrent retourner la valeur nulle en cas de manque de mmoire au lieu de lancer une exception. On peut rendre ces implmentations compatibles avec la norme en installant un gestionnaire derreur qui lance lui-mme lexception std::bad_alloc.

7.12. Des entres - sorties simplies


Les ux dentre / sortie de la bibliothque standard C++ constituent sans doute lune des applications les plus intressantes de la surcharge des oprateurs. Comme nous allons le voir, la surcharge des oprateurs << et >> permet dcrire et de lire sur ces ux de manire trs intuitive. En effet, la bibliothque standard C++ dnit dans len-tte iostream des classes extrmement puissantes permettant de manipuler les ux dentre / sortie. Ces classes ralisent en particulier les oprations dentre / sortie de et vers les priphriques dentre et les priphriques de sortie standards (gnralement, le clavier et lcran), mais elles ne sarrtent pas l : elles permettent galement de travailler sur des chiers ou encore sur des tampons en mmoire. Les classes dentre / sortie de la bibliothque standard C++ permettent donc deffectuer les mmes oprations que les fonctions printf et scanf de la bibliothque C standard. Cependant, grce au mcanisme de surcharge des oprateurs, elles sont beaucoup plus faciles dutilisation. En effet, les oprateurs << et >> de ces classes ont t surchargs pour chaque type de donne du langage, permettant ainsi de raliser des entres / sorties types extrmement facilement. Loprateur <<, galement appele oprateur dinsertion, sera utilis pour raliser des critures sur un ux de donnes, tandis que loprateur >>, ou oprateur dextraction, permettra de raliser la lecture dune nouvelle donne dans

150

Chapitre 7. C++ : la couche objet le ux dentre. Ces deux oprateurs renvoient tous les deux le ux de donnes utilis, ce qui permet de raliser plusieurs oprations dentre / sortie successivement sur le mme ux.
Note : Cette section na pas pour but de dcrire en dtail les ux dentre / sortie de la bibliothque standard C++, mais plutt den faire une prsentation simple permettant de les utiliser sans avoir se plonger prmaturment dans des notions extrmement volues. Vous trouverez une description exhaustive des mcanismes des ux dentre / sortie de la bibliothque standard C++ dans le Chapitre 15.

La bibliothque standard dnit quatre instances particulires de ses classes dentre / sortie : cin, cout, cerr et clog. Ces objets sont des instances des classes istream et ostream, prenant respectivement en charge lentre et la sortie des donnes des programmes. Lobjet cin correspond au ux dentre standard stdin du programme, et lobjet cout aux ux de sortie standard stdout. Enn, les objets cerr et clog sont associs au ux derreurs standard stderr. Thoriquement, cerr doit tre utilis pour lcriture des messages derreur des programmes, et clog pour les messages dinformation. Cependant, en pratique, les donnes crites sur ces deux ux sont crites dans le mme ux, et lemploi de lobjet clog est assez rare. Lutilisation des oprateurs dinsertion et dextraction sur ces ux se rsume donc la syntaxe suivante :
cin >> variable [>> variable [...]]; cout << valeur [<< valeur [...]];

Comme on le voit, il est possible deffectuer plusieurs entres ou plusieurs sortie successivement sur un mme ux. De plus, la bibliothque standard dnie ce que lon appelle des manipulateurs permettant de raliser des oprations simples sur les ux dentre / sortie. Le manipulateur le plus utilis est sans nul doute le manipulateur endl qui, comme son nom lindique, permet de signaler une n de ligne et deffectuer un saut de ligne lorsquil est employ sur un ux de sortie. Exemple 7-24. Flux dentre / sortie cin et cout
#include <stdlib.h> #include <iostream> using namespace std; int main(void) { int i; // Lit un entier : cin >> i; // Affiche cet entier et le suivant : cout << i << " " << i+1 << endl; return EXIT_SUCCESS; }

Note : Comme on le verra dans le Chapitre 15, les manipulateurs sont en ralit des fonctions pour le type desquelles un oprateur << ou un oprateur >> a t dni dans les classes dentre / sortie. Ces oprateurs appellent ces fonctions, qui effectuent chacune des modications spciques sur le ux sur lequel elles travaillent. Les ux dentre / sortie cin, cout cerr et clog sont dclars dans lespace de nommage std:: de la bibliothque standard C++. On devra donc faire prcder leur nom du prxe std:: pour

151

Chapitre 7. C++ : la couche objet


y accder, ou utiliser un directive using pour importer les symboles de la bibliothque standard C++ dans lespace de nommage global. Vous trouverez de plus amples renseignements sur les espaces de nommages dans le Chapitre 10.

Les avantages des ux C++ sont nombreux, on notera en particulier ceux-ci :

le type des donne est automatiquement pris en compte par les oprateurs dinsertion et dextraction (ils sont surchargs pour tous les types prdnis) ; les oprateurs dextraction travaillent par rfrence (on ne risque plus domettre loprateur & dans la fonction scanf) ; il est possible de dnir des oprateurs dinsertion et dextraction pour dautres types de donnes que les types de base du langage ; leur utilisation est globalement plus simple.

Les ux dentre / sortie dnis par la bibliothque C++ sont donc dune extrme souplesse et sont extensibles aux types de donnes utilisateur. Par ailleurs, ils disposent dun grand nombre de paramtres de formatage et doptions avances. Toutes ces fonctionnalits seront dcrites dans le Chapitre 15, o nous verrons galement comment raliser des entres / sorties dans des chiers.

7.13. Mthodes virtuelles


Les mthodes virtuelles nont strictement rien voir avec les classes virtuelles, bien quelles utilisent le mme mot cl virtual. Ce mot cl est utilis ici dans un contexte et dans un sens diffrent. Nous savons quil est possible de rednir les mthodes dune classe mre dans une classe lle. Lors de lappel dune fonction ainsi rednie, la fonction appele est la dernire fonction dnie dans la hirarchie de classe. Pour appeler la fonction de la classe mre alors quelle a t rednie, il faut prciser le nom de la classe laquelle elle appartient avec loprateur de rsolution de porte (::). Bien que simple, cette utilisation de la rednition des mthodes peut poser des problmes. Supposons quune classe B hrite de sa classe mre A. Si A possde une mthode x appelant une autre mthode y rednie dans la classe lle B, que se passe-t-il lorsquun objet de classe B appelle la mthode x ? La mthode appele tant celle de la classe A, elle appellera la mthode y de la classe A. Par consquent, la rednition de y ne sert rien ds quon lappelle partir dune des fonctions dune des classes mres. Une premire solution consisterait rednir la mthode x dans la classe B. Mais ce nest ni lgant, ni efcace. Il faut en fait forcer le compilateur ne pas faire le lien dans la fonction x de la classe A avec la fonction y de la classe A. Il faut que x appelle soit la fonction y de la classe A si elle est appele par un objet de la classe A, soit la fonction y de la classe B si elle est appele pour un objet de la classe B. Le lien avec lune des mthodes y ne doit tre fait quau moment de lexcution, cest--dire quon doit faire une dition de liens dynamique. Le C++ permet de faire cela. Pour cela, il suft de dclarer virtuelle la fonction de la classe de base qui est rednie dans la classe lle, cest--dire la fonction y. Cela se fait en faisant prcder par le mot cl virtual dans la classe de base.

152

Chapitre 7. C++ : la couche objet Exemple 7-25. Rednition de mthode de classe de base
#include <iostream> using namespace std; // Dfinit la classe de base des donnes. class DonneeBase { protected: int Numero; int Valeur;

// Les donnes sont numrotes. // et sont constitues dune valeur entire // pour les donnes de base. // Entre une donne. // Met jour la donne.

public: void Entre(void); void MiseAJour(void); };

void DonneeBase::Entre(void) { cin >> Numero; // Entre le numro de la donne. cout << endl; cin >> Valeur; // Entre sa valeur. cout << endl; return; } void DonneeBase::MiseAJour(void) { Entre(); // Entre une nouvelle donne // la place de la donne en cours. return; } /* Dfinit la classe des donnes dtailles. */ class DonneeDetaillee : private DonneeBase { int ValeurEtendue; // Les donnes dtailles ont en plus // une valeur tendue. public: void Entre(void); };

// Redfinition de la mthode dentre.

void DonneeDetaillee::Entre(void) { DonneeBase::Entre(); // Appelle la mthode de base. cin >> ValeurEtendue; // Entre la valeur tendue. cout << endl; return; }

Si d est un objet de la classe DonneeDetaillee, lappel de d.Entre ne causera pas de problme. En revanche, lappel de d.MiseAJour ne fonctionnera pas correctement, car la fonction Entre appele

153

Chapitre 7. C++ : la couche objet dans MiseAJour est la fonction de la classe DonneeBase, et non la fonction rednie dans DonneeDetaille. Il fallait dclarer la fonction Entre comme une fonction virtuelle. Il nest ncessaire de le faire que dans la classe de base. Celle-ci doit donc tre dclare comme suit :
class DonneeBase { protected: int Numero; int Valeur; public: virtual void Entre(void); void MiseAJour(void); };

// Fonction virtuelle.

Cette fois, la fonction Entre appele dans MiseAJour est soit la fonction de la classe DonneeBase, si MiseAJour est appele pour un objet de classe DonneeBase, soit celle de la classe DonneeDetaille si MiseAJour est appele pour un objet de la classe DonneeDetaillee. En rsum, les mthodes virtuelles sont des mthodes qui sont appeles selon la vraie classe de lobjet qui lappelle. Les objets qui contiennent des mthodes virtuelles peuvent tre manipuls en tant quobjets des classes de base, tout en effectuant les bonnes oprations en fonction de leur type. Ils apparaissent donc comme tant des objets de la classe de base et des objets de leur classe complte indiffremment, et on peut les considrer soit comme les uns, soit comme les autres. Un tel comportement est appel polymorphisme (cest--dire qui peut avoir plusieurs aspects diffrents). Nous verrons une application du polymorphisme dans le cas des pointeurs sur les objets.

7.14. Drivation
Nous allons voir ici les rgles de drivation. Ces rgles permettent de savoir ce qui est autoris et ce qui ne lest pas lorsquon travaille avec des classes de base et leurs classes lles (ou classes drives). La premire rgle, qui est aussi la plus simple, indique quil est possible dutiliser un objet dune classe drive partout o lon peut utiliser un objet dune de ses classes mres. Les mthodes et donnes des classes mres appartiennent en effet par hritage aux classes lles. Bien entendu, on doit avoir les droits daccs sur les membres de la classe de base que lon utilise (laccs peut tre restreint lors de lhritage). La deuxime rgle indique quil est possible de faire une affectation dune classe drive vers une classe mre. Les donnes qui ne servent pas linitialisation sont perdues, puisque la classe mre ne possde pas les champs correspondants. En revanche, linverse est strictement interdit. En effet, les donnes de la classe lle qui nexistent pas dans la classe mre ne pourraient pas recevoir de valeur, et linitialisation ne se ferait pas correctement. Enn, la troisime rgle dit que les pointeurs des classes drives sont compatibles avec les pointeurs des classes mres. Cela signie quil est possible daffecter un pointeur de classe drive un pointeur dune de ses classes de base. Il faut bien entendu que lon ait en outre le droit daccder la classe de base, cest--dire quau moins un de ses membres puisse tre utilis. Cette condition nest pas toujours vrie, en particulier pour les classes de base dont lhritage est private.

154

Chapitre 7. C++ : la couche objet Un objet driv point par un pointeur dune des classes mres de sa classe est considr comme un objet de la classe du pointeur qui le pointe. Les donnes spciques sa classe ne sont pas supprimes, elles sont seulement momentanment inaccessibles. Cependant, le mcanisme des mthodes virtuelles continue de fonctionner correctement. En particulier, le destructeur de la classe de base doit tre dclar en tant que mthode virtuelle. Cela permet dappeler le bon destructeur en cas de destruction de lobjet. Il est possible de convertir un pointeur de classe de base en un pointeur de classe drive si la classe de base nest pas virtuelle. Cependant, mme lorsque la classe de base nest pas virtuelle, cela est dangereux, car la classe drive peut avoir des membres qui ne sont pas prsents dans la classe de base, et lutilisation de ce pointeur peut conduire des erreurs trs graves. Cest pour cette raison quun transtypage est ncessaire pour ce type de conversion. Soient par exemple les deux classes dnies comme suit :
#include <iostream> using namespace std; class Mere { public: Mere(void); ~Mere(void); }; Mere::Mere(void) { cout << "Constructeur de la classe mre." << endl; return; } Mere::~Mere(void) { cout << "Destructeur de la classe mre." << endl; return; } class Fille : public Mere { public: Fille(void); ~Fille(void); }; Fille::Fille(void) : Mere() { cout << "Constructeur de la classe fille." << endl; return; } Fille::~Fille(void) { cout << "Destructeur de la classe fille." << endl; return; }

155

Chapitre 7. C++ : la couche objet

Avec ces dnitions, seule la premire des deux affectations suivantes est autorise :
Mere m; Fille f; m=f; f=m; // Instanciation de deux objets.

// Cela est autoris, mais linverse ne le serait pas : // ERREUR !! (ne compile pas).

Les mmes rgles sont applicables pour les pointeurs dobjets :


Mere *pm, m; Fille *pf, f; pf=&f; // Autoris. pm=pf; // Autoris. Les donnes et les mthodes // de la classe fille ne sont plus accessibles // avec ce pointeur : *pm est un objet // de la classe mre. pf=&m; // ILLGAL : il faut faire un transtypage : pf=(Fille *) &m; // Cette fois, cest lgal, mais DANGEREUX ! // En effet, les mthodes de la classe filles // ne sont pas dfinies, puisque m est une classe mre.

Lutilisation dun pointeur sur la classe de base pour accder une classe drive ncessite dutiliser des mthodes virtuelles. En particulier, il est ncessaire de rendre virtuels les destructeurs. Par exemple, avec la dnition donne ci-dessus pour les deux classes, le code suivant est faux :
Mere *pm; Fille *pf = new Fille; pm = pf; delete pm; // Appel du destructeur de la classe mre !

Pour rsoudre le problme, il faut que le destructeur de la classe mre soit virtuel (il est inutile de dclarer virtuel le destructeur des classes lles) :
class Mere { public: Mere(void); virtual ~Mere(void); };

On notera que bien que loprateur delete soit une fonction statique, le bon destructeur est appel, car le destructeur est dclar virtual. En effet, loprateur delete recherche le destructeur appeler dans la classe de lobjet le plus driv. De plus, loprateur delete restitue la mmoire de lobjet complet, et pas seulement celle du sous-objet rfrenc par le pointeur utilis dans lexpression delete. Lorsquon utilise la drivation, il est donc trs important de dclarer les destructeurs virtuels pour que loprateur delete utilise le vrai type de lobjet dtruire.

156

Chapitre 7. C++ : la couche objet

7.15. Mthodes virtuelles pures - Classes abstraites


Une mthode virtuelle pure est une mthode qui est dclare mais non dnie dans une classe. Elle est dnie dans une des classes drives de cette classe. Une classe abstraite est une classe comportant au moins une mthode virtuelle pure. tant donn que les classes abstraites ont des mthodes non dnies, il est impossible dinstancier des objets pour ces classes. En revanche, on pourra les rfrencer avec des pointeurs. Le mcanisme des mthodes virtuelles pures et des classes abstraites permet de crer des classes de base contenant toutes les caractristiques dun ensemble de classes drives, pour pouvoir les manipuler avec un unique type de pointeur. En effet, les pointeurs des classes drives sont compatibles avec les pointeurs des classes de base, on pourra donc rfrencer les classes drives avec des pointeurs sur les classes de base, donc avec un unique type sous-jacent : celui de la classe de base. Cependant, les mthodes des classes drives doivent exister dans la classe de base pour pouvoir tre accessibles travers le pointeur sur la classe de base. Cest ici que les mthodes virtuelles pures apparaissent. Elles forment un moule pour les mthodes des classes drives, qui les dnissent. Bien entendu, il faut que ces mthodes soient dclares virtuelles, puisque laccs se fait avec un pointeur de classe de base et quil faut que ce soit la mthode de la classe relle de lobjet (cest--dire la classe drive) qui soit appele. Pour dclarer une mthode virtuelle pure dans une classe, il suft de faire suivre sa dclaration de =0 . La fonction doit galement tre dclare virtuelle :
virtual type nom(paramtres) =0; =0 signie ici simplement quil ny a pas dimplmentation de cette mthode dans cette classe. Note : =0 doit tre plac compltement en n de dclaration, cest--dire aprs le mot cl const pour les mthodes const et aprs la dclaration de la liste des exceptions autorises (voir le Chapitre 8 pour plus de dtails ce sujet).

Un exemple vaut mieux quun long discours. Soit donc, par exemple, construire une structure de donnes pouvant contenir dautres structures de donnes, quels que soient leurs types. Cette structure de donnes est appele un conteneur, parce quelle contient dautres structures de donnes. Il est possible de dnir diffrents types de conteneurs. Dans cet exemple, on ne sintressera quau conteneur de type sac. Un sac est un conteneur pouvant contenir zro ou plusieurs objets, chaque objet ntant pas forcment unique. Un objet peut donc tre plac plusieurs fois dans le sac. Un sac dispose de deux fonctions permettant dy mettre et den retirer un objet. Il a aussi une fonction permettant de dire si un objet se trouve dans le sac. Nous allons dclarer une classe abstraite qui servira de classe de base pour tous les objets utilisables. Le sac ne manipulera que des pointeurs sur la classe abstraite, ce qui permettra son utilisation pour toute classe drivant de cette classe. An de diffrencier deux objets gaux, un numro unique devra tre attribu chaque objet manipul. Le choix de ce numro est la charge des objets, la classe abstraite dont ils drivent devra donc avoir une mthode renvoyant ce numro. Les objets devront tous pouvoir tre afchs dans un format qui leur est propre. La fonction utiliser pour cela sera print. Cette fonction sera une mthode virtuelle pure de la classe abstraite, puisquelle devra tre dnie pour chaque objet. Passons maintenant au programme...

157

Chapitre 7. C++ : la couche objet Exemple 7-26. Conteneur dobjets polymorphiques


#include <iostream> using namespace std; /************* LA CLASSE ABSTRAITE DE BASE *****************/

class Object { unsigned long int new_handle(void); protected: unsigned long int h;

// Identifiant de lobjet.

public: Object(void); // Le constructeur. virtual ~Object(void); // Le destructeur virtuel. virtual void print(void) =0; // Fonction virtuelle pure. unsigned long int handle(void) const; // Fonction renvoyant // le numro didentification // de lobjet. }; // Cette fonction nest appelable que par la classe Object : unsigned long int Object::new_handle(void) { static unsigned long int hc = 0; return hc = hc + 1; // hc est lidentifiant courant. // Il est incrment } // chaque appel de new_handle. // Le constructeur de Object doit tre appel par les classes drives : Object::Object(void) { h = new_handle(); return; } Object::~Object(void) { return ; } unsigned long int Object::handle(void) const { return h; // Renvoie le numro de lobjet. } /******************** LA CLASSE SAC class Bag : public Object // // // // ******************/ Elle hrite un sac peut autre. Le sac sous la forme

// Trouve un nouvel identifiant.

La classe sac. de Object, car en contenir un est implment

158

Chapitre 7. C++ : la couche objet


// dune liste chane. { struct BagList { BagList *next; Object *ptr; }; BagList *head; public: Bag(void); ~Bag(void); void print(void); bool has(unsigned // La tte de liste.

// Le constructeur : appel celui de Object. // Le destructeur. // Fonction daffichage du sac. long int) const; // true si le sac contient lobjet. bool is_empty(void) const; // true si le sac est vide. void add(Object &); // Ajoute un objet. void remove(Object &); // Retire un objet.

}; Bag::Bag(void) : Object() { return; // Ne fait rien dautre quappeler Object::Object(). } Bag::~Bag(void) { BagList *tmp = head; while (tmp != NULL) { tmp = tmp->next; delete head; head = tmp; } return; }

// Dtruit la liste dobjet.

void Bag::print(void) { BagList *tmp = head; cout << "Sac n " << handle() << "." << endl; cout << " Contenu :" << endl; while (tmp != NULL) { cout << "\t"; tmp->ptr->print(); tmp = tmp->next; } return; } bool Bag::has(unsigned long int h) const { BagList *tmp = head; while (tmp != NULL && tmp->ptr->handle() != h)

// Indente la sortie des objets. // Affiche la liste objets.

159

Chapitre 7. C++ : la couche objet


tmp = tmp->next; return (tmp != NULL); } bool Bag::is_empty(void) const { return (head==NULL); } void Bag::add(Object &o) { BagList *tmp = new BagList; tmp->ptr = &o; tmp->next = head; head = tmp; return; } // Cherche lobjet.

// Ajoute un objet la liste.

void Bag::remove(Object &o) { BagList *tmp1 = head, *tmp2 = NULL; while (tmp1 != NULL && tmp1->ptr->handle() != o.handle()) { tmp2 = tmp1; // Cherche lobjet... tmp1 = tmp1->next; } if (tmp1!=NULL) // et le supprime de la liste. { if (tmp2!=NULL) tmp2->next = tmp1->next; else head = tmp1->next; delete tmp1; } return; }

Avec la classe Bag dnie telle quelle, il est prsent possible de stocker des objets drivant de la classe Object avec les fonctions add et remove :
#include <stdlib.h> class MonObjet : public Object { /* Dfinir la mthode print() pour lobjet... }; Bag MonSac; int main(void) { MonObjet a, b, c; MonSac.add(a); MonSac.add(b); MonSac.add(c); MonSac.print(); MonSac.remove(b);

*/

// Effectue quelques oprations // avec le sac :

160

Chapitre 7. C++ : la couche objet


MonSac.add(MonSac); MonSac.print(); return EXIT_SUCCESS; } // Un sac peut contenir un sac ! // Attention ! Cet appel est rcursif ! // (plantage assur).

Nous avons vu que la classe de base servait de moule aux classes drives. Le droit dempcher une fonction membre virtuelle pure dnie dans une classe drive daccder en criture non seulement aux donnes de la classe de base, mais aussi aux donnes de la classe drive, peut donc faire partie de ses prrogatives. Cela est faisable en dclarant le pointeur this comme tant un pointeur constant sur objet constant. Nous avons vu que cela pouvait se faire en rajoutant le mot cl const aprs la dclaration de la fonction membre. Par exemple, comme lidentiant de lobjet de base est plac en protected au lieu dtre en private, la classe Object autorise ses classes drives le modier. Cependant, elle peut empcher la fonction print de le modier en la dclarant const :
class Object { unsigned long int new_handle(void); protected: unsigned long int h; public: Object(void); // virtual void print(void) const=0; // unsigned long int handle(void) const; // // };

Le constructeur. Fonction virtuelle pure. // Fonction renvoyant le numro didentification de lobjet.

Dans lexemple donn ci-dessus, la fonction print peut accder en lecture h, mais plus en criture. En revanche, les autres fonctions membres des classes drives peuvent y avoir accs, puisque cest une donne membre protected. Cette mthode dencapsulation est donc cooprative (elle requiert la bonne volont des autres fonctions membres des classes drives), tout comme la mthode qui consistait en C dclarer une variable constante. Cependant, elle permettra de dtecter des anomalies la compilation, car si une fonction print cherche modier lobjet sur lequel elle travaille, il y a manifestement une erreur de conception. Bien entendu, cela fonctionne galement avec les fonctions membres virtuelles non pures, et mme avec les fonctions non virtuelles.

7.16. Pointeurs sur les membres dune classe


Nous avons dj vu les pointeurs sur les objets. Il nous reste voir les pointeurs sur les membres des classes. Les classes regroupent les caractristiques des donnes et des fonctions des objets. Les membres des classes ne peuvent donc pas tre manipuls sans passer par la classe laquelle ils appartiennent. Par consquent, il faut, lorsquon veut faire un pointeur sur un membre, indiquer le nom de sa classe. Pour cela, la syntaxe suivante est utilise :

161

Chapitre 7. C++ : la couche objet


dfinition classe::* pointeur

Par exemple, si une classe test contient des entiers, le type de pointeurs utiliser pour stocker leur adresse est :
int test::*

Si on veut dclarer un pointeur p de ce type, on crira donc :


int test::*p1; // Construit le pointeur sur entier // de la classe test.

Une fois le pointeur dclar, on pourra linitialiser en prenant ladresse du membre de la classe du type correspondant. Pour cela, il faudra encore spcier le nom de la classe avec loprateur de rsolution de porte :
p1 = &test::i; // Rcupre ladresse de i.

La mme syntaxe est utilisable pour les fonctions. Lemploi dun typedef est dans ce cas fortement recommand. Par exemple, si la classe test dispose dune fonction membre appele lit, qui nattend aucun paramtre et qui renvoie un entier, on pourra rcuprer son adresse ainsi :
typedef int (test::* pf)(void); pf p2=&test::lit; // Dfinit le type de pointeur. // Construit le pointeur et // lit ladresse de la fonction.

Cependant, ces pointeurs ne sont pas utilisables directement. En effet, les donnes dune classe sont instancies pour chaque objet, et les fonctions membres reoivent systmatiquement le pointeur this sur lobjet de manire implicite. On ne peut donc pas faire un drfrencement direct de ces pointeurs. Il faut spcier lobjet pour lequel le pointeur va tre utilis. Cela se fait avec la syntaxe suivante :
objet.*pointeur

Pour les pointeurs dobjet, on pourra utiliser loprateur ->* la place de loprateur .* (appel pointeur sur oprateur de slection de membre). Ainsi, si a est un objet de classe test, on pourra accder la donne i de cet objet travers le pointeur p1 avec la syntaxe suivante :
a.*p1 = 3; // Initialise la donne membre i de a avec la valeur 3.

Pour les fonctions membres, on mettra des parenthses cause des priorits des oprateurs :
int i = (a.*p2)(); // Appelle la fonction lit() pour lobjet a.

162

Chapitre 7. C++ : la couche objet

Pour les donnes et les fonctions membres statiques, cependant, la syntaxe est diffrente. En effet, les donnes nappartiennent plus aux objets de la classe, mais la classe elle-mme, et il nest plus ncessaire de connatre lobjet auquel le pointeur sapplique pour les utiliser. De mme, les fonctions membres statiques ne reoivent pas le pointeur sur lobjet, et on peut donc les appeler sans rfrencer ce dernier. La syntaxe sen trouve donc modie. Les pointeurs sur les membres statiques des classes sont compatibles avec les pointeurs sur les objets et les fonctions non-membres. Par consquent, si une classe contient une donne statique entire, on pourra rcuprer son adresse directement et la mettre dans un pointeur dentier :
int *p3 = &test::entier_statique; // Rcupre ladresse // de la donne membre // statique.

La mme syntaxe sappliquera pour les fonctions :


typedef int (*pg)(void); pg p4 = &test::fonction_statique;

// Rcupre ladresse // dune fonction membre // statique.

Enn, lutilisation des ces pointeurs est identique celle des pointeurs classiques, puisquil nest pas ncessaire de fournir le pointeur this. Il est donc impossible de spcier le pointeur sur lobjet sur lequel la fonction doit travailler aux fonctions membres statiques. Cela est naturel, puisque les fonctions membres statiques ne peuvent pas accder aux donnes non statiques dune classe. Exemple 7-27. Pointeurs sur membres statiques
#include <stdlib.h> #include <iostream> using namespace std; class test { int i; static int j; public: test(int j) { i=j; return ; } static int get(void) { /* return i ; INTERDIT : i est non statique et get lest ! */ return j; // Autoris. }

163

Chapitre 7. C++ : la couche objet


}; int test::j=5; typedef int (*pf)(void); pf p=&test::get; // Initialise la variable statique. // // // // Pointeur de fonction renvoyant un entier. Initialisation licite, car get est statique.

int main(void) { cout << (*p)() << endl;// Affiche 5. On ne spcifie pas lobjet. return EXIT_SUCCESS; }

164

Chapitre 8. Les exceptions en C++


Une exception est linterruption de lexcution du programme la suite dun vnement particulier. Le but des exceptions est de raliser des traitements spciques aux vnements qui en sont la cause. Ces traitements peuvent rtablir le programme dans son mode de fonctionnement normal, auquel cas son excution reprend. Il se peut aussi que le programme se termine, si aucun traitement nest appropri. Le C++ supporte les exceptions logicielles, dont le but est de grer les erreurs qui surviennent lors de lexcution des programmes. Lorsquune telle erreur survient, le programme doit lancer une exception. Lexcution normale du programme sarrte ds que lexception est lance, et le contrle est pass un gestionnaire dexception. Lorsquun gestionnaire dexception sexcute, on dit quil a attrap lexception. Comme nous allons le voir, les exceptions permettent une gestion simplie des erreurs, parce quelles en reportent le traitement en dehors de la squence nominale de lalgorithme, ce qui le simplie grandement. De plus, elles permettent de rgler les problmes de libration des ressources alloues avant lapparition de lerreur de manire automatique, ce qui simplie dautant le code de traitement des erreurs. Enn, les exceptions permettent de spcier avec prcision les erreurs possibles quune mthode peut gnrer, et de classier les erreurs en catgories derreurs sur lesquelles des traitements gnriques peuvent tre effectues.

8.1. Techniques de gestion des erreurs


En gnral, une fonction qui dtecte une erreur dexcution ne peut pas se terminer normalement. Comme son traitement na pas pu se drouler normalement, il est probable que la fonction qui la appele considre elle aussi quune erreur a eu lieu et termine son excution. Lerreur remonte ainsi la liste des appelants de la fonction qui a gnr lerreur. Ce processus continue, de fonction en fonction, jusqu ce que lerreur soit compltement gre ou jusqu ce que le programme se termine (ce cas survient lorsque la fonction principale ne peut pas grer lerreur). Traditionnellement, ce mcanisme est implment laide de codes de retour des fonctions. Chaque fonction doit renvoyer une valeur spcique lissue de son excution, permettant dindiquer si elle sest correctement droule ou non. La valeur renvoye est donc utilise par lappelant pour dterminer si lappel sest bien effectu ou non, et, si erreur il y a, prendre les mesures ncessaires. La nature de lerreur peut tre indique soit directement par la valeur retourne par la fonction, soit par une donne globale que lappelant peut consulter. Exemple 8-1. Gestion des erreurs par codes derreur
// Fonction qui rserve trois ressources et effectue // un travail avec ces trois ressources. // Retourne 0 si tout sest bien pass, // -1 si la premire ressource ne peut tre prise, // -2 si la deuxime ressource ne peut tre prise, // -3 si la troisime ressource ne peut tre prie, // et -1001 -1004 selon le code derreur du travail. // Les ressources sont consommes par le travail // si celui-ci russit. int fait_boulot(const char *rsrc1, const char *rsrc2, const char *rsrc3) { // Initilise le code de rsultat la premire // erreur possible :

165

Chapitre 8. Les exceptions en C++


int res = -1; // Alloue la premire ressource : int r1 = alloue_ressource(rsrc1); if (r1 != 0) { // Idem avec la deuxime : res = -2; int r2 = alloue_ressource(rsrc2); if (r2 != 0) { // Idem avec la troisime : res = -3; int r3 = alloue_ressource(rsrc3); if (r3 != 0) { // OK, on essaie : int trv = consomme(r1, r2, r3); switch (trv) { case 0: res = 0; break; case 1: res = -1001; break; case 2: res = -1002; break; case 4: res = -1003; break; case 25: res = -1004; break; } // Il faut librer en cas dchec : if (res != 0) libere_ressource(r3); } // Libre r2 : if (res != 0) libere_ressource(r2); } if (res != 0) libere_ressource(r1); } return res; }

Malheureusement, comme cet exemple le montre, cette technique ncessite de tester les codes de retour de chaque fonction appele. La logique derreur dveloppe nit par devenir trs lourde, puisque ces tests simbriquent les uns la suite des autres et que le code du traitement des erreurs se trouve mlang avec le code du fonctionnement normal de lalgorithme. Cette complication peut devenir ingrable lorsque plusieurs valeurs de codes de retour peuvent tre renvoyes an de distinguer les diffrents cas derreurs possibles, car il peut en dcouler un grand nombre de tests et beaucoup de cas particuliers grer dans les fonctions appelantes.

166

Chapitre 8. Les exceptions en C++ Certains programmes rsolvent le problme de limbrication des tests dune manire astucieuse, qui consiste dporter le traitement des erreurs effectuer en dehors de lalgorithme par des sauts vers la n de la fonction. Le code de nettoyage, qui se trouve alors aprs lalgorithme, est excut compltement si tout se passe correctement. En revanche, si la moindre erreur est dtecte en cours dexcution, un saut est ralis vers la partie du code de nettoyage correspondante au traitement qui a dj t effectu. Ainsi, ce code nest crit quune seule fois, et le traitement des erreurs est situ en dehors du traitement normal. Exemple 8-2. Gestion des erreurs par sauts
int fait_boulot(const char *rsrc1, const char *rsrc2, const char *rsrc3) { int res; // Alloue la premire ressource : res = -1; int r1 = alloue_ressource(rsrc1); if (r1 == 0) goto err_alloc_r1; // Alloue la deuxime ressource : res = -2; int r2 = alloue_ressource(rsrc2); if (r2 == 0) goto err_alloc_r2; // Alloue la troisime ressource : res = -3; int r3 = alloue_ressource(rsrc3); if (r3 == 0) goto err_alloc_r3; // Effectue le boulot : int trv = consomme(r1, r2, r3); switch (trv) { case 0: res = 0; goto fin_ok; case 1: res = -1001; break; case 2: res = -1002; break; case 4: res = -1003; break; case 25: res = -1004; break; } err_boulot: libere_ressource(r3); err_alloc_r3: libere_ressource(r2); err_alloc_r2: libere_ressource(r1);

167

Chapitre 8. Les exceptions en C++


err_alloc_r1: fin_ok: return res; }

La solution prcdente est tout fait valable (en fait, cest mme la solution la plus simple), et elle est trs utilise dans les programmes C bien raliss. Cependant, elle nest toujours pas parfaite. En effet, il faut toujours conserver linformation de lerreur dans le code derreur si celle-ci doit tre remonte aux fonctions appelantes. Or, des problmes fondamentaux sont attachs la notion de code derreur. Premirement, si lon veut dnir des catgories derreurs (par exemple pour les erreurs dentre/sortie, les erreurs de droits daccs, et les erreurs de manque de mmoire), il est ncessaire dutiliser des plages de valeurs distinctes pour chaque catgorie dans le type utilis pour les codes derreur. Cela ncessite une autorit centrale de dnition des codes derreurs, faute de quoi des conits de valeurs pour les code, et donc des interprtations erronnes des erreurs, se produiront. Et cela, croyez moi, ce nest pas facile du tout grer (ce nest dailleurs plus un problme de programmation). Deuximement, il nest a priori pas possible de dterminer tous les codes de retour possibles dune fonction sans consulter sa documentation ou son code source. Ce problme impose de dnir des traitements gnriques, alors quils ne sont a posteriori pas ncessaires. Comme nous allons le voir, la solution qui met en uvre les exceptions est une alternative intressante. En effet, la fonction qui dtecte une erreur peut se contenter de lancer une exception en lui fournissant le contexte de lerreur. Cette exception interrompt lexcution de la fonction, et un gestionnaire dexception appropri est recherch. La recherche du gestionnaire suit le mme chemin que celui utilis lors de la remonte classique des erreurs : savoir la liste des appelants. Le premier bloc dinstructions qui contient un gestionnaire dexception capable de traiter cette exception prend donc le contrle, et effectue le traitement de lerreur. Si le traitement est complet, le programme reprend son excution normale. Sinon, le gestionnaire dexception peut relancer lexception (auquel cas le gestionnaire dexception suivant pour ce type dexception est recherch) ou terminer le programme. Les gestionnaires dexceptions capables de traiter une exception sont identis par les mcanismes de typage du langage. Ainsi, plusieurs types dexceptions, a priori dnis de manire indpendants, peuvent tre utiliss. De plus, le polymorphisme des exceptions permet de les structurer facilement an de prendre en charge des catgories derreurs. Enn, toutes les informations relatives lerreur, et notamment son contexte, sont transmises aux gestionnaires dexceptions automatiquement par le mcanisme des exceptions du langage. Ainsi, il ny a plus de risque de conit lors de la dnition de codes derreurs, ni de donnes globales utilises pour stocker les informations descriptives de lerreur. Pour nir, chaque fonction peut spcier les exceptions quelle peut lancer, ce qui permet lappelant de savoir quoi sattendre. Les exceptions permettent donc de simplier le code et de le rendre plus able. Par ailleurs, la logique derreur est compltement prise en charge par le langage.
Note : Lutilisation des exceptions nest pour autant pas forcment la meilleure des choses dans un programme. En effet, si un programme utilise du code qui gre les exceptions et du code qui ne gre pas les exceptions (par exemple dans deux bibliothques tierces), il aura la lourde tche soit dencapsuler le code qui ne gre pas les exceptions pour pouvoir lutiliser correctement, soit de prendre en charge les deux types de traitements derreurs directement. La premire solution est trs lourde et coteuse, et est difcilement justiable par des considrations de facilit de traitement des erreurs. La deuxime solution est encore pire, le code devenant absolument horrible. Dans ce genre de situations, mieux vaut sadapter et utiliser le mcanisme majoritaire. Trs souvent hlas, il faut abandonner les exceptions et utiliser les notions de codes derreur.

168

Chapitre 8. Les exceptions en C++ Nous allons prsent voir comment utiliser les exceptions en C++.

8.2. Lancement et rcupration dune exception


En C++, lorsquil faut lancer une exception, on doit crer un objet dont la classe caractrise cette exception, et utiliser le mot cl throw. Sa syntaxe est la suivante :
throw objet;

o objet est lobjet correspondant lexception. Cet objet peut tre de nimporte quel type, et pourra ainsi caractriser pleinement lexception. Les exceptions doivent alors tre traites par un gestionnaire dexception qui leur correspond. Pour cela, il faut dlimiter chaque zone de code susceptible de lancer des exceptions. Cela se fait en plaant le code protger dans un bloc dinstructions particulier. Ce bloc est introduit avec le mot cl try :
try { // Code susceptible de gnrer des exceptions... }

Les gestionnaires dexceptions doivent suivre le bloc try. Ils sont introduits avec le mot cl catch :
catch (classe [&][temp]) { // Traitement de lexception associe la classe }

Ds quune exception est lance, le compilateur recherche un gestionnaire dexception appropri en remontant les blocs dinstructions et la pile dappel des fonctions. chaque tape, les objets de classe de stockage automatique dnis dans les blocs dont la remonte de lexception en fait sortir le contrle du programme sont bien entendu automatiquement dtruits. De ce fait, si lensemble des ressources utilises par le programme est encapsul dans des classes dont les destructeurs sont capables de les dtruire ou de les ramener dans un tat cohrent, la gestion des ressources devient totalement automatique pendant les traitements derreurs. Les traitements que lon doit effectuer dans les blocs catch sont les traitements derreurs que le C++ ne fera pas automatiquement. Ces traitements comprennent gnralement le rtablissement de ltat des donnes manipules par le code qui a lanc lexception (dont, pour les fonctions membres dune classe, les donnes membres de lobjet courant), ainsi que la libration des ressources non encapsules dans des objets de classe de stockage automatique (par exemple, les chiers ouverts, les connexions rseau, etc.). Un gestionnaire dexception peut relancer lexception sil le dsire, par exemple pour permettre aux gestionnaires de niveau suprieur de faire la suite du traitement derreur. Pour cela, il suft dutiliser le mot cl throw. La syntaxe est la suivante :
throw ;

Lexception est alors relance, et un nouveau bloc catch est recherch avec les mmes paramtres. Le parcours de lexception sarrtera donc ds que lerreur aura t compltement traite.

169

Chapitre 8. Les exceptions en C++


Note : Bien entendu, il est possible de lancer une autre exception que celle que lon a reue, comme ce peut tre par exemple le cas si le traitement de lerreur provoque lui-mme une erreur.

Il peut y avoir plusieurs gestionnaires dexceptions. Chacun traitera les exceptions qui ont t gnres dans le bloc try et dont lobjet est de la classe indique par son paramtre. Il nest pas ncessaire de donner un nom lobjet (temp) dans lexpression catch. Cependant, cela permet de le rcuprer, ce qui peut tre ncessaire si lon doit rcuprer des informations sur la nature de lerreur. Enn, il est possible de dnir un gestionnaire dexception universel, qui rcuprera toutes les exceptions possibles, quels que soient leurs types. Ce gestionnaire dexception doit prendre comme paramtre trois points de suspension entre parenthses dans sa clause catch. Bien entendu, dans ce cas, il est impossible de spcier une variable qui contient lexception, puisque son type est indni. Exemple 8-3. Utilisation des exceptions
#include <stdlib.h> #include <iostream> using namespace std; class erreur // Premire exception possible, associe // lobjet erreur.

{ public: int cause; // Entier spcifiant la cause de lexception. // Le constructeur. Il appelle le constructeur de cause. erreur(int c) : cause(c) {} // Le constructeur de copie. Il est utilis par le mcanisme // des exceptions : erreur(const erreur &source) : cause(source.cause) {} }; class other {}; // Objet correspondant toutes // les autres exceptions.

int main(void) { int i; // Type de lexception gnrer. cout << "Tapez 0 pour gnrer une exception Erreur, " "1 pour une Entire :"; cin >> i; // On va gnrer une des trois exceptions // possibles. cout << endl; try // Bloc o les exceptions sont prises en charge. { switch (i) // Selon le type dexception dsire, { case 0: { erreur a(0); throw (a); // on lance lobjet correspondant // (ici, de classe erreur). // Cela interrompt le code. break est // donc inutile ici. } case 1:

170

Chapitre 8. Les exceptions en C++


{ // Exception de type entier. } default: // Si lutilisateur na pas tap 0 ou 1, { other c; // on cre lobjet c (type dexception throw (c); // other) et on le lance. } } } // fin du bloc try. Les blocs catch suivent : catch (erreur &tmp) // Traitement de lexception erreur ... { // (avec rcupration de la cause). cout << "Erreur erreur ! (cause " << tmp.cause << ")" << endl; } catch (int tmp) // Traitement de lexception int... { cout << "Erreur int ! (cause " << tmp << ")" << endl; } catch (...) // Traitement de toutes les autres { // exceptions (...). // On ne peut pas rcuprer lobjet ici. cout << "Exception inattendue !" << endl; } return EXIT_SUCCESS; } int a=1; throw (a);

Selon ce quentre lutilisateur, une exception du type erreur, int ou other est gnre.

8.3. Hirarchie des exceptions


Le mcanisme des exceptions du C++ se base sur le typage des objets, puisque le lancement dune exception ncessite la construction dun objet qui la caractrise, et le bloc catch destination de cette exception sera slectionn en fonction du type de cet objet. Bien entendu, les objets utiliss pour lancer les exceptions peuvent contenir des informations concernant la nature des erreurs qui se produisent, mais il est galement possible de classier ces erreurs par catgories en se basant sur leurs types. En effet, les objets exceptions peuvent tre des instances de classes disposant de relations dhritage. Comme les objets des classes drives peuvent tre considrs comme des instances de leurs classes de base, les gestionnaires dexception peuvent rcuprer les exceptions de ces classes drives en rcuprant un objet du type dune de leurs classes de base. Ainsi, il est possible de classier les diffrents cas derreurs en dnissant une hirarchie de classe dexceptions, et dcrire des traitements gnriques en nutilisant que les objets dun certain niveau dans cette hirarchie. Le mcanisme des exceptions se montre donc plus puissant que toutes les autres mthodes de traitement derreurs ce niveau, puisque la slection du gestionnaire derreur est automatiquement ralise par le langage. Cela peut tre trs pratique pour peu que lon ait dni correctement sa hirarchie de classes dexceptions. Exemple 8-4. Classication des exceptions
#include <stdlib.h> #include <iostream> using namespace std;

171

Chapitre 8. Les exceptions en C++

// Classe de base de toutes les exceptions : class ExRuntimeError { }; // Classe de base des exceptions pouvant se produire // lors de manipulations de fichiers : class ExFileError : public ExRuntimeError { }; // Classes des erreurs de manipulation des fichiers : class ExInvalidName : public ExFileError { }; class ExEndOfFile : public ExFileError { }; class ExNoSpace : public ExFileError { }; class ExMediumFull : public ExNoSpace { }; class ExFileSizeMaxLimit : public ExNoSpace { }; // Fonction faisant un travail quelconque sur un fichier : void WriteData(const char *szFileName) { // Exemple derreur : if (szFileName == NULL) throw ExInvalidName(); else { // Traitement de la fonction // etc. // Lancement dune exception : throw ExMediumFull(); } } void Save(const char *szFileName) { try { WriteData(szFileName); } // Traitement dun erreur spcifique : catch (ExInvalidName &) {

172

Chapitre 8. Les exceptions en C++


cout << "Impossible de faire la sauvegarde" << endl; } // Traitement de toutes les autres erreurs en groupe : catch (ExFileError &) { cout << "Erreur dentre / sortie" << endl; } } int main(void) { Save(NULL); Save("data.dat"); return EXIT_SUCCESS; }

La bibliothque standard C++ dnit elle-mme un certain nombre dexceptions standards, qui sont utilises pour signaler les erreurs qui se produisent lexcution des programmes. Quelques-unes de ces exceptions ont dj t prsentes avec les fonctionnalits qui sont susceptibles de les lancer. Vous trouverez une liste complte des exceptions de la bibliothque standard du C++ dans la Section 13.2.

8.4. Traitement des exceptions non captes


Si, lorsquune exception se produit dans un bloc try, il est impossible de trouver le bloc catch correspondant la classe de cette exception, il se produit une erreur dexcution. La fonction prdnie std::terminate est alors appele. Elle se contente dappeler une fonction de traitement de lerreur, qui elle-mme appelle la fonction abort de la bibliothque C. Cette fonction termine en catastrophe lexcution du programme fautif en gnrant une faute (les ressources alloues par le programme ne sont donc pas libres, et des donnes peuvent tre perdues). Ce nest gnralement pas le comportement dsir, aussi est-il possible de le modier en changeant la fonction appele par std::terminate. Pour cela, il faut utiliser la fonction std::set_terminate, qui attend en paramtre un pointeur sur la fonction de traitement derreur, qui ne prend aucun paramtre et renvoie void. La valeur renvoye par std::set_terminate est le pointeur sur la fonction de traitement derreur prcdente. std::terminate et std::set_terminate sont dclaree dans le chier den-tte exception.
Note : Comme leurs noms lindiquent, std::terminate et std::set_terminate sont dclares dans lespace de nommage std::, qui est rserv pour tous les objets de la bibliothque standard C++. Si vous ne voulez pas avoir utiliser systmatiquement le prxe std:: devant ces noms, vous devrez ajouter la ligne using namespace std; aprs avoir inclus len-tte exception. Vous obtiendrez de plus amples renseignements sur les espaces de nommage dans le Chapitre 10.

Exemple 8-5. Installation dun gestionnaire dexception avec set_terminate


#include <stdlib.h> #include <iostream> #include <exception> using namespace std; void mon_gestionnaire(void)

173

Chapitre 8. Les exceptions en C++


{ cout << "Exception non gre reue !" << endl; cout << "Je termine le programme proprement..." << endl; exit(-1); } int lance_exception(void) { throw 2; } int main(void) { set_terminate(&mon_gestionnaire); try { lance_exception(); } catch (double d) { cout << "Exception de type double reue : " << d << endl; } return EXIT_SUCCESS; }

8.5. Liste des exceptions autorises pour une fonction


Il est possible de spcier les exceptions qui peuvent tre lances par une fonction. Pour cela, il faut faire suivre son en-tte du mot cl throw avec, entre parenthses et spares par des virgules, les classes des exceptions quelle est autorise lancer. Par exemple, la fonction suivante :
int fonction_sensible(void) throw (int, double, erreur) { ... }

na le droit de lancer que des exceptions du type int, double ou erreur. Si une exception dun autre type est lance, par exemple une exception du type char *, il se produit encore une fois une erreur lexcution. Dans ce cas, la fonction std::unexpected est appele. Cette fonction se comporte de manire similaire std::terminate, puisquelle appelle par dfaut une fonction de traitement de lerreur qui elle-mme appelle la fonction std::terminate (et donc abort en n de compte). Cela conduit la terminaison du programme. On peut encore une fois changer ce comportement par dfaut en remplaant la fonction appele par std::unexpected par une autre fonction laide de std::set_unexpected, qui est dclare dans le chier den-tte exception. Cette dernire attend en paramtre un pointeur sur la fonction de traitement derreur, qui ne doit prendre aucun paramtre et qui ne doit rien renvoyer. std::set_unexpected renvoie le pointeur sur la fonction de traitement derreur prcdemment appele par std::unexpected.

174

Chapitre 8. Les exceptions en C++


Note : Comme leurs noms lindiquent, std::unexpected et std::set_unexpected sont dclares dans lespace de nommage std::, qui est rserv pour les objets de la bibliothque standard C++. Si vous ne voulez pas avoir utiliser systmatiquement le prxe std:: pour ces noms, vous devrez ajouter la ligne using namespace std; aprs avoir inclus len-tte exception. Vous obtiendrez de plus amples renseignements sur les espaces de nommage dans le Chapitre 10.

Il est possible de relancer une autre exception lintrieur de la fonction de traitement derreur. Si cette exception satisfait la liste des exceptions autorises, le programme reprend son cours normalement dans le gestionnaire correspondant. Cest gnralement ce que lon cherche faire. Le gestionnaire peut galement lancer une exception de type std::bad_exception, dclare comme suit dans le chier den-tte exception :
class bad_exception : public exception { public: bad_exception(void) throw(); bad_exception(const bad_exception &) throw(); bad_exception &operator=(const bad_exception &) throw(); virtual ~bad_exception(void) throw(); virtual const char *what(void) const throw(); };

Cela a pour consquence de terminer le programme. Enn, le gestionnaire dexceptions non autorises peut directement mettre n lexcution du programme en appelant std::terminate. Cest le comportement utilis par la fonction std::unexpected dnie par dfaut. Exemple 8-6. Gestion de la liste des exceptions autorises
#include <stdlib.h> #include <iostream> #include <exception> using namespace std; void mon_gestionnaire(void) { cout << "Une exception illgale a t lance." << endl; cout << "Je relance une exception de type int." << endl; throw 2; } int f(void) throw (int) { throw "5.35"; } int main(void) { set_unexpected(&mon_gestionnaire); try { f(); }

175

Chapitre 8. Les exceptions en C++


catch (int i) { cout << "Exception de type int reue : " << i << endl; } return EXIT_SUCCESS; }

Note : La liste des exceptions autorises dans une fonction ne fait pas partie de sa signature. Elle nintervient donc pas dans les mcanismes de surcharge des fonctions. De plus, elle doit se placer aprs le mot cl const dans les dclarations de fonctions membres const (en revanche, elle doit se placer avant =0 dans les dclarations des fonctions virtuelles pures). On prendra garde au fait que les exceptions ne sont pas gnres par le mcanisme de gestion des erreurs du C++ (ni du C). Cela signie que pour avoir une exception, il faut la lancer, le compilateur ne fera pas les tests pour vous (tests de dbordements numriques dans les calculs par exemple). Cela supposerait de prdnir un ensemble de classes pour les erreurs gnriques. Les tests de validit dune opration doivent donc tre faits malgr tout et, le cas chant, il faut lancer une exception pour reporter le traitement en cas dchec. De mme, les exceptions gnres par la machine hte du programme ne sont en gnral pas rcupres par les implmentations et, si elles le sont, les programmes qui les utilisent ne sont pas portables.

8.6. Gestion des objets exception


Lors de la lance dune exception, lobjet exception fourni en paramtre throw peut tre copi dans une variable temporaire prise en charge par le compilateur ou non. Dans ce dernier cas, lobjet exception est construit directement dans cette variable temporaire, et utilis directement pour propager lexception. Le comportement exact nest pas x par la norme, et relve dune optimisation.
Note : Cela implique que les programmes portables doivent absolument viter les effets de bord dans le traitement des constructeurs de copie et des destructeurs des classes utilises pour les exceptions.

Lobjet temporaire utilis par le compilateur pour propager lexception est utilis pour initialiser les blocs catch qui la traitent. Lutilisation de cet objet par les blocs catch se fait exactement de la mme manire que pour le passage dune variable en paramtre une fonction. Les blocs catch peuvent donc recevoir leurs paramtres par valeur ou par rfrence. Lorsque lobjet exception est reu par valeur, une copie supplmentaire est ralise. Lorsquil est reu par rfrence, cette copie est vite, mais toutes les modications effectues sur lobjet exception seront effectues dans la copie de travail du compilateur, et seront donc galement visibles dans les blocs catch des fonctions appelantes ou de porte suprieure si lexception est relance aprs traitement. An dviter les copies dobjets inutiles, il est recommand de capter les exceptions par rfrence. On pourra utiliser le mot cl const pour viter les effets de bords si ceux-ci ne sont pas dsirs. Lobjet temporaire utilis par le compilateur pour propager lexception est dtruit automatiquement une fois que le traitement dexception est termin. Pendant tout le traitement de propagation dune exception, y compris pendant la destruction automatique des objets dont la porte est quitte, la fonction globale std::uncaught_exception renvoie

176

Chapitre 8. Les exceptions en C++


true. Cette fonction est dclare dans le chier den-tte exception comme une fonction retournant un boolen et ne prenant aucun paramtre. Cette fonction renvoie false ds que lexception est attrape par lune des clauses catch, ou ds quil ny a plus de traitement dexception en cours.

8.7. Exceptions dans les constructeurs et les destructeurs


Il est parfaitement lgal de lancer une exception dans un constructeur. En fait, cest mme la seule solution pour signaler une erreur lors de la construction dun objet, puisque les constructeurs nont pas de valeur de retour. Lorsquune exception est lance partir dun constructeur, la construction de lobjet choue. Par consquent, le compilateur nappellera jamais le destructeur pour cet objet, puisque cela na pas de sens. En revanche, les donnes membres et les classes de bases dj contruites sont automatiquement dtruites avant de remonter lexception. De ce fait, toutes les ressources que le constructeur a commenc rserver sont libres automatiquement, pourvu quelles soient encapsules dans des classes disposant de destructeurs. Pour les autres ressources (ressources systmes ou pointeurs contenant des donnes alloues dynamiquement), aucun code de destruction nest excut. Cela peut conduire des fuites de mmoire ou une consommation de ressource qui ne pourront plus tre libres. De ce fait, il est conseill de ne jamais faire de traitements complexes ou susceptibles de lancer une exception dans un constructeur, et de dnir une fonction dinitialisation que lon appelera dans un contexte plus able. Si cela nest pas possible, il est impratif dencapsuler toutes les ressources que le constructeur rservera dans des classes disposant dun destructeur an de librer correctement la mmoire. De mme, lorsque la construction de lobjet se fait dans le cadre dune allocation dynamique de mmoire, le compilateur appelle automatiquement loprateur delete an de restituer la mmoire alloue pour cet objet. Il est donc inutile de restituer la mmoire de lobjet allou dans le traitement de lexception qui suit la cration dynamique de lobjet, et il ne faut pas y appeler loprateur delete manuellement.
Note : Le compilateur dtruit les donnes membres et les classes de base qui ont t construites avant le lancement de lexception avant dappeler loprateur delete pour librer la mmoire. Dordinaire, cest cet oprateur qui appelle le destructeur de lobjet dtruire, mais il ne le fait pas une deuxime fois. Le comportement de loprateur delete est donc lui aussi lgrement modi par le mcanisme des exceptions lorsquil sen produit une pendant la construction dun objet.

Il est possible de capter les exceptions qui se produisent pendant lexcution du constructeur dune classe, an de faire un traitement sur cette exception, ou ventuellement an de la traduire dans une autre exception. Pour cela, le C++ fournit une syntaxe particulire. Cette syntaxe permet simplement dutiliser un bloc try pour le corps de fonction des constructeurs. Les blocs catch suivent alors la dnition du constructeur, et effectuent les traitements sur lexception. Exemple 8-7. Exceptions dans les constructeurs
#include <stdlib.h> #include <iostream> using namespace std;

177

Chapitre 8. Les exceptions en C++


class A { struct Buffer { char *p; Buffer() : p(0) { } ~Buffer() { if (p != 0) delete[] p; } }; Buffer pBuffer; int *pData; public: A() throw (double); ~A(); static void *operator new(size_t taille) { cout << "new" << endl; return malloc(taille); } static void operator delete(void *p) { cout << "delete" << endl; free(p); } }; // Constructeur susceptible de lancer une exception : A::A() throw (double) try : pData() { cout << "Dbut du constructeur" << endl; pBuffer.p = new char[10]; pData = new int[10]; // Fuite de mmoire ! cout << "Lancement de lexception" << endl; throw 2; } catch (int) { cout << "catch du constructeur..." << endl; // delete[] pData; // Illgal! Lobjet est dj dtruit ! // Conversion de lexception en flottant : throw 3.14; } A::~A() { // Pointeur libration automatique. // Pointeur classique.

178

Chapitre 8. Les exceptions en C++


// Libration des donnes non automatiques : delete[] pData; // Jamais appel ! cout << "A::~A()" << endl; } int main(void) { try { A *a = new A; } catch (double d) { cout << "Ae, mme pas mal !" << endl; } return EXIT_SUCCESS; }

Dans cet exemple, lors de la cration dynamique dun objet A, une erreur dinitialisation se produit et une exception de type entier est lance. Celle-ci est alors traite dans le bloc catch qui suit la dnition du constructeur de la classe A. Ce bloc convertit lexception en exception de type ottant. Loprateur delete est bien appel automatiquement, mais le destructeur de A nest jamais excut. Comme indiqu dans le bloc catch du constructeur, les donnes membres de lobjet, qui est dj dtruit, ne sont plus accessibles. De ce fait, le bloc allou dynamiquement pour pData ne peut tre libr, et comme le destructeur nest pas appel, ce bloc de mmoire est dnitivement perdu. On ne peut corriger le problme que de deux manires : soit on neffectue pas ce type de traitement dans le constructeur, soit on encapsule le pointeur dans une classe disposant dun destructeur. Lexemple prcdent dnit une classe Buffer pour la donne membre pBuffer. Pour information, la bibliothque standard C++ fournit une classe gnrique similaire que lon pourra galement utiliser dans ce genre de situation (voir la Section 14.2.1 pour plus de dtails ce sujet). Le comportement du bloc catch des constructeurs avec bloc try est diffrent de celui des blocs catch classiques. Lobjet nayant pu tre construit, lexception doit obligatoirement tre transmise au code qui a cherch le crer, an dviter de le laisser dans un tat incohrent (cest--dire avec une rfrence dobjet non construit). Par consquent, contrairement aux exceptions traites dans un bloc catch classique, les exceptions lances dans les constructeurs sont automatiquement relances une fois quelles ont t traites dans le bloc catch du constructeur. Lexception doit donc toujours tre capte dans le code qui cherche crer lobjet, an de prendre les mesures adquates. Ainsi, dans lexemple prcdent, si lexception ntait pas convertie en exception de type ottant, le programme planterait, car elle serait malgr tout relance automatiquement et la fonction main ne dispose pas de bloc catch pour les exceptions de type entier.
Note : Cette rgle implique que les programmes dclarant des objets globaux dont le constructeur peut lancer une exception risquent de se terminer en catastrophe. En effet, si une exception est lance par ce constructeur linitialisation du programme, aucun gestionnaire dexception ne sera en mesure de la capter lorsque le bloc catch la relancera. On vitera donc tout prix de crer des objets globaux dont les constructeurs effectuent des tches susceptibles de lancer une exception. De manire gnrale, comme il la dj t dit, il nest de toutes manires pas sage de raliser des tches complexes dans un constructeur et lors de linitialisation des donnes statiques.

En gnral, si une classe hrite de une ou plusieurs classes de base, lappel aux constructeurs des classes de base doit se faire entre le mot cl try et la premire accolade. En effet, les constructeurs

179

Chapitre 8. Les exceptions en C++ des classes de base sont susceptibles, eux aussi, de lancer des exceptions. La syntaxe est alors la suivante :
Classe::Classe try : Base(paramtres) [, Base(paramtres) [...]] { } catch ...

Enn, les exceptions lances dans les destructeurs ne peuvent tre captes. En effet, elles correspondent une situation grave, puisquil nest pas pas possible de dtruire correctement lobjet dans ce cas. Dans ce cas, la fonction std::terminate est appele automatiquement, et le programme se termine en consquence.

180

Chapitre 9. Identication dynamique des types


Le C++ est un langage fortement typ. Malgr cela, il se peut que le type exact dun objet soit inconnu cause de lhritage. Par exemple, si un objet est considr comme un objet dune classe de base de sa vritable classe, on ne peut pas dterminer a priori quelle est sa vritable nature. Cependant, les objets polymorphiques (qui, rappelons-le, sont des objets disposant de mthodes virtuelles) conservent des informations sur leur type dynamique, savoir leur vritable nature. En effet, lors de lappel des mthodes virtuelles, la mthode appele est la mthode de la vritable classe de lobjet. Il est possible dutiliser cette proprit pour mettre en place un mcanisme permettant didentier le type dynamique des objets, mais cette manire de procder nest pas portable. Le C++ fournit donc un mcanisme standard permettant de manipuler les informations de type des objets polymorphiques. Ce mcanisme prend en charge lidentication dynamique des types et la vrication de la validit des transtypages dans le cadre de la drivation.

9.1. Identication dynamique des types


9.1.1. Loprateur typeid
Le C++ fournit loprateur typeid, qui permet de rcuprer les informations de type des expressions. Sa syntaxe est la suivante :
typeid(expression)

o expression est lexpression dont il faut dterminer le type. Le rsultat de loprateur typeid est une rfrence sur un objet constant de classe type_info. Cette classe sera dcrite dans la Section 9.1.2. Les informations de type rcupres sont les informations de type statique pour les types non polymorphiques. Cela signie que lobjet renvoy par typeid caractrisera le type de lexpression fournie en paramtre, que cette expression soit un sous-objet dun objet plus driv ou non. En revanche, pour les types polymorphiques, si le type ne peut pas tre dtermin statiquement (cest--dire la compilation), une dtermination dynamique (cest--dire lexcution) du type a lieu, et lobjet de classe type_info renvoy dcrit le vrai type de lexpression (mme si elle reprsente un sous-objet dun objet dune classe drive). Cette situation peut arriver lorsquon manipule un objet laide dun pointeur ou dune rfrence sur une classe de base de la classe de cet objet. Exemple 9-1. Oprateur typeid
#include <stdlib.h> #include <typeinfo> using namespace std; class Base { public: virtual ~Base(void);

// Il faut une fonction virtuelle // pour avoir du polymorphisme.

181

Chapitre 9. Identication dynamique des types


}; Base::~Base(void) { return ; } class Derivee : public Base { public: virtual ~Derivee(void); }; Derivee::~Derivee(void) { return ; } int main(void) { Derivee* pd = new Derivee; Base* pb = pd; const type_info &t1=typeid(*pd); const type_info &t2=typeid(*pb); return EXIT_SUCCESS; }

// t1 qualifie le type de *pd. // t2 qualifie le type de *pb.

Les objets t1 et t2 sont gaux, puisquils qualient tous les deux le mme type ( savoir, la classe Derivee). t2 ne contient pas les informations de type de la classe Base, parce que le vrai type de lobjet point par pb est la classe Derivee.
Note : Notez que la classe type_info est dnie dans lespace de nommage std::, rserv la bibliothque standard C++, dans len-tte typeinfo. Par consquent, son nom doit tre prcd du prxe std::. Vous pouvez vous passer de ce prxe en important les dnitions de lespace de nommage de la bibliothque standard laide dune directive using. Vous trouverez de plus amples renseignements sur les espaces de nommage dans le Chapitre 10.

On fera bien attention drfrencer les pointeurs, car sinon, on obtient les informations de type sur ce pointeur, pas sur lobjet point. Si le pointeur drfrenc est le pointeur nul, loprateur typeid lance une exception dont lobjet est une instance de la classe bad_typeid. Cette classe est dnie comme suit dans len-tte typeinfo :
class bad_typeid : public logic { public: bad_typeid(const char * what_arg) : logic(what_arg) { return ; } void raise(void) { handle_raise(); throw *this; }

182

Chapitre 9. Identication dynamique des types


};

9.1.2. La classe type_info


Les informations de type sont enregistres dans des objets de la classe type_info prdnie par le langage. Cette classe est dclare dans len-tte typeinfo de la manire suivante :
class type_info { public: virtual ~type_info(); bool operator==(const type_info &rhs) const; bool operator!=(const type_info &rhs) const; bool before(const type_info &rhs) const; const char *name() const; private: type_info(const type_info &rhs); type_info &operator=(const type_info &rhs); };

Les objets de la classe type_info ne peuvent pas tre copis, puisque loprateur daffectation et le constructeur de copie sont tous les deux dclars private. Par consquent, le seul moyen de gnrer un objet de la classe type_info est dutiliser loprateur typeid. Les oprateurs de comparaison permettent de tester lgalit et la diffrence de deux objets type_info, ce qui revient exactement comparer les types des expressions. Les objets type_info contiennent des informations sur les types sous la forme de chanes de caractres. Une de ces chanes reprsente le type sous une forme lisible par un tre humain, et une autre sous une forme plus approprie pour le traitement des types. Le format de ces chanes de caractres nest pas prcis et peut varier dune implmentation une autre. Il est possible de rcuprer le nom lisible du type laide de la mthode name. La valeur renvoye est un pointeur sur une chane de caractres. On ne doit pas librer la mmoire utilise pour stocker cette chane de caractres. La mthode before permet de dterminer un ordre dans les diffrents types appartenant la mme hirarchie de classes, en se basant sur les proprits dhritage. Lutilisation de cette mthode est toutefois difcile, puisque lordre entre les diffrentes classes nest pas x et peut dpendre de limplmentation.

9.2. Transtypages C++


Les rgles de drivation permettent dassurer le fait que lorsquon utilise un pointeur sur une classe, lobjet point existe bien et est bien de la classe sur laquelle le pointeur est bas. En particulier, il est possible de convertir un pointeur sur un objet en un pointeur sur un sous-objet. En revanche, il est interdit dutiliser un pointeur sur une classe de base pour initialiser un pointeur sur une classe drive. Pourtant, cette opration peut tre lgale, si le programmeur sait que le pointeur pointe bien sur un objet de la classe drive. Le langage exige cependant un transtypage explicite. Une telle situation demande lanalyse du programme an de savoir si elle est lgale ou non.

183

Chapitre 9. Identication dynamique des types Parfois, il est impossible de faire cette analyse. Cela signie que le programmeur ne peut pas certier que le pointeur dont il dispose est un pointeur sur un sous-objet. Le mcanisme didentication dynamique des types peut tre alors utilis pour vrier, lexcution, si le transtypage est lgal. Sil ne lest pas, un traitement particulier doit tre effectu, mais sil lest, le programme peut se poursuivre normalement. Le C++ fournit un jeu doprateurs de transtypage qui permettent de faire ces vrications dynamiques, et qui donc sont nettement plus srs que le transtypage tout puissant du C que lon a utilis jusquici. Ces oprateurs sont capables de faire un transtypage dynamique, un transtypage statique, un transtypage de constance et un transtypage de rinterprtation des donnes. Nous allons voir les diffrents oprateurs permettant de faire ces transtypages, ainsi que leur signication.

9.2.1. Transtypage dynamique


Le transtypage dynamique permet de convertir une expression en un pointeur ou une rfrence dune classe, ou un pointeur sur void. Il est ralis laide de loprateur dynamic_cast. Cet oprateur impose des restrictions lors des transtypages an de garantir une plus grande abilit :

il effectue une vrication de la validit du transtypage ; il nest pas possible dliminer les qualications de constance (pour cela, il faut utiliser loprateur const_cast, que lon verra plus loin).

En revanche, loprateur dynamic_cast permet parfaitement daccrotre la constance dun type complexe, comme le font les conversions implicites du langage vues dans la Section 3.6 et dans la Section 4.7. Il ne peut pas travailler sur les types de base du langage, sauf void *. La syntaxe de loprateur dynamic_cast est donne ci-dessous :
dynamic_cast<type>(expression)

o type dsigne le type cible du transtypage, et expression lexpression transtyper. Le transtypage dun pointeur ou dune rfrence dune classe drive en classe de base se fait donc directement, sans vrication dynamique, puisque cette opration est toujours valide. Les lignes suivantes :
// La classe B hrite de la classe A : B *pb; A *pA=dynamic_cast<A *>(pB);

sont donc strictement quivalentes celles-ci :


// La classe B hrite de la classe A : B *pb; A *pA=pB;

Tout autre transtypage doit se faire partir dun type polymorphique, an que le compilateur puisse utiliser lidentication dynamique des types lors du transtypage. Le transtypage dun pointeur dun objet vers un pointeur de type void renvoie ladresse du dbut de lobjet le plus driv, cest--dire

184

Chapitre 9. Identication dynamique des types ladresse de lobjet complet. Le transtypage dun pointeur ou dune rfrence sur un sous-objet dun objet vers un pointeur ou une rfrence de lobjet complet est effectu aprs vrication du type dynamique. Si lobjet point ou rfrenc est bien du type indiqu pour le transtypage, lopration se droule correctement. En revanche, sil nest pas du bon type, dynamic_cast neffectue pas le transtypage. Si le type cible est un pointeur, le pointeur nul est renvoy. Si en revanche lexpression caractrise un objet ou une rfrence dobjet, une exception de type bad_cast est lance. La classe bad_cast est dnie comme suit dans len-tte typeinfo :
class bad_cast : public exception { public: bad_cast(void) throw(); bad_cast(const bad_cast&) throw(); bad_cast &operator=(const bad_cast&) throw(); virtual ~bad_cast(void) throw(); virtual const char* what(void) const throw(); };

Lors dun transtypage, aucune ambigut ne doit avoir lieu pendant la recherche dynamique du type. De telles ambiguts peuvent apparatre dans les cas dhritage multiple, o plusieurs objets de mme type peuvent coexister dans le mme objet. Cette restriction mise part, loprateur dynamic_cast est capable de parcourir une hirarchie de classe aussi bien verticalement (convertir un pointeur de sous-objet vers un pointeur dobjet complet) que transversalement (convertir un pointeur dobjet vers un pointeur dun autre objet frre dans la hirarchie de classes). Loprateur dynamic_cast peut tre utilis dans le but de convertir un pointeur sur une classe de base virtuelle vers une des ses classes lles, ce que ne pouvaient pas faire les transtypages classiques du C. En revanche, il ne peut pas tre utilis an daccder des classes de base qui ne sont pas visibles (en particulier, les classes de base hrites en private). Exemple 9-2. Oprateur dynamic_cast
#include <stdlib.h> struct A { virtual void f(void) { return ; } }; struct B : virtual public A { }; struct C : virtual public A, public B { }; struct D { virtual void g(void) {

185

Chapitre 9. Identication dynamique des types


return ; } }; struct E : public B, public C, public D { }; int main(void) { E e;

e contient deux sous-objets de classe B (mais un seul sous-objet de classe A). Les sous-objets de classe C et D sont frres. Drivation lgale : le sous-objet de classe A est unique. pA;// Illgal : A est une classe de base // virtuelle (erreur de compilation). C *pC=dynamic_cast<C *>(pA); // Lgal. Transtypage // dynamique vertical. D *pD=dynamic_cast<D *>(pC); // Lgal. Transtypage // dynamique horizontal. B *pB=dynamic_cast<B *>(pA); // Lgal, mais chouera // lexcution (ambigut). return EXIT_SUCCESS;

// // // // A *pA=&e; // // // C *pC=(C *)

9.2.2. Transtypage statique


Contrairement au transtypage dynamique, le transtypage statique neffectue aucune vrication des types dynamiques lors du transtypage. Il est donc nettement plus dangereux que le transtypage dynamique. Cependant, contrairement au transtypage C classique, il ne permet toujours pas de supprimer les qualications de constance. Le transtypage statique seffectue laide de loprateur static_cast, dont la syntaxe est exactement la mme que celle de loprateur dynamic_cast :
static_cast<type>(expression)

o type et expression ont la mme signication que pour loprateur dynamic_cast. Essentiellement, loprateur static_cast neffectue lopration de transtypage que si lexpression suivante est valide :
type temporaire(expression);

Cette expression construit un objet temporaire quelconque de type type et linitialise avec la valeur de expression. Contrairement loprateur dynamic_cast, loprateur static_cast permet donc deffectuer les conversions entre les types autres que les classes dnies par lutilisateur. Aucune vrication de la validit de la conversion na lieu cependant (comme pour le transtypage C classique). Si une telle expression nest pas valide, le transtypage peut malgr tout avoir lieu sil sagit dun transtypage entre classes drives et classes de base. Loprateur static_cast permet deffectuer les transtypages de ce type dans les deux sens (classe de base vers classe drive et classe drive

186

Chapitre 9. Identication dynamique des types vers classe de base). Le transtypage dune classe de base vers une classe drive ne doit tre fait que lorsquon est sr quil ny a pas de danger, puisquaucune vrication dynamique na lieu avec static_cast. Enn, toutes les expressions peuvent tre converties en void avec des qualications de constance et de volatilit. Cette opration a simplement pour but de supprimer la valeur de lexpression (puisque void reprsente le type vide).

9.2.3. Transtypage de constance et de volatilit


La suppression des attributs de constance et de volatilit peut tre ralise grce loprateur const_cast. Cet oprateur suit exactement la mme syntaxe que les oprateurs dynamic_cast et static_cast :
const_cast<type>(expression)

Loprateur const_cast peut travailler essentiellement avec des rfrences et des pointeurs. Il permet de raliser les transtypages dont le type destination est moins contraint que le type source vis--vis des mots cls const et volatile. En revanche, loprateur const_cast ne permet pas deffectuer dautres conversions que les autres oprateurs de transtypage (ou simplement les transtypages C classiques) peuvent raliser. Par exemple, il est impossible de lutiliser pour convertir un ottant en entier. Lorsquil travaille avec des rfrences, loprateur const_cast vrie que le transtypage est lgal en convertissant les rfrences en pointeurs et en regardant si le transtypage nimplique que les attributs const et volatile. const_cast ne permet pas de convertir les pointeurs de fonctions.

9.2.4. Rinterprtation des donnes


Loprateur de transtypage le plus dangereux est reinterpret_cast. Sa syntaxe est la mme que celle des autres oprateurs de transtypage dynamic_cast, static_cast et const_cast :
reinterpret_cast<type>(expression)

Cet oprateur permet de rinterprter les donnes dun type en un autre type. Aucune vrication de la validit de cette opration nest faite. Ainsi, les lignes suivantes :
double f=2.3; int i=1; const_cast<int &>(f)=i;

sont strictement quivalentes aux lignes suivantes :


double f=2.3; int i=1; *((int *) &f)=i;

Loprateur reinterpret_cast doit cependant respecter les rgles suivantes :

187

Chapitre 9. Identication dynamique des types


il ne doit pas permettre la suppression des attributs de constance et de volatilit ; il doit tre symtrique (cest--dire que la rinterprtation dun type T1 en tant que type T2, puis la rinterprtation du rsultat en type T1 doit redonner lobjet initial).

188

Chapitre 10. Les espaces de nommage


Les espaces de nommage sont des zones de dclaration qui permettent de dlimiter la recherche des noms des identicateurs par le compilateur. Leur but est essentiellement de regrouper les identicateurs logiquement et dviter les conits de noms entre plusieurs parties dun mme projet. Par exemple, si deux programmeurs dnissent diffremment une mme structure dans deux chiers diffrents, un conit entre ces deux structures aura lieu au mieux ldition de liens, et au pire lors de lutilisation commune des sources de ces deux programmeurs. Ce type de conit provient du fait que le C++ ne fournit quun seul espace de nommage de porte globale, dans lequel il ne doit y avoir aucun conit de nom. Grce aux espaces de nommage non globaux, ce type de problme peut tre plus facilement vit, parce que lon peut viter de dnir les objets globaux dans la porte globale.

10.1. Dnition des espaces de nommage


10.1.1. Espaces de nommage nomms
Lorsque le programmeur donne un nom un espace de nommage, celui-ci est appel un espace de nommage nomm. La syntaxe de ce type despace de nommage est la suivante :
namespace nom { dclarations | dfinitions } nom est le nom de lespace de nommage, et dclarations et dfinitions sont les dclarations et

les dnitions des identicateurs qui lui appartiennent. Contrairement aux rgions dclaratives classiques du langage (comme par exemple les classes), un namespace peut tre dcoup en plusieurs morceaux. Le premier morceaux sert de dclaration, et les suivants dextensions. La syntaxe pour une extension despace de nommage est exactement la mme que celle de la partie de dclaration. Exemple 10-1. Extension de namespace
namespace A { int i; } namespace B { int i; } namespace A { int j; } // Dclaration de lespace de nommage A.

// Dclaration de lespace de nommage B.

// Extension de lespace de nommage A.

Les identicateurs dclars ou dnis lintrieur dun mme espace de nommage ne doivent pas entrer en conit. Ils peuvent avoir les mmes noms, mais seulement dans le cadre de la surcharge. Un

189

Chapitre 10. Les espaces de nommage espace de nommage se comporte donc exactement comme les zones de dclaration des classes et de la porte globale. Laccs aux identicateurs des espaces de nommage se fait par dfaut grce loprateur de rsolution de porte (::), et en qualiant le nom de lidenticateur utiliser du nom de son espace de nommage. Cependant, cette qualication est inutile lintrieur de lespace de nommage lui-mme, exactement comme pour les membres des classes lintrieur de leur classe. Exemple 10-2. Accs aux membres dun namespace
#include <stdlib.h> int i=1; // i est global.

namespace A { int i=2; // i de lespace de nommage A. int j=i; // Utilise A::i. } int main(void) { i=1; // Utilise ::i. A::i=3; // Utilise A::i. return EXIT_SUCCESS; }

Les fonctions membres dun espace de nommage peuvent tre dnies lintrieur de cet espace, exactement comme les fonctions membres de classes. Elles peuvent galement tre dnies en dehors de cet espace, si lon utilise loprateur de rsolution de porte. Les fonctions ainsi dnies doivent apparatre aprs leur dclaration dans lespace de nommage. Exemple 10-3. Dnition externe dune fonction de namespace
namespace A { int f(void); } int A::f(void) { return 0; }

// Dclaration de A::f.

// Dfinition de A::f.

Il est possible de dnir un espace de nommage lintrieur dun autre espace de nommage. Cependant, cette dclaration doit obligatoirement avoir lieu au niveau dclaratif le plus externe de lespace de nommage qui contient le sous-espace de nommage. On ne peut donc pas dclarer despaces de nommage lintrieur dune fonction ou lintrieur dune classe. Exemple 10-4. Dnition de namespace dans un namespace
namespace Conteneur { int i; namespace Contenu {

// Conteneur::i.

190

Chapitre 10. Les espaces de nommage


int j; } } // Conteneur::Contenu::j.

10.1.2. Espaces de nommage anonymes


Lorsque, lors de la dclaration dun espace de nommage, aucun nom nest donn, un espace de nommage anonyme est cr. Ce type despace de nommage permet dassurer lunicit du nom de lespace de nommage ainsi dclar. Les espaces de nommage anonymes peuvent donc remplacer efcacement le mot cl static pour rendre unique des identicateurs dans un chier. Cependant, elles sont plus puissantes, parce que lon peut galement dclarer des espaces de nommage anonymes lintrieur dautres espaces de nommage. Exemple 10-5. Dnition de namespace anonyme
namespace { int i; }

// quivalent unique::i;

Dans lexemple prcdent, la dclaration de i se fait dans un espace de nommage dont le nom est choisi par le compilateur de manire unique. Cependant, comme on ne connat pas ce nom, le compilateur utilise une directive using (voir plus loin) an de pouvoir utiliser les identicateurs de cet espace de nommage anonyme sans prciser leur nom complet avec loprateur de rsolution de porte. Si, dans un espace de nommage, un identicateur est dclar avec le mme nom quun autre identicateur dclar dans un espace de nommage plus global, lidenticateur global est masqu. De plus, lidenticateur ainsi dni ne peut tre accd en dehors de son espace de nommage que par un nom compltement quali laide de loprateur de rsolution de porte. Toutefois, si lespace de nommage dans lequel il est dni est un espace de nommage anonyme, cet identicateur ne pourra pas tre rfrenc, puisquon ne peut pas prciser le nom des espaces de nommage anonymes. Exemple 10-6. Ambiguts entre espaces de nommage
namespace { int i; } void f(void) { ++i; } namespace A { namespace { int i; int j; } void g(void) {

// Dclare unique::i.

// Utilise unique::i.

// Dfinit A::unique::i. // Dfinit A::unique::j.

191

Chapitre 10. Les espaces de nommage


++i; ++A::i; ++j; } } // Utilise A::unique::i. // Utilise A::unique::i. // Utiliser A::unique::j.

Les identicateurs dclars dans un espace de nommage anonyme ne sont visibles que du chier courant. Ils ne peuvent en effet tre vus des autres units de compilation, puisque pour cela il faudrait connatre le nom utilis par le compilateur pour lespace de nommage anonyme. De ce fait, les espaces de nommage anonymes constituent une technique de remplacement de lutilisation de la classe de stockage static pour les identicateurs globaux.

10.1.3. Alias despaces de nommage


Lorsquun espace de nommage porte un nom trs compliqu, il peut tre avantageux de dnir un alias pour ce nom. Lalias aura alors un nom plus simple. Cette opration peut tre ralise laide de la syntaxe suivante :
namespace nom_alias = nom; nom_alias est ici le nom de lalias de lespace de nommage, et nom est le nom de lespace de

nommage lui-mme. Les noms donns aux alias despaces de nommage ne doivent pas entrer en conit avec les noms des autres identicateurs du mme espace de nommage, que celui-ci soit lespace de nommage de porte globale ou non.

10.2. Dclaration using


Les dclarations using permettent dutiliser un identicateur dun espace de nommage de manire simplie, sans avoir spcier son nom complet (cest--dire le nom de lespace de nommage suivi du nom de lidenticateur).

10.2.1. Syntaxe des dclarations using


La syntaxe des dclarations using est la suivante :
using identificateur;

o identificateur est le nom complet de lidenticateur utiliser, avec qualication despace de nommage. Exemple 10-7. Dclaration using
namespace A { int i; int j; }

// Dclare A::i. // Dclare A::j.

192

Chapitre 10. Les espaces de nommage


void f(void) { using A::i; i=1; j=1; return ; }

// A::i peut tre utilis sous le nom i. // quivalent A::i=1. // Erreur ! j nest pas dfini !

Les dclarations using permettent en fait de dclarer des alias des identicateurs. Ces alias doivent tre considrs exactement comme des dclarations normales. Cela signie quils ne peuvent tre dclars plusieurs fois que lorsque les dclarations multiples sont autorises (dclarations de variables ou de fonctions en dehors des classes), et de plus ils appartiennent lespace de nommage dans lequel ils sont dnis. Exemple 10-8. Dclarations using multiples
#include <stdlib.h> namespace A { int i; void f(void) { } } namespace B { using A::i; using A::i; using A::f; } int main(void) { B::f(); // Appelle A::f. return EXIT_SUCCESS; }

// Dclaration de lalias B::i, qui reprsente A::i. // Lgal : double dclaration de A::i. // Dclare void B::f(void), // fonction identique A::f.

Lalias cr par une dclaration using permet de rfrencer uniquement les identicateurs qui sont visibles au moment o la dclaration using est faite. Si lespace de nommage concern par la dclaration using est tendu aprs cette dernire, les nouveaux identicateurs de mme nom que celui de lalias ne seront pas pris en compte. Exemple 10-9. Extension de namespace aprs une dclaration using
namespace A { void f(int); } using A::f; namespace A // f est synonyme de A::f(int).

193

Chapitre 10. Les espaces de nommage


{ void f(char); } void g() { f(a); } // f est toujours synonyme de A::f(int), // mais pas de A::f(char).

// Appelle A::f(int), mme si A::f(char) // existe.

Si plusieurs dclarations locales et using dclarent des identicateurs de mme nom, ou bien ces identicateurs doivent tous se rapporter au mme objet, ou bien ils doivent reprsenter des fonctions ayant des signatures diffrentes (les fonctions dclares sont donc surcharges). Dans le cas contraire, des ambiguts peuvent apparatre et le compilateur signale une erreur lors de la dclaration using. Exemple 10-10. Conit entre dclarations using et identicateurs locaux
namespace A { int i; void f(int); } void g(void) { int i; using A::i; void f(char); using A::f; return ; }

// // // //

Dclaration locale de i. Erreur : i est dj dclar. Dclaration locale de f(char). Pas derreur, il y a surcharge de f.

Note : Ce comportement diffre de celui des directives using. En effet, les directives using reportent la dtection des erreurs la premire utilisation des identicateurs ambigus.

10.2.2. Utilisation des dclarations using dans les classes


Une dclaration using peut tre utilise dans la dnition dune classe. Dans ce cas, elle doit se rapporter une classe de base de la classe dans laquelle elle est utilise. De plus, lidenticateur donn la dclaration using doit tre accessible dans la classe de base (cest--dire de type protected ou public). Exemple 10-11. Dclaration using dans une classe
namespace A { float f; } class Base {

194

Chapitre 10. Les espaces de nommage


int i; public: int j; }; class Derivee : public Base { using A::f; // Illgal : f nest pas dans une classe // de base. using Base::i; // Interdit : Derivee na pas le droit // dutiliser Base::i. public: using Base::j; // Lgal. };

Dans lexemple prcdent, seule la troisime dclaration est valide, parce que cest la seule qui se rfre un membre accessible de la classe de base. Le membre j dclar sera donc un synonyme de Base::j dans la classe Derivee. En gnral, les membres des classes de base sont accessibles directement. Quelle est donc lutilit des dclarations using dans les classes ? En fait, elles peuvent tre utilises pour modier les droits daccs aux membres des classes de base, pourvu que la classe drive puisse y accder bien entendu. Pour cela, il suft de placer la dclaration using dans une zone de dclaration du type dsir. Exemple 10-12. Modication des droits daccs laide dune directive using
class Base { public: int i; protected: int j; }; class Derivee : protected Base { private: using Base::i; // i ne sera plus visible des classes drives de Derivee. public: using Base::j; // j est maintenant publiquement accessible. };

Note : Certains compilateurs interprtent diffremment les paragraphe 7.3.3 et 11.3 de la norme C++, qui concerne laccessibilit des membres introduits avec une dclaration using. Certains considrent que les dclarations using ne permettent que de rtablir laccessibilit des droits sur les membres des classes de base dont lhritage a restreint laccs. Dautres ne permettent que de restreindre laccessibilit et non pas de les modier. Pour autant, le comportement est parfaitement dni, il permet de modier les droits daccs, pas de les restreindre, ni seulement de les rtablir.

Quand une fonction dune classe de base est introduite dans une classe drive laide dune dclaration using, et quune fonction de mme nom et de mme signature est dnie dans la classe drive, cette dernire fonction surcharge la fonction de la classe de base. Il ny a pas dambigut dans ce cas.

195

Chapitre 10. Les espaces de nommage

10.3. Directive using


La directive using permet dutiliser, sans spcication despace de nommage, non pas un identicateur comme dans le cas de la dclaration using, mais tous les identicateurs de cet espace de nommage. La syntaxe de la directive using est la suivante :
using namespace nom;

o nom est le nom de lespace de nommage dont les identicateurs doivent tre utiliss sans qualication complte. Exemple 10-13. Directive using
namespace A { int i; int j; }

// Dclare A::i. // Dclare A::j.

void f(void) { using namespace A; // On utilise les identificateurs de A. i=1; // quivalent A::i=1. j=1; // quivalent A::j=1. return ; }

Aprs une directive using, il est toujours possible dutiliser les noms complets des identicateurs de lespace de nommage, mais ce nest plus ncessaire. Les directives using sont valides partir de la ligne o elles sont dclares jusqu la n du bloc de porte courante. Si un espace de nommage est tendu aprs une directive using, les identicateurs dnis dans lextension de lespace de nommage peuvent tre utiliss exactement comme les identicateurs dnis avant la directive using (cest-dire sans qualication complte de leurs noms). Exemple 10-14. Extension de namespace aprs une directive using
namespace A { int i; } using namespace A; namespace A { int j; } void f(void) { i=0; // Initialise A::i. j=0; // Initialise A::j. return ; }

196

Chapitre 10. Les espaces de nommage Il se peut que lors de lintroduction des identicateurs dun espace de nommage par une directive using, des conits de noms apparaissent. Dans ce cas, aucune erreur nest signale lors de la directive using. En revanche, une erreur se produit si un des identicateurs pour lesquels il y a conit est utilis. Exemple 10-15. Conit entre directive using et identicateurs locaux
namespace A { int i; // Dfinit A::i. } namespace B { int i; // Dfinit B::i. using namespace A; // A::i et B::i sont en conflit. // Cependant, aucune erreur napparat. } void f(void) { using namespace B; i=2; // Erreur : il y a ambigut. return ; }

197

Chapitre 10. Les espaces de nommage

198

Chapitre 11. Les template


11.1. Gnralits
Nous avons vu prcdemment comment raliser des structures de donnes relativement indpendantes de la classe de leurs donnes (cest--dire de leur type) avec les classes abstraites. Par ailleurs, il est faisable de faire des fonctions travaillant sur de nombreux types grce la surcharge. Je rappelle quen C++, tous les types sont en fait des classes. Cependant, lemploi des classes abstraites est assez fastidieux et a linconvnient daffaiblir le contrle des types ralis par le compilateur. De plus, la surcharge nest pas gnralisable pour tous les types de donnes. Il serait possible dutiliser des macros pour faire des fonctions atypiques mais cela serait au dtriment de la taille du code. Le C++ permet de rsoudre ces problmes grce aux paramtres gnriques, que lon appelle encore paramtres template. Un paramtre template est soit un type gnrique, soit une constante dont le type est assimilable un type intgral. Comme leur nom lindique, les paramtres template permettent de paramtrer la dnition des fonctions et des classes. Les fonctions et les classes ainsi paramtres sont appeles respectivement fonctions template et classes template. Les fonctions template sont donc des fonctions qui peuvent travailler sur des objets dont le type est un type gnrique (cest--dire un type quelconque), ou qui peuvent tres paramtrs par une constante de type intgral. Les classes template sont des classes qui contiennent des membres dont le type est gnrique ou qui dpendent dun paramtre intgral. En gnral, la gnration du code a lieu lors dune opration au cours de laquelle les types gnriques sont remplacs par des vrais types et les paramtres de type intgral prennent leur valeur. Cette opration sappelle linstanciation des template. Elle a lieu lorsquon utilise la fonction ou la classe template pour la premire fois. Les types rels utiliser la place des types gnriques sont dtermins lors de cette premire utilisation par le compilateur, soit implicitement partir du contexte dutilisation du template, soit par les paramtres donns explicitement par le programmeur.

11.2. Dclaration des paramtres template


Les paramtres template sont, comme on la vu, soit des types gnriques, soit des constantes dont le type peut tre assimil un type intgral.

11.2.1. Dclaration des types template


Les template qui sont des types gnriques sont dclars par la syntaxe suivante :
template <class|typename nom[=type] [, class|typename nom[=type] [...]>

o nom est le nom que lon donne au type gnrique dans cette dclaration. Le mot cl class a ici exactement la signication de type . Il peut dailleurs tre remplac indiffremment dans cette syntaxe par le mot cl typename. La mme dclaration peut tre utilise pour dclarer un nombre arbitraire de types gnriques, en les sparant par des virgules. Les paramtres template qui sont des types peuvent prendre des valeurs par dfaut, en faisant suivre le nom du paramtre dun signe gal et de la valeur. Ici, la valeur par dfaut doit videmment tre un type dj dclar.

199

Chapitre 11. Les template Exemple 11-1. Dclaration de paramtres template


template <class T, typename U, class V=int>

Dans cet exemple, T, U et V sont des types gnriques. Ils peuvent remplacer nimporte quel type du langage dj dclar au moment o la dclaration template est faite. De plus, le type gnrique V a pour valeur par dfaut le type entier int. On voit bien dans cet exemple que les mots cls typename et class peuvent tre utiliss indiffremment. Lorsquon donne des valeurs par dfaut un type gnrique, on doit donner des valeurs par dfaut tous les types gnriques qui le suivent dans la dclaration template. La ligne suivante provoquera donc une erreur de compilation :
template <class T=int, class V>

Il est possible dutiliser une classe template en tant que type gnrique. Dans ce cas, la classe doit tre dclare comme tant template lintrieur mme de la dclaration template. La syntaxe est donc la suivante :
template <template <class Type> class Classe [,...]>

o Type est le type gnrique utilis dans la dclaration de la classe template Classe. On appelle les paramtres template qui sont des classes template des paramtres template template. Rien ninterdit de donner une valeur par dfaut un paramtre template template : le type utilis doit alors tre une classe template dclare avant la dclaration template. Exemple 11-2. Dclaration de paramtre template template
template <class T> class Tableau { // Dfinition de la classe template Tableau. }; template <class U, class V, template <class T> class C=Tableau> class Dictionnaire { C<U> Clef; C<V> Valeur; // Reste de la dfinition de la classe Dictionnaire. };

Dans cet exemple, la classe template Dictionnaire permet de relier des cls leurs lments. Ces cls et ces valeurs peuvent prendre nimporte quel type. Les cls et les valeurs sont stockes paralllement dans les membres Clef et Valeur . Ces membres sont en fait des conteneurs template, dont la classe est gnrique et dsigne par le paramtre template template C. Le paramtre template de C est utilis pour donner le type des donnes stockes, savoir les types gnriques U et V dans le cas de la classe Dictionnaire. Enn, la classe Dictionnaire peut utiliser un conteneur par dfaut, qui est la classe template Tableau. Pour plus de dtails sur la dclaration des classes template, voir la Section 11.3.2.

200

Chapitre 11. Les template

11.2.2. Dclaration des constantes template


La dclaration des paramtres template de type constante se fait de la manire suivante :
template <type paramtre[=valeur][, ...]>

o type est le type du paramtre constant, paramtre est le nom du paramtre et valeur est sa valeur par dfaut. Il est possible de donner des paramtres template qui sont des types gnriques et des paramtres template qui sont des constantes dans la mme dclaration. Le type des constantes template doit obligatoirement tre lun des types suivants :

type intgral (char, wchar_t, int, long, short et leurs versions signes et non signes) ou numr ; pointeur ou rfrence dobjet ; pointeur ou rfrence de fonction ; pointeur sur membre.

Ce sont donc tous les types qui peuvent tre assimils des valeurs entires (entiers, numrs ou adresses). Exemple 11-3. Dclaration de paramtres template de type constante
template <class T, int i, void (*f)(int)>

Cette dclaration template comprend un type gnrique T, une constante template i de type int, et une constante template f de type pointeur sur fonction prenant un entier en paramtre et ne renvoyant rien.
Note : Les paramtres constants de type rfrence ne peuvent pas tre initialiss avec une donne immdiate ou une donne temporaire lors de linstanciation du template. Voir la Section 11.4 pour plus de dtails sur linstanciation des template.

11.3. Fonctions et classes template


Aprs la dclaration dun ou de plusieurs paramtres template suit en gnral la dclaration ou la dnition dune fonction ou dune classe template. Dans cette dnition, les types gnriques peuvent tre utiliss exactement comme sil sagissait de types normaux. Les constantes template peuvent tre utilises dans la fonction ou la classe template comme des constantes locales.

11.3.1. Fonctions template


La dclaration et la dnition des fonctions template se fait exactement comme si la fonction tait une fonction normale, ceci prs quelle doit tre prcde de la dclaration des paramtres template. La syntaxe dune dclaration de fonction template est donc la suivante :
template <paramtres_template> type fonction(paramtres_fonction);

201

Chapitre 11. Les template o paramtre_template est la liste des paramtres template et paramtres_fonction est la liste des paramtres de la fonction fonction. type est le type de la valeur de retour de la fonction, ce peut tre un des types gnriques de la liste des paramtres template. Tous les paramtres template qui sont des types doivent tre utiliss dans la liste des paramtres de la fonction, moins quune instanciation explicite de la fonction ne soit utilise. Cela permet au compilateur de raliser lidentication des types gnriques avec les types utiliser lors de linstanciation de la fonction. Voir la Section 11.4 pour plus de dtails ce sujet. La dnition dune fonction template se fait comme une dclaration avec le corps de la fonction. Il est alors possible dy utiliser les paramtres template comme sils taient des types normaux : des variables peuvent tre dclares avec un type gnrique, et les constantes template peuvent tre utilises comme des variables dnies localement avec la classe de stockage const. Les fonctions template scrivent donc exactement comme des fonctions classiques. Exemple 11-4. Dnition de fonction template
template <class T> T Min(T x, T y) { return x<y ? x : y; }

La fonction Min ainsi dnie fonctionnera parfaitement pour toute classe pour laquelle loprateur < est dni. Le compilateur dterminera automatiquement quel est loprateur employer pour chaque fonction Min quil rencontrera. Les fonctions template peuvent tre surcharges, aussi bien par des fonctions classiques que par dautres fonctions template. Lorsquil y a ambigut entre une fonction template et une fonction normale qui la surcharge, toutes les rfrences sur le nom commun ces fonctions se rapporteront la fonction classique. Une fonction template peut tre dclare amie dune classe, template ou non, pourvu que cette classe ne soit pas locale. Toutes les instances gnres partir dune fonction amie template sont amies de la classe donnant lamiti, et ont donc libre accs sur toutes les donnes de cette classe.

11.3.2. Les classes template


La dclaration et la dnition dune classe template se font comme celles dune fonction template : elles doivent tre prcdes de la dclaration template des types gnriques. La dclaration suit donc la syntaxe suivante :
template <paramtres_template> class|struct|union nom;

o paramtres_template est la liste des paramtres template utiliss par la classe template nom. La seule particularit dans la dnition des classes template est que si les mthodes de la classe ne sont pas dnies dans la dclaration de la classe, elles devront elles aussi tre dclares template :
template <paramtres_template> type classe<paramtres>::nom(paramtres_mthode) { ... }

202

Chapitre 11. Les template o paramtre_template reprsente la liste des paramtres template de la classe template classe, nom reprsente le nom de la mthode dnir, et paramtres_mthode ses paramtres. Il est absolument ncessaire dans ce cas de spcier tous les paramtres template de la liste paramtres_template dans paramtres, spars par des virgules, an de caractriser le fait que cest la classe classe qui est template et quil ne sagit pas dune mthode template dune classe normale. Dune manire gnrale, il faudra toujours spcier les types gnriques de la classe entre les signes dinfriorit et de supriorit, juste aprs son nom, chaque fois quon voudra la rfrencer. Cette rgle est cependant facultative lorsque la classe est rfrence lintrieur dune fonction membre. Contrairement aux fonctions template non membres, les mthodes des classes template peuvent utiliser des types gnriques de leur classe sans pour autant quils soient utiliss dans la liste de leurs paramtres. En effet, le compilateur dtermine quels sont les types identier aux types gnriques lors de linstanciation de la classe template, et na donc pas besoin deffectuer cette identication avec les types des paramtres utiliss. Voir la Section 11.3.3 pour plus de dtails ce sujet. Exemple 11-5. Dnition dune pile template
template <class T> class Stack { typedef struct stackitem { T Item; // On utilise le type T comme struct stackitem *Next; // si ctait un type normal. } StackItem; StackItem *Tete; public: // Les fonctions de la pile : Stack(void); Stack(const Stack<T> &); // La classe est rfrence en indiquant // son type entre < et > ("Stack<T>"). // Ici, ce nest pas une ncessit // cependant. ~Stack(void); Stack<T> &operator=(const Stack<T> &); void push(T); T pop(void); bool is_empty(void) const; void flush(void); }; // Pour les fonctions membres dfinies en dehors de la dclaration // de la classe, il faut une dclaration de type gnrique : template <class T> Stack<T>::Stack(void) // // // // { Tete = NULL; return; }

La classe est rfrence en indiquant son type entre < et > ("Stack<T>"). Cest impratif en dehors de la dclaration de la classe.

203

Chapitre 11. Les template

template <class T> Stack<T>::Stack(const Stack<T> &Init) { Tete = NULL; StackItem *tmp1 = Init.Tete, *tmp2 = NULL; while (tmp1!=NULL) { if (tmp2==NULL) { Tete= new StackItem; tmp2 = Tete; } else { tmp2->Next = new StackItem; tmp2 = tmp2->Next; } tmp2->Item = tmp1->Item; tmp1 = tmp1->Next; } if (tmp2!=NULL) tmp2->Next = NULL; return; } template <class T> Stack<T>::~Stack(void) { flush(); return; } template <class T> Stack<T> &Stack<T>::operator=(const Stack<T> &Init) { flush(); StackItem *tmp1 = Init.Tete, *tmp2 = NULL; while (tmp1!=NULL) { if (tmp2==NULL) { Tete = new StackItem; tmp2 = Tete; } else { tmp2->Next = new StackItem; tmp2 = tmp2->Next; } tmp2->Item = tmp1->Item; tmp1 = tmp1->Next; } if (tmp2!=NULL) tmp2->Next = NULL; return *this; }

204

Chapitre 11. Les template


template <class T> void Stack<T>::push(T Item) { StackItem *tmp = new StackItem; tmp->Item = Item; tmp->Next = Tete; Tete = tmp; return; } template <class T> T Stack<T>::pop(void) { T tmp; StackItem *ptmp = Tete; if (Tete!=NULL) { tmp = Tete->Item; Tete = Tete->Next; delete ptmp; } return tmp; } template <class T> bool Stack<T>::is_empty(void) const { return (Tete==NULL); } template <class T> void Stack<T>::flush(void) { while (Tete!=NULL) pop(); return; }

Les classes template peuvent parfaitement avoir des fonctions amies, que ces fonctions soient ellesmmes template ou non.

11.3.3. Fonctions membres template


Les destructeurs mis part, les mthodes dune classe peuvent tre template, que la classe ellemme soit template ou non, pourvu que la classe ne soit pas une classe locale. Les fonctions membres template peuvent appartenir une classe template ou une classe normale. Lorsque la classe laquelle elles appartiennent nest pas template, leur syntaxe est exactement la mme que pour les fonctions template non membre.

205

Chapitre 11. Les template Exemple 11-6. Fonction membre template


class A { int i; // Valeur de la classe. public: template <class T> void add(T valeur); }; template <class T> void A::add(T valeur) { i=i+((int) valeur); return ; }

// Ajoute valeur A::i.

Si, en revanche, la classe dont la fonction membre fait partie est elle aussi template, il faut spcier deux fois la syntaxe template : une fois pour la classe, et une fois pour la fonction. Si la fonction membre template est dnie lintrieur de la classe, il nest pas ncessaire de donner les paramtres template de la classe, et la dnition de la fonction membre template se fait donc exactement comme celle dune fonction template classique. Exemple 11-7. Fonction membre template dune classe template
template<class T> class Chaine { public: // Fonction membre template dfinie // lextrieur de la classe template : template<class T2> int compare(const T2 &); // Fonction membre template dfinie // lintrieur de la classe template : template<class T2> Chaine(const Chaine<T2> &s) { // ... } }; // lextrieur de la classe template, on doit donner // les dclarations template pour la classe // et pour la fonction membre template : template<class T> template<class T2> int Chaine<T>::compare(const T2 &s) { // ... }

Les fonctions membres virtuelles ne peuvent pas tre template. Si une fonction membre template a le mme nom quune fonction membre virtuelle dune classe de base, elle ne constitue pas une rednition de cette fonction. Par consquent, les mcanismes de virtualit sont inutilisables avec les

206

Chapitre 11. Les template fonctions membres template. On peut contourner ce problme de la manire suivante : on dnira une fonction membre virtuelle non template qui appellera la fonction membre template. Exemple 11-8. Fonction membre template et fonction membre virtuelle
class B { virtual void f(int); }; class D : public B { template <class T> void f(T); // Cette fonction ne redfinit pas B::f(int). void f(int i) { f<>(i); return ; } }; // Cette fonction surcharge B::f(int). // Elle appelle de la fonction template.

Dans lexemple prcdent, on est oblig de prciser que la fonction appeler dans la fonction virtuelle est la fonction template, et quil ne sagit donc pas dun appel rcursif de la fonction virtuelle. Pour cela, on fait suivre le nom de la fonction template dune paire de signes infrieur et suprieur. Plus gnralement, si une fonction membre template dune classe peut tre spcialise en une fonction qui a la mme signature quune autre fonction membre de la mme classe, et que ces deux fonctions ont le mme nom, toute rfrence ce nom utilisera la fonction non-template. Il est possible de passer outre cette rgle, condition de donner explicitement la liste des paramtres template entre les signes infrieur et suprieur lors de lappel de la fonction. Exemple 11-9. Surcharge de fonction membre par une fonction membre template
#include <stdlib.h> #include <iostream> using namespace std; struct A { void f(int); template <class T> void f(T) { cout << "Template" << endl; } }; // Fonction non template : void A::f(int) { cout << "Non template" << endl; } // Fonction template :

207

Chapitre 11. Les template


template <> void A::f<int>(int) { cout << "Spcialisation f<int>" << endl; } int main(void) { A a; a.f(1); // Appel de la version non-template de f. a.f(c); // Appel de la version template de f. a.f<>(1); // Appel de la version template spcialise de f. return EXIT_SUCCESS; }

Pour plus de dtails sur la spcialisation des template, voir la Section 11.5.

11.4. Instanciation des template


La dnition des fonctions et des classes template ne gnre aucun code tant que tous les paramtres template nont pas pris chacun une valeur spcique. Il faut donc, lors de lutilisation dune fonction ou dune classe template, fournir les valeurs pour tous les paramtres qui nont pas de valeur par dfaut. Lorsque sufsamment de valeurs sont donnes, le code est gnr pour ce jeu de valeurs. On appelle cette opration linstanciation des template. Plusieurs possibilits sont offertes pour parvenir ce rsultat : linstanciation implicite et linstanciation explicite.

11.4.1. Instanciation implicite


Linstanciation implicite est utilise par le compilateur lorsquil rencontre une expression qui utilise pour la premire fois une fonction ou une classe template, et quil doit linstancier pour continuer son travail. Le compilateur se base alors sur le contexte courant pour dterminer les types des paramtres template utiliser. Si aucune ambigut na lieu, il gnre le code pour ce jeu de paramtres. La dtermination des types des paramtres template peut se faire simplement, ou tre dduite de lexpression compiler. Par exemple, les fonctions membres template sont instancies en fonction du type de leurs paramtres. Si lon reprend lexemple de la fonction template Min dnie dans lExemple 11-4, cest son utilisation directe qui provoque une instanciation implicite. Exemple 11-10. Instanciation implicite de fonction template
int i=Min(2,3);

Dans cet exemple, la fonction Min est appele avec les paramtres 2 et 3. Comme ces entiers sont tous les deux de type int, la fonction template Min est instancie pour le type int. Partout dans la dnition de Min, le type gnrique T est donc remplac par le type int. Si lon appelle une fonction template avec un jeu de paramtres qui provoque une ambigut, le compilateur signale une erreur. Cette erreur peut tre leve en surchargeant la fonction template par une fonction qui accepte les mmes paramtres. Par example, la fonction template Min ne peut pas tre instancie dans le code suivant :

208

Chapitre 11. Les template


int i=Min(2,3.0);

parce que le compilateur ne peut pas dterminer si le type gnrique T doit prendre la valeur int ou double. Il y a donc une erreur, sauf si une fonction Min(int, double) est dnie quelque part. Pour rsoudre ce type de problme, on devra spcier manuellement les paramtres template de la fonction, lors de lappel. Ainsi, la ligne prcdente compile si on la rcrit comme suit :
int i=Min<int>(2,3.0);

dans cet exemple, le paramtre template est forc int, et 3.0 est converti en entier. On prendra garde au fait que le compilateur utilise une politique minimaliste pour linstanciation implicite des template. Cela signie quil ne crera que le code ncessaire pour compiler lexpression qui exige une instanciation implicite. Par exemple, la dnition dun objet dune classe template dont tous les types dnis provoque linstanciation de cette classe, mais la dnition dun pointeur sur cette classe ne le fait pas. Linstanciation aura lieu lorsquun drfrencement sera fait par lintermdiaire de ce pointeur. De mme, seules les fonctionnalits utilises de la classe template seront effectivement dnies dans le programme nal. Par exemple, dans le programme suivant :
#include <stdlib.h> #include <iostream> using namespace std; template <class T> class A { public: void f(void); void g(void); }; // Dfinition de la mthode A<T>::f() : template <class T> void A<T>::f(void) { cout << "A<T>::f() appele" << endl; } // On ne dfinit pas la mthode A<T>::g()... int main(void) { A<char> a; // Instanciation de A<char>. a.f(); // Instanciation de A<char>::f(). return EXIT_SUCCESS; }

seule la mthode f de la classe template A est instancie, car cest la seule mthode utilise cet endroit. Ce programme pourra donc parfaitement tre compil, mme si la mthode g na pas t dnie.

209

Chapitre 11. Les template

11.4.2. Instanciation explicite


Linstanciation explicite des template est une technique permettant au programmeur de forcer linstanciation des template dans son programme. Pour raliser une instanciation explicite, il faut spcier explicitement tous les paramtres template utiliser. Cela se fait simplement en donnant la dclaration du template, prcde par le mot cl template :
template nom<valeur[, valeur[...]]>;

Par exemple, pour forcer linstanciation dune pile telle que celle dnie dans lExemple 11-5, il faudra prciser le type des lments entre crochets aprs le nom de la classe :
template Stack<int>; // Instancie la classe Stack<int>.

Cette syntaxe peut tre simplie pour les fonctions template, condition que tous les paramtres template puissent tre dduits par le compilateur des types des paramtres utiliss dans la dclaration de la fonction. Ainsi, il est possible de forcer linstanciation de la fonction template Min de la manire suivante :
template int Min(int, int);

Dans cet exemple, la fonction template Min est instancie pour le type int, puisque ses paramtres sont de ce type. Lorsquune fonction ou une classe template a des valeurs par dfaut pour ses paramtres template, il nest pas ncessaire de donner une valeur pour ces paramtres. Si toutes les valeurs par dfaut sont utilises, la liste des valeurs peut tre vide (mais les signes dinfriorit et de supriorit doivent malgr tout tre prsents). Exemple 11-11. Instanciation explicite de classe template
template<class T = char> class Chaine; template Chaine<>; // Instanciation explicite de Chaine<char>.

11.4.3. Problmes soulevs par linstanciation des template


Les template doivent imprativement tre dnis lors de leur instanciation pour que le compilateur puisse gnrer le code de linstance. Cela signie que les chiers den-tte doivent contenir non seulement la dclaration, mais galement la dnition complte des template. Cela a plusieurs inconvnients. Le premier est bien entendu que lon ne peut pas considrer les template comme les fonctions et les classes normales du langage, pour lesquels il est possible de sparer la dclaration de la dnition dans des chiers spars. Le deuxime inconvnient est que les instances des template sont compiles plusieurs fois, ce qui diminue dautant plus les performances des compilateurs. Enn, ce qui est le plus grave, cest que les instances des template sont en multiples exemplaires dans les chiers objets gnrs par le compilateur, et accroissent donc la taille des chiers excutables lissue

210

Chapitre 11. Les template de ldition de liens. Cela nest pas gnant pour les petits programmes, mais peut devenir rdhibitoire pour les programmes assez gros. Le premier problme nest pas trop gnant, car il rduit le nombre de chiers sources, ce qui nest en gnral pas une mauvaise chose. Notez galement que les template ne peuvent pas tre considrs comme des chiers sources classiques, puisque sans instanciation, ils ne gnrent aucun code machine (ce sont des classes de classes, ou mtaclasses ). Mais ce problme peut devenir ennuyant dans le cas de bibliothques template crites et vendues par des socits dsireuses de conserver leur savoir-faire. Pour rsoudre ce problme, le langage donne la possibilit dexporter les dnitions des template dans des chiers complmentaires. Nous verrons la manire de procder dans la Section 11.7. Le deuxime problme peut tre rsolu avec lexportation des template, ou par tout autre technique doptimisation des compilateurs. Actuellement, la plupart des compilateurs sont capables de gnrer des chiers den-tte prcompils, qui contiennent le rsultat de lanalyse des chiers den-tte dj lus. Cette technique permet de diminuer considrablement les temps de compilation, mais ncessite souvent dutiliser toujours le mme chier den-tte au dbut des chiers sources. Le troisime problme est en gnral rsolu par des techniques varies, qui ncessitent des traitements complexes dans lditeur de liens ou le compilateur. La technique la plus simple, utilise par la plupart des compilateurs actuels, passe par une modication de lditeur de liens pour quil regroupe les diffrentes instances des mmes template. Dautres compilateurs, plus rares, grent une base de donnes dans laquelle les instances de template gnres lors de la compilation sont stockes. Lors de ldition de liens, les instances de cette base sont ajoutes la ligne de commande de lditeur de liens an de rsoudre les symboles non dnis. Enn, certains compilateurs permettent de dsactiver les instanciations implicites des template. Cela permet de laisser au programmeur la responsabilit de les instancier manuellement, laide dinstanciations explicites. Ainsi, les template peuvent ntre dnies que dans un seul chier source, rserv cet effet. Cette dernire solution est de loin la plus sre, et il est donc recommand dcrire un tel chier pour chaque programme. Ce paragraphe vous a prsent trois des principaux problmes soulevs par lutilisation des template, ainsi que les solutions les plus courantes qui y ont t apportes. Il est vivement recommand de consulter la documentation fournie avec lenvironnement de dveloppement utilis, an la fois de rduire les temps de compilation et doptimiser les excutables gnrs.

11.5. Spcialisation des template


Jusqu prsent, nous avons dni les classes et les fonctions template dune manire unique, pour tous les types et toutes les valeurs des paramtres template. Cependant, il peut tre intressant de dnir une version particulire dune classe ou dune fonction pour un jeu particulier de paramtres template. Par exemple, la pile de lExemple 11-5 peut tre implmente beaucoup plus efcacement si elle stocke des pointeurs plutt que des objets, sauf si les objets sont petits (ou appartiennent un des types prdnis du langage). Il peut tre intressant de manipuler les pointeurs de manire transparente au niveau de la pile, pour que la mthode pop renvoie toujours un objet, que la pile stocke des pointeurs ou des objets. An de raliser cela, il faut donner une deuxime version de la pile pour les pointeurs. Le C++ permet tout cela : lorsquune fonction ou une classe template a t dnie, il est possible de la spcialiser pour un certain jeu de paramtres template. Il existe deux types de spcialisation : les spcialisations totales, qui sont les spcialisations pour lesquelles il ny a plus aucun paramtre template (ils ont tous une valeur bien dtermine), et les spcialisations partielles, pour lesquelles seuls quelques paramtres template ont une valeur xe.

211

Chapitre 11. Les template

11.5.1. Spcialisation totale


Les spcialisations totales ncessitent de fournir les valeurs des paramtres template, spares par des virgules et entre les signes dinfriorit et de supriorit, aprs le nom de la fonction ou de la classe template. Il faut faire prcder la dnition de cette fonction ou de cette classe par la ligne suivante :
template <>

qui permet de signaler que la liste des paramtres template pour cette spcialisation est vide (et donc que la spcialisation est totale). Par exemple, si la fonction Min dnie dans lExemple 11-4 doit tre utilise sur une structure Structure et se baser sur un des champs de cette structure pour effectuer les comparaisons, elle pourra tre spcialise de la manire suivante : Exemple 11-12. Spcialisation totale
struct Structure { int Clef; void *pData; };

// Clef permettant de retrouver des donnes. // Pointeur sur les donnes.

template <> Structure Min<Structure>(Structure s1, Structure s2) { if (s1.Clef>s2.Clef) return s1; else return s2; }

Note : Pour quelques compilateurs, la ligne dclarant la liste vide des paramtres template ne doit pas tre crite. On doit donc faire des spcialisations totale sans le mot cl template. Ce comportement nest pas celui spci par la norme, et le code crit pour ces compilateurs nest donc pas portable.

11.5.2. Spcialisation partielle


Les spcialisations partielles permettent de dnir limplmentation dune fonction ou dune classe template pour certaines valeurs de leurs paramtres template et de garder dautres paramtres indnis. Il est mme possible de changer la nature dun paramtre template (cest--dire prciser sil sagit dun pointeur ou non) et de forcer le compilateur prendre une implmentation plutt quune autre selon que la valeur utilise pour ce paramtre est elle-mme un pointeur ou non. Comme pour les spcialisations totales, il est ncessaire de dclarer la liste des paramtres template utiliss par la spcialisation. Cependant, la diffrence des spcialisations totales, cette liste ne peut plus tre vide. Comme pour les spcialisations totales, la dnition de la classe ou de la fonction template doit utiliser les signes dinfriorit et de supriorit pour donner la liste des valeurs des paramtres template pour la spcialisation.

212

Chapitre 11. Les template Exemple 11-13. Spcialisation partielle


// Dfinition dune classe template : template <class T1, class T2, int I> class A { }; // Spcialisation n 1 de la classe : template <class T, int I> class A<T, T*, I> { }; // Spcialisation n 2 de la classe : template <class T1, class T2, int I> class A<T1*, T2, I> { }; // Spcialisation n 3 de la classe : template <class T> class A<int, T*, 5> { }; // Spcialisation n 4 de la classe : template <class T1, class T2, int I> class A<T1, T2*, I> { };

On notera que le nombre des paramtres template dclars la suite du mot cl template peut varier, mais que le nombre de valeurs fournies pour la spcialisation est toujours constant (dans lexemple prcdent, il y en a trois). Les valeurs utilises dans les identicateurs template des spcialisations doivent respecter les rgles suivantes :

une valeur ne peut pas tre exprime en fonction dun paramtre template de la spcialisation ;
template <int I, int J> struct B { }; template <int I> struct B<I, I*2> { };

// Erreur ! // Spcialisation incorrecte !

le type dune des valeurs de la spcialisation ne peut pas dpendre dun autre paramtre ;
template <class T, T t> struct C { };

213

Chapitre 11. Les template

template <class T> struct C<T, 1>; // Erreur ! // Spcialisation incorrecte !

la liste des arguments de la spcialisation ne doit pas tre identique la liste implicite de la dclaration template correspondante.

Enn, la liste des paramtres template de la dclaration dune spcialisation ne doit pas contenir des valeurs par dfaut. On ne pourrait dailleurs les utiliser en aucune manire.

11.5.3. Spcialisation dune mthode dune classe template


La spcialisation partielle dune classe peut parfois tre assez lourde employer, en particulier si la structure de donnes quelle contient ne change pas entre les versions spcialises. Dans ce cas, il peut tre plus simple de ne spcialiser que certaines mthodes de la classe et non la classe complte. Cela permet de conserver la dnition des mthodes qui nont pas lieu dtre modies pour les diffrents types, et dviter davoir rednir les donnes membres de la classe lidentique. La syntaxe permettant de spcialiser une mthode dune classe template est trs simple. Il suft en effet de considrer la mthode comme une fonction template normale, et de la spcialiser en prcisant les paramtres template utiliser pour cette spcialisation. Exemple 11-14. Spcialisation de fonction membre de classe template
#include <iostream> using namespace std; template <class T> class Item { T item; public: Item(T); void set(T); T get(void) const; void print(void) const; }; template <class T> Item<T>::Item(T i) { item = i; } // Accesseurs : template <class T> void Item<T>::set(T i) { item = i; }

// Constructeur

214

Chapitre 11. Les template


template <class T> T Item<T>::get(void) const { return item; } // Fonction daffichage gnrique : template <class T> void Item<T>::print(void) const { cout << item << endl; } // Fonction daffichage spcialise explicitement pour le type int * // et la mthode print : template <> void Item<int *>::print(void) const { cout << *item << endl; }

11.6. Mot-cl typename


Nous avons dj vu que le mot cl typename pouvait tre utilis pour introduire les types gnriques dans les dclarations template. Cependant, il peut tre utilis dans un autre contexte pour introduire les identicateurs de types inconnus dans les template. En effet, un type gnrique peut trs bien tre une classe dnie par lutilisateur, lintrieur de laquelle des types sont dnis. An de pouvoir utiliser ces types dans les dnitions des template, il est ncessaire dutiliser le mot cl typename pour les introduire, car a priori le compilateur ne sait pas que le type gnrique contient la dnition dun autre type. Ce mot cl doit tre plac avant le nom complet du type :
typename identificateur

Le mot cl typename est donc utilis pour signaler au compilateur que lidenticateur identificateur est un type. Exemple 11-15. Mot-cl typename
class A { public: typedef int Y; }; template <class T> class X { typename T::Y i; };

// Y est un type dfini dans la classe A.

// La classe template X suppose que le // type gnrique T dfinisse un type Y.

215

Chapitre 11. Les template

X<A> x;

// A peut servir instancier une classe // partir de la classe template X.

11.7. Fonctions exportes


Comme on la vu, les fonctions et classes template sont toutes instancies lorsquelles sont rencontres pour la premire fois par le compilateur ou lorsque la liste de leurs paramtres est fournie explicitement. Cette rgle a une consquence majeure : la dnition complte des fonctions et des classes template doit tre incluse dans chacun des chiers dans lequel elles sont utilises. En gnral, les dclarations et les dnitions des fonctions et des classes template sont donc regroupes ensemble dans les chiers den-tte (et le code ne se trouve pas dans un chier C++). Cela est la fois trs lent (la dnition doit tre relue par le compilateur chaque fois quun template est utilis) et ne permet pas de protger le savoir faire des entreprises qui ditent des bibliothques template, puisque leur code est accessible tout le monde. An de rsoudre ces problmes, le C++ permet de compiler les fonctions et les classes template, et ainsi dviter linclusion systmatique de leur dnition dans les chiers sources. Cette compilation se fait laide du mot cl export. Pour parvenir ce rsultat, vous devez dclarer export les fonctions et les classes template concernes. La dclaration dune classe template export revient dclarer export toutes ses fonctions membres non inline, toutes ses donnes statiques, toutes ses classes membres et toutes ses fonctions membres template non statiques. Si une fonction template est dclare comme tant inline, elle ne peut pas tre de type export. Les fonctions et les classes template qui sont dnies dans un espace de nommage anonyme ne peuvent pas tre dclares export. Voir le Chapitre 10 plus de dtails sur les espaces de nommage. Exemple 11-16. Mot-cl export
export template <class T> void f(T); // Fonction dont le code nest pas fourni // dans les fichiers qui lutilisent.

Dans cet exemple, la fonction f est dclare export. Sa dnition est fournie dans un autre chier, et na pas besoin dtre fournie pour que f soit utilisable. Les dnitions des fonctions et des classes dclares export doivent elles aussi utiliser le mot cl export. Ainsi, la dnition de f pourra ressembler aux lignes suivantes :
export template <class T> void f(T p) { // Corps de la fonction. return ; }

Note : Aucun compilateur ne gre le mot cl export ce jour.

216

Chapitre 12. Conventions de codage et techniques de base


Lapprentissage de la syntaxe dun langage est certainement la chose la plus facile faire en programmation. Mais connatre la syntaxe est loin dtre sufsant pour raliser des programmes performants, ables et lgants. La programmation ncessite en effet de bien connatre les techniques de base et les fonctionnalits offertes par les bibliothques pour pouvoir les combiner de manire lgante et efcace. Ce chapitre a donc pour but daller au del des considrations syntaxiques vues dans les chapitres prcdents, et de prsenter quelques conseils et astuces permettant de raliser des programmes plus ables et plus srs. Il ne prsentera bien entendu pas toutes les techniques qui peuvent tre mises en place, tant ces techniques peuvent tre diverses et varies, mais il essaiera de dbroussailler un peu le terrain. Les conseils donns ici nont videmment pas force de loi. Cependant, ils peuvent vous sensibiliser sur certains points dont peu de programmeurs dbutants ont connaissance. Ils vous donneront peuttre aussi des ides et vous aideront sans doute dnir vos propres rgles de codage. Vous tes donc libres de vous en inspirer si vous ne voulez pas les appliquer telles quelles. Nous verrons dabord limportance des conventions de codage. Quelques mthodes classiques permettant de raliser des programmes ables et volutifs seront ensuite abordes. Enn, nous prsenterons les principales considrations systme quil est prfrable davoir lesprit lorsque lon ralise un programme.

12.1. Conventions de codage


Une convention de codage est un ensemble de rgles dnissant le style et la manire de programmer. Le but des conventions de codage est souvent de garantir une certaine uniformit dans les codes sources produits par plusieurs personnes dun mme groupe, mais elles peuvent galement permettre de rduire les risques de bogues de bas niveau.

12.1.1. Gnralits
De nombreuses conventions de codage ont t dnies. Quasiment chaque entreprise, voire mme chaque groupe de dveloppeurs, utilise une convention de codage qui lui est propre. Les conventions de codage sont donc nombreuses, bien entendu non standardises, et trs souvent incompatibles. De plus, elles sont souvent trs restrictives et imposent des rgles qui nont pas toujours une autre justication que des considrations de style. Les rgles de ces conventions relvent donc de lesthtique, et comme les gots et les couleurs sont quelque chose de personnel, peu de programmeurs les considrent comme une bonne chose. Alors, ces conventions doivent-elles tre ignores ? Sans doute pas. Une mauvaise convention est toujours prfrable une anarchie du code, car elle permet au moins de xer un style de programmation. Si lon ne devait ne donner quune seule rgle, ce serait sans doute de sadapter lenvironnement dans lequel on se trouve, et de faire du code mimtique . En effet, lanarchie ne va pas trs bien avec la rigueur quun code doit avoir, et lhtrognit des solutions choisies multiplie les risques de bogues et les dpendances des programmes.

217

Chapitre 12. Conventions de codage et techniques de base


Note : Certains programmeurs ont une vision artistique de la programmation. Cela nest pas contradictoire avec le respect de rgles. Aprs tout, une dmonstration mathmatique bien articule a galement une certaine beaut. Mais lessentiel est qu partir dun certain niveau, seules les procdures et les rgles de qualit peuvent garantir la abilit dun programme.

En fait, outre la cohrence, les conventions de codage permettent de prvenir les bogues, en imposant une manire de travailler qui suit les rgles de lart. En effet, contrairement dautres langages plus stricts et qui interdisent certaines fonctionnalits ou certaines constructions syntaxiques risques, le C/C++ permet de raliser virtuellement nimporte quoi. Il est donc trs facile de faire des erreurs grossires en C/C++. Ces erreurs, bien que conduisant gnralement le programme une issue fatale, ne sont gnralement pas des erreurs complexes. Cependant, elles peuvent tre difciles localiser et diagnostiquer a posteriori, mme si, une fois diagnostiques, elles peuvent tre corriges de manire extrmement facile. Or, la plupart de ces erreurs peuvent tre vites a priori en suivant des rgles lmentaires dhygine de codage, pourvu que le programmeur sy tienne. Cest pour cela que, comme le dit ladage, mieux vaut prvenir que gurir, et respecter des rgles de base justies par une argumentation pratique ne peut pas faire de mal. En rsum, les bonnes conventions de codage sintressent plus au fond qu la forme. Elles ne dnissent pas de rgles contraignantes et inutiles, mais au contraire facilitent le dveloppement et rduisent les cots de mise au point, en liminant les facteurs de bogues la source et en augmentant la qualit du logiciel par une cohrence accrue. Nous prsenterons donc quelques-unes de ces rgles et leur justication pratique dans les sections suivantes, en les classant suivant les principaux objectifs des conventions de codage.

12.1.2. Lisibilit et cohrence du code


La lisibilit et la cohrence du code source est une aide sa comprhension, et facilite sa mise au point, sa maintenance et sa rutilisation. Elle peut galement tre une aide pour le programmeur luimme, pour se retrouver dans son propre code source. Tout ce qui peut amliorer la lisibilit et la cohrence du code est donc une bonne chose, et les rgles suivantes y contribuent directement.

12.1.2.1. Rgles de nommage des identicateurs


La rgle fondamentale pour faciliter la lecture du code est de sassurer quil est auto-descriptif autant que faire se peut. Pour cela, il faut sassurer que les noms des identicateurs soient le plus proche de leur smantique possible, et viter au maximum les conventions implicites. N1. Le nom dune fonction doit dire ce quelle renvoie. N2. Le nom dune procdure doit dire ce quelle fait. N3. Le nom dune variable doit dire ce quelle est ou ltat quelle reprsente. Ces rgles sont donc trs importantes, car elles permettent de garantir que lon sait exactement ce quoi sert une entit la simple lecture de son nom. Ainsi, le risque de mal utiliser cette entit est rduit. Bien entendu, ces rgles deviennent vitales dans le cas de code partag entre plusieurs projets ou plusieurs programmeurs, ou dans le cas de code de bibliothque susceptible dtre rutilis dans un contexte a priori inconnu.

218

Chapitre 12. Conventions de codage et techniques de base Exemple 12-1. Noms autodescriptifs
unsigned get_best_score(); int connect_to_http_server(const char *);

N4. Nommer les entits suivant la smantique de leur implmentation et non suivant la fonctionnalit fournie. Cette rgle est un corollaire des rgles prcdentes. Elle permet de garantir une bonne utilisation de lentit en question. Elle implique, en cas de changement dimplmentation susceptible de modier le comportement, un renommage. Cela est le meilleur moyen pour dtecter facilement les utilisations qui deviennent incompatibles suite ce changement. Inversement, un nommage bas uniquement sur la fonctionnalit fournie est susceptible de provoquer une dgradation des performances, voire des effets de bords si plusieurs fonctionnalits sont combines de manire incompatibles du point de vue implmentation. Par exemple, si une classe doit tre dnie spciquement pour grer une le dobjet, il est prfrable de lui donner un nom explicite quant ses capacits relles plutt quun nom abstrait reprsentant la fonctionnalit obtenue :
class CQueue // Imprcis, peut tre utilis mauvais escient. { // Implmentation quelconque. ... public: void add_tail(object_t *); object_t *get_head(); object_t *search(const char *name); }; class CList // Prcis, on ne lutilisera pas si on a besoin { // de rechercher un lment. // Implmentation base sur une liste. ... public: void add_tail(object_t *); object_t *get_head(); // Recherche possible, mais fatalement lente car base // sur un parcours des lments de la liste : object_t *search(const char *name); };

Note : Cette rgle est facultative si lencapsulation des fonctionnalits est parfaite et labstraction totale. Cependant, raliser une abstraction totale des structures de donnes est une chose rarement ralisable ou mme dsirable, car cela conduit gnralement une implmentation structurellement mauvaise ou inadapte au problme rsoudre. Voir les rgles doptimisation pour plus de dtails ce sujet. Cette rgle est donc surtout importante pour les bibliothques utilitaires gnriques, qui fournissent des fonctionnalits dont on ne peut prvoir a priori le cadre dutilisation. Il est dans ce cas prfrable de nommer les entits relativement leur implmentation et de laisser au programmeur le choix des fonctionnalits et des classes utilises en fonction de lutilisation quil veut en faire.

219

Chapitre 12. Conventions de codage et techniques de base N5. En cas de surcharge, conserver la smantique initiale de la mthode surcharge. Cette rgle est imprative pour prserver les rgles prcdentes. Elle est essentielle pour viter les effets de bords. Par exemple, si lon rednit un oprateur daddition pour une classe implmentant un type de donnes, il est essentiel que lopration fournie soit en rapport avec la notion daddition ou de regroupement. Cela vaut bien entendu aussi pour toutes les mthodes surchargeables des classes de base : il faut en conserver la smantique tout prix. N6. Utiliser la notation hongroise simplie pour les variables et des donnes membres. La notation hongroise simplie propose de qualier les variables et les donnes membres dun prxe permettant den dcrire la nature et le type. Cela permet de dterminer le type dune variable sans avoir rechercher sa dclaration, et de dtecter directement la lecture du code des erreurs de base. Il sagit donc dune technique complmentaire aux rgles de nommage descriptives prcdentes, qui permet de dcrire la nature des entits manipules et non plus seulement ce quelles sont. De nombreuses variantes de cette notation sont utilises de part le monde, parce que les prxes ne sont souvent pas uniques et laissent une part dambigut, qui est en gnral leve par le contexte du code. Cette technique nest donc pas parfaite, mais elle est prfrable aucune caractrisation de la nature des variables. Le tableau suivant vous donnera un jeu de prxes possibles, directement drivs des noms anglais des types de donnes et choisis pour minimiser les conits entre les prxes. Tableau 12-1. Prxes en notation hongroise simplie Type Boolens Caractres simples Caractres larges Entiers courts Entiers natifs Entiers longs Taille (type size_t) Type intgral non sign Type intgral sign numrations Flottants en simple prcision Flottants en double prcision Chane C simple Chane C en caractres larges Tableaux Pointeurs Prxe b c wc h i l s u s e f d sz wsz a p

Les prxes doivent tre combins pour caractriser les types complexes. Lordre gnralement adopt pour les prxes est le suivant : prxe des tableaux ou des pointeurs, prxe du signe, prxe de taille et prxe du type de donnes.

220

Chapitre 12. Conventions de codage et techniques de base Exemple 12-2. Notation hongroise simplie
unsigned int uiItemCount = 0; long int lOffset = 0; const char *szHelloMessage = "Hello World!"; bool bSuccess = false; void *pBuffer = NULL; double adCoeffs[256];

N7. Prxer les donnes membres par m_ , les variables globales par g_ , et les constantes par k_ . Cette rgle permet de garantir quil ny a pas de conit entre des variables homonymes de portes diffrentes. Elle contribue donc limiter les effets de bords implicites et permet au programmeur de savoir en permanence ce quil manipule.
const unsigned k_uiMaxAge = 140; class CMan { private: unsigned m_uiAge; ... }; CPeople g_World; // Constante.

// Donne membre

// Variable globale.

12.1.2.2. Rgles de cohrence et de portabilit


Les rgles de cohrence permettent dobtenir un code source plus facile lire, maintenir et faire voluer. En effet, ces rgles permettent de savoir exactement comment sont nommes les entits du programme, et donc vitent davoir rechercher leur dnition en supprimant tout doute sur son nom. Il est noter que la plupart des programmeurs nissent par acqurir de manire inconsciente un jeu de rgles de cohrence, et quils considrent leurs anciens programmes comme non structurs aprs cela. En gnral, ce nest pas la formalisation des rgles de cohrence qui prime, mais le fait mme quelles existent. De ce fait, les rgles donnes ci-dessous nimposent aucun choix, mais servent seulement donner une ide de ce qui peut tre pris en compte pour garantir la cohrence du code source. Il faudra toutefois veiller ne pas dnir de rgles trop nombreuses ou trop spciques, an de ne pas encombrer lesprit du programmeur et risquer ainsi de rduire sa productivit ou, pire, de lui faire rejeter lensemble des rgles en raison de leurs trop fortes contraintes. Ici donc, tout est affaire de mesure, dautant plus que la nalit de ces rgles nest pas directe. C1. tre cohrent et consistant dans la forme des noms des identicateurs. Les noms didenticateurs doivent avoir toujours la mme forme. Gnralement, ils sont constitus de verbes et de noms. On peut imposer lemploi dun verbe et la manire de le conjuguer pour les procdures par exemple. De mme, les mots utiliss dans les identicateurs peuvent tre spars par des soulignements bas (caractre _), ou tout simplement accols mais identis par une majuscule en tte de chaque mot. Gnralement, dnir comment accoler ces noms suft. Les deux conventions les plus utilises sont :

221

Chapitre 12. Conventions de codage et techniques de base

soit de coller les mots et de les mettre en majuscules (par exemple CreateFileMapping), sauf ventuellement la premire lettre ; soit de les mettre en minuscules et de les sparer par des soulignements (par exemple sched_get_priority_max).

C2. viter les rednitions de types fondamentaux. Nombre de programmeurs ont ressenti le besoin de rednir les types fondamentaux, en raison de leur manque de portabilit. Parfois, la raison est la recherche dune abstraction vis vis du type de donnes choisie. Cette technique est viter tout prix, pour trois raisons :

premirement, des types portables sont prsent disponibles dans len-tte stdint.h ; deuximement, le choix dun type est un choix de conception sur lequel on ne doit pas avoir revenir, et abstraire le type des instances est gnralement inutile ; enn, les programmes nissent par manipuler une foison de types a priori identiques, qui ne servent rien, et que lon doit convertir de lun lautre en permanence.

Exemple 12-3. Exemple de types redondants


guint32 UINT uint DWORD // // // // Inutile, utiliser uint32_t. Inutile, utiliser unsigned. Inutile, utiliser unsigned. Inutile et imprcis, utiliser uint32_t.

La situation peut devenir encore plus grave avec les types plus complexes comme les types de chanes de caractres (LPWSTR, BSTR, LPOLESTR, CString, CComBSTR, bstr_t, wstring, etc. sous Windows par exemple), car la conversion nest dans ce cas pas immdiate. Beaucoup de programmes nissent ainsi par faire des conversions en srie, ce qui grve fortement les performances. C3. Ne pas mlanger les types de gestion derreur. Entre les exceptions, les codes de retour et les goto, il faut faire son choix. Mlanger les techniques de gestion derreur conduit directement du code complexe, illisible et tout simplement non able (voir la Chapitre 8 pour plus de dtails ce sujet). Respecter cette rgle nest pas toujours facile, surtout si lon utilise des bibliothques de provenances diffrentes. Dans ce cas, le plus simple est dappliquer la rgle en vigueur dans le programme principal, dont le code source est gnralement majoritaire. C4. Fixer les codes derreurs et les classes dexception globalement. Une fois la technique de gestion des erreurs dnie, il est essentiel de dnir une politique globale de dnition des erreurs. Cette politique est particulirement importante si la gestion des erreurs se fait via des codes de retour, car dans ce cas il peut y avoir conit entre plusieurs programmeurs. Dans le cas des exceptions, les conits sont vitables, mais le traitement des erreurs gnriques via des exceptions polymorphiques peut induire des erreurs inattendues si une classe dexception nest pas bien situe dans la hirarchie des exceptions. C5. Fixer la langue de codage. Les noms didenticateurs doivent tre soit en anglais (recommand pour les programmes vocation internationale ou Open Source), soit dans la langue maternelle du dveloppeur, mais jamais dans un joyeux mlange des deux. Dans le cas contraire, la situation peut devenir catastrophique, surtout pour

222

Chapitre 12. Conventions de codage et techniques de base pour les codes derreurs utiliss couramment ou les fonctions de bibliothque. Lexemple suivant prsente quelques cas de franglais relativement classiques :
create_fichier ERROR_SATURE IsValideFilePath // Franglais. // Franglais. // Faute dorthographe anglaise.

Souvent, les dveloppeurs non anglophones ne parviendront pas respecter cette rgle si langlais est choisi, ou feront des fautes danglais normes qui ne seront pas du meilleur effet. Si le programme est vocation locale, autant dans ce cas ne pas les ennuyer et choisir la langue maternelle. La lecture du programme peut toutefois en ptir, en raison du fait que les mots cls resteront toujours en anglais (bien entendu, il est absolument interdit de dnir des macros localises pour chaque mot cl du langage !). C6. Utiliser des noms de chiers en minuscules et sans espaces. Certains systmes ne grent pas la casse des noms de chiers. Il est donc impratif de xer une rgle, car les programmes qui nen xent pas ne seront ds lors pas portables. En effet, le transfert dun code source fait sous Unix vers Windows peut ne pas tre ralisable si les chiers sont homonymes sur le systme cible. Inversement, des chiers sources provenant de Windows ne seront a priori pas trouvs par le prprocesseur dans les directives #include si la casse nest pas la mme dans la directive et dans le nom de chier (chose que les prprocesseurs pour Windows ne vrient bien sr pas).
#include "Mauvais Nom.h" // Non portable (un espace et casse variable).

C7. Utiliser le sparateur / dans les directives #include. Certains systmes dexploitation utilisent le sparateur \ pour les noms de chiers. Utiliser ce caractre est non portable. Le caractre / tant compris par tout prprocesseur standard, et ce quel que soit le systme utilis, autant nutiliser que lui. De plus, en C, le caractre \ doit tre doubl dans les chanes de caractres, y compris dans les directives #include. Cela peut gnralement tre vit sur les systmes qui ont bidouill leur prprocesseur, mais le faire provoquera immanquablement une erreur de compilation sur les systmes conformes.
#include "utilities\containers.h" #include "utilities\\containers.h" #include "utilities/containers.h" // Incorrect // Toujours incorrect // Correct

C8. Faire des en-ttes protgs. Un chier den-tte doit pouvoir tre inclus plusieurs fois. Il faut donc utiliser les directives de compilation conditionnelle pour viter des erreurs dues des dclarations multiples (surtout en C++, la structure des classes tant dnie dans les chiers den-tte). On nutilisera pas les mcanismes propritaires non portables, du type #pragma once (ils ne font gagner que deux lignes par chier au prix dun non-respect des standards). On remplacera systmatiquement ceux ajouts automatiquement par les gnrateurs de code conus pour rendre les programmes dpendants dune plateforme spcique.
#ifndef __MONPROJET_FICHIER_H__ #define __MONPROJET_FICHIER_H__ // Contenu du fichier den-tte

223

Chapitre 12. Conventions de codage et techniques de base

#endif // __MONPROJET_FICHIER_H__

12.1.2.3. Rgles de formatage et dindentation


Les rgles dindentation et de forme relvent du style. Elles sont soumises aux mmes contraintes que les rgles de cohrence, laquelle elles contribuent. Autrement dit, elles ne devront pas tre contraignantes, et les choix quelles imposent sont moins importants que le fait quelles existent. F1. Indenter le code. Lindentation est une aide la lecture et au codage. Elle permet galement de voir plus rapidement la structure du programme. Cependant, les styles de codage, ainsi que les caractres utiliss pour indenter, varient grandement. La rgle est donc ici de se plier aux conventions utilises pour le projet sur lequel on travaille. En particulier, les caractres utiliser pour lindentation peuvent tre des espaces ou des tabulations. Certaines personnes prtendent que seuls les espaces doivent tre utiliss pour indenter, car la taille des tabulations nest pas xe et varie selon les diteurs de texte. De plus, les tabulations font souvent huit caractres sur les imprimantes texte, ce qui empche dimprimer le code source correctement. Cependant, le caractre de tabulation a clairement pour but de raliser un dcalage dune colonne, et il ny a par consquent aucune raison de ne pas lutiliser pour cela. Prtendre que cela est gnant pour des raisons doutils nest pas honnte de nos jours, car tous les outils permettent depuis longtemps de paramtrer la taille des tabulations (une taille de quatre caractres est gnralement pratique) et dimprimer correctement un listing. Quoi quil en soit, quel que soit le choix effectu, il faut sy tenir sur lensemble des chiers sources : ds que lon utilise des espaces, il ne faut utiliser que cela pour les indentations (et inversement). Dans le cas contraire, lindentation ne sera pas la mme pour les lignes qui utilisent des tabulations et celles qui utilisent des espaces, et si plusieurs programmeurs travaillent sur un mme projet avec des tailles de tabulation diffrentes, le code ne sera pas lisible sans recongurer les diteurs pour chaque chier source. F2. Commenter le code. Sans commentaire. Toutefois, on prendra conscience du fait que les commentaires utiles sont prfrables aux commentaires tautologiques, et quil faut donc sefforcer de faire des commentaires clairs et concis. Gnralement, on ne commente pas un code dj clair et simple, mme si cela nest pas interdit. En revanche, il est important de commenter les structures de donnes, les fonctions et mthodes. Cela doit se faire de prfrence dans les chiers den-tte, car cest ce chier qui constitue la dclaration de linterface de programmation, et souvent le seul chier source distribu dans le cas des bibliothques. Le formalisme des commentaires pour ces fonctions et mthodes doit permettre de les utiliser sans risque. Les informations importantes sont tout particulirement les suivantes :

les paramtres en entre ; les paramtres en sortie ; ce que renvoie la fonction ou ce que fait la mthode ; les codes de retours, exceptions et autre cas derreurs ; les prconditions (dans quelles circonstances ou quel ordre les fonctions doivent tre appeles) ; les effets de bord et les remarques complmentaires.

224

Chapitre 12. Conventions de codage et techniques de base

Exemple 12-4. Commentaire descriptif dune fonction


/* Normalise un vecteur. Entre : vector : Pointeur sur le vecteur devant tre normalis. Sortie : pResult : Vecteur normalis. pNorm : Norme du vecteur avant normalisation. Retour : 0 si succs, -1 si lun des pointeurs de sortie est nul, -2 si le vecteur normaliser est nul. Prcondition : Aucune Effets de bords : Aucun Description : Le vecteur normalis est le vecteur colinaire au vecteur dentre et dont la norme est 1. Il est calcul en divisant chaque composante du vecteur normaliser par sa norme. Ce calcul est impossible si le vecteur en entre est le vecteur nul. */ extern int get_normalize(const vector_t &vector, vector_t *pResult, double *pNorm);

On notera que des outils performants permettent dextraire la documentation partir du code. Les documents gnrs par ces outils ne peuvent toutefois pas se substituer une documentation complte. En particulier la documentation prsentant les concepts gnraux dun programme ou dune bibliothque, ainsi que les documents de conception, qui doivent tre ralises avant le code de toutes manires, restent ncessaires. F3. Commenter les #endif . Les #if et #endif ne se laissent pas indenter facilement. De ce fait, ils apparaissent plat dans les codes sources. Il est donc utile de commenter chaque #endif en rappelant la condition du #if correspondant. F4. Utiliser les espaces dans les ASCII art. Il est possible de faire un schma en ASCII art dans un code source. Dans ce cas, il ne faut surtout pas utiliser le caractre de tabulation pour aligner les diffrentes parties du schma. En effet, la largeur de ce caractre dpend de loutil utilis pour visualiser le code source. De ce fait, il ne faut utiliser que des espaces pour les blancs dans les schmas de ce type.

12.1.3. Rduction des risques


De nombreuses erreurs de bas niveau peuvent tre vites ds la phase de codage simplement en respectant quelques rgles. Les rgles suivantes permettent donc daugmenter la abilit du code et de faire gagner du temps pendant la phase de mise au point.

225

Chapitre 12. Conventions de codage et techniques de base

12.1.3.1. Rgles de simplicit


Rechercher la simplicit au sens gnral est la rgle dor. Les anglophones disent KISS (abrviation de Keep It Simple Stupid ), pour bien traduire le fait que ltre humain est limit et nest pas capable de comprendre les choses complexes. De plus, les choses ont sufsamment tendance se compliquer toutes seules pour que le programmeur nen rajoute pas. S1. Utiliser des algorithmes simples. La complexit dun algorithme est un passeport direct pour les erreurs, les effets de bords et les comportements non prvus. En effet, le comportement dun programme peut trs vite devenir combinatoire, et il est facile dobtenir des algorithmes moyennement complexes ne pouvant dj plus tre tests. S2. viter les astuces. Les astuces de programmation ne plaisent qu celui qui les fait. Les autres perdent leur temps analyser le code pour savoir ce quil fait. Elles sont donc proscrire, dautant plus quelles constituent un facteur de risque inutile. S3. Ne pas faire doptimisations locales. Les compilateurs actuels sont parfaitement capables de faire les optimisations locales. Le programmeur qui fait de telles optimisations perd donc son temps et accrot les risques de bogues. Au pire, il peut mme dgrader les performances, en trompant le compilateur sur ses vritables intentions et en le mettant dans une situation o il ne peut plus appliquer dautres optimisations plus efcaces. Enn, les optimisations les plus efcaces sont gnralement structurelles. Voir la section Optimisations ce sujet.

12.1.3.2. Rgles de rduction des effets de bords


Les effets de bords sont inhrents et mme ncessaires aux langages impratifs, puisque ceux-ci se basent sur la modication de ltat du programme pour lexcuter. Cependant, il faut distinguer les effets de bords dsirs (le traitement que le programme doit faire) des effets de bords indsirs (les bogues). Les rgles suivantes ont donc pour but dviter les constructions risques et de cloisonner les donnes accessibles aux traitements qui en ont besoin. B1. liminer les variables globales. Les variables globales sont accessibles de lensemble du programme et le risque quelles soient modies par inadvertance ou dans le cadre dun effet de bord non document dune fonction ou dune procdure est maximum. Or, dans la majorit des cas, une variable na pas besoin dtre globale et son accs peut tre rduit une portion de code rduite. Il est donc impratif de rduire au maximum la porte des variables et de les dclarer au plus proche de leur utilisation. Cela implique de dclarer les variables le plus proche possible du bloc dinstructions qui en a besoin, voire, en C++, lendroit mme o elle est utilise (et non au dbut du bloc dinstructions). De mme, les donnes manipules par les fonctions et les mthodes devront, autant que faire se peut, tre fournies en paramtre et non accdes directement. Cela permet galement de rendre ces mthodes et ces fonctions autonomes et utilisables de manire indpendante de tout contexte, facilitant ainsi leur rutilisation et leurs tests unitaires. B2. Utiliser les objets. La notion dobjet permet de rduire la porte des variables en les encapsulant et en les regroupant

226

Chapitre 12. Conventions de codage et techniques de base avec le code qui les utilise, et de fournir laccs ces variables implicitement dans les mthodes de la classe via le pointeur sur lobjet. Le principal avantage de la programmation objet est donc sans doute, avec la structuration du code, de permettre la rduction de lutilisation des variables globales. Il ne faut donc pas sen priver. On gardera toutefois lesprit quune donne membre dune classe nest rien dautre quune variable globale pour toutes les mthodes de cette classe. La rgle de localit sapplique donc galement aux donnes membres, et si une mthode est la seule utiliser une donne ou si cette donne peut tre fournie en paramtre sans prjudice de la lisibilit du code, alors cette donne doit tre dclare localement et non en tant que donnes membre. B3. Ne pas exposer la structure interne. Sil est important de conserver lesprit la nature de limplmentation des classes et des fonctions que lon utilise, leur implmentation elle-mme doit tre inaccessible autant que faire se peut. En effet, laisser un accs incontrl limplmentation dune fonctionnalit est une manire de rendre globale sa structure ou son mcanisme. De plus, cest un frein aux volutions ultrieures du programme, car la modication de limplmentation ne peut plus se faire de manire aussi facile si lensemble du code sappuie sur des dtails de cette implmentation. Les techniques dencapsulation sont nombreuses, et la programmation objet permet de les mettre en uvre facilement. En particulier, on sassurera que les droits daccs aux donnes membres sont minimaux et que, pour les classes les plus importantes, les fonctionnalits offertes sont structures et exposes via la notion dinterface. B4. viter les encapsulations effets de bord. Les interfaces et les accesseurs fournis doivent imprativement viter de raliser des oprations susceptibles davoir dautres effets que les effets documents ou dductibles des noms de leurs identicateurs. Autrement dit, ils ne doivent en aucun cas avoir deffets de bords. Cette rgle est un corollaire des rgles de nommage et de rduction des risques dj vues, appliques aux techniques objet. Par exemple, les accesseurs en lecture ne doivent pas modier ltat de lobjet auxquels ils sappliquent. Les accesseurs en criture sont bien entendu obligs davoir des effets de bords, mais ceux-ci sont documents et connus de celui qui les utilise. Exemple 12-5. Accesseur effet de bord
int CTimer::GetCurrentValue() { NotifyClients(); // Effets de bords incontrlables ! return m_iValue; }

B5. Utiliser const. Une manire simple de rduire les effets de bords est dutiliser le mot cl const. Cela permet de garantir, via les mcanismes de typage du langage, que les donnes qui ne doivent pas tre modies ne le seront pas par inadvertance. De plus, les compilateurs sont souvent capables doptimiser la manipulation des donnes dont ils savent quelles ne peuvent tre modies par le code qui les utilise. Ils nont pas maintenir un certain nombre de rgles de cohrence et peuvent donc appliquer des optimisations plus pousses. Ce peut tre le cas par exemple pour le passage par valeur de types complexes : un passage par valeur constante peut souvent tre transform en un passage par rfrence constante et viter ainsi des copies coteuses dobjets.

227

Chapitre 12. Conventions de codage et techniques de base B6. Passer les paramtres de retour par pointeur. B7. Passer les paramtres dentre par rfrence constante ou par valeur. Les paramtres de retour des mthodes et des fonctions doivent tre clairement identis comme tels. Pour cela, il est conseill dutiliser des pointeurs, forant ainsi le programmeur prendre conscience du fait que les variables dont il fournit ladresse seront modies. Lutilisation des rfrences pour les paramtres de retour est dconseille, car elle ne permet pas de distinguer, lors de lcriture de lappel de la mthode, les variables qui sont susceptibles dtre modies de celles qui ne le seront pas. Il est donc facile, avec les passages de paramtres par rfrence, dobtenir des effets de bords indsirs. De la mme manire, les paramtres dentre devront tre passs soit par valeur, soit par rfrence constante sils sont de grande taille. En aucun cas ils ne devront tre passs par rfrence non constante, car cela permettrait de modier les donnes fournies par lappelant. Enn, les paramtres dentre / sortie peuvent, exceptionnellement, tre passs par rfrence, si la smantique de la fonction ou de la procdure est sufsamment claire pour quil ny ait pas dambigut sur le fait que ces paramtres peuvent tre modis lors de lappel. Exemple 12-6. Passage de paramtres en entre/sortie
void get_clipping_rectangle(const window_t &window, rect_t *pRect); int set_clipping_rectancle(window_t &window, const rect_t &rect);

B8. viter les macros. Les macros sont, par dnition, un moyen de raliser plusieurs oprations avec une criture simplie. De ce fait, elles sont susceptibles de raliser des effets de bords de manire trs simple. De plus, les macros sont difcilement dbogables, peuvent valuer plusieurs fois leurs paramtres dentre, et ne sont pas soumises aux contrles de vrication des types du langage. Il est donc important dviter au maximum les macros, et de les rserver uniquement la dnition de constantes utilises dans les directives de compilation conditionnelle. Noubliez pas que les macros peuvent souvent tre avantageusement remplaces par des fonctions inline.

12.1.3.3. Rgles de prvention


Un code peut tre utilis dans des conditions qui ntaient pas connues lors de sa conception, mme en utilisation nominale, car les programmes sont des entits complexes dans lesquels lensemble des choix de lutilisateur peut rarement tre prvu. De plus, un programme peut tre appel voluer et subir des modications du contexte dutilisation de son code source. Le code source peut galement tre rcupr dans un autre programme, le mettant dans une situation a priori non prvue par son dveloppeur. Ce genre de situation peut amener des erreurs dues des hypothses implicites ou des impasses qui ont t faites lors du codage initial. Les rgles suivantes permettent de garantir quun code restera able en toute circonstance, principalement en xant le contexte et en forant le programmeur prvoir les chemins dtourns. P1. Fixer et documenter les conventions dappel. Les conventions dappel sont les conventions qui dcrivent la manire dont les mthodes et les fonctions doivent tre appeles. Il existe par exemple des conventions dappel pour chaque langage ou chaque systme, an de dcrire les mcanismes utiliss pour effectuer les passage des paramtres (ordre de passage des paramtres, types de donnes natifs utiliss, qui de lappel ou lappelant doit se charger de la destruction des paramtres, etc.). Mais il est galement possible de dnir des conven-

228

Chapitre 12. Conventions de codage et techniques de base tions dappel de plus haut niveau, qui seront utilises pour tout un programme. Ces conventions doivent tre dnies et appliques globalement, tout comme les conventions de codage, pour tre efcaces. Elles doivent au minimum dcrire les mcanismes dallocation mmoire utiliss lorsque des blocs mmoires doivent tre transfrs dune fonction une autre, et la manire de remonter les erreurs. Les valeurs de codes de retour ne feront pas exception la rgle (an dviter, par exemple, que la valeur 0 signale tantt une erreur, tantt un succs en retour de fonction). Le problme de lallocation mmoire est complexe. Supposons que lon dsire appeler une fonction qui retourne une chane de caractres. Cette chane doit tre stocke dans une zone mmoire, dont la taille est a priori dpendante du rsultat de la fonction. Il est donc courant dutiliser une allocation dynamique de mmoire pour retourner le rsultat. Mais si le code qui utilise cette fonction et la fonction elle-mme utilisent des conventions dappel diffrentes, le programme risque de faire des erreurs mmoire trs grave et planter soit immdiatement, soit, pire encore, bien aprs lutilisation de la fonction. Par exemple, la fonction strdup de la bibliothque C des systmes compatibles Unix 98 permet de dupliquer une chane de caractres et den retourner la copie dans un tampon allou par la fonction malloc :
// Duplication dune chane de caractres : char *szCopy = strdup("Hello World!");

La libration de la mmoire doit tre ralise avec la fonction free. Or, un programme C++ qui appellerait delete[] sur la chane copie ne serait pas correct (bien que dans la plupart des cas, les oprateurs new et delete du C++ utilisent les fonctions de gestion de la mmoire de la bibliothque C sous-jacente). Les conventions dappel doivent donc tre dnies de manire stricte, et si possible tre uniformes dans tout le programme. P2. Initialiser les variables. Les variables non initialises sont extrmement dangereuses, car elles peuvent contenir nimporte quelle valeur. Lorsquelles sont utilises dans un algorithme qui suppose que leur valeur est correcte, celui-ci adopte un comportement alatoire, souvent non reproductible, et susceptible deffectuer des traitements qui ne sont logiquement pas prvus par le programme. Cela conduit donc des bogues difciles reproduire et diagnostiquer, et capables de gnrer des effets de bords complexes. Dans le cas des pointeurs, cette rgle est absolument vitale, car lutilisation dun pointeur non initialis peut conduire au mieux un plantage immdiat, au pire une criture arbitraire dans la mmoire du programme ! Linitialisation dune variable une valeur par dfaut, mme invalide pour la smantique du programme, est donc imprative. Cest une opration que lon doit raliser ds la cration de la variable, et permet de garantir la reproductibilit du comportement du programme. Cette initialisation doit tre ralise mme lorsque lalgorithme qui lutilise permettrait de sen passer, car cela relve de loptimisation inutile et accrot les risques considrablement. Toute dnition de variable doit donc tre immdiatement suivie de son initialisation, et tout ajout dune donne membre une classe doit tre immdiatement suivi de lcriture de son code dinitialisation dans le constructeur ou dans le chier dimplmentation pour les variables statiques. Ce comportement doit tre acquis comme un simple rexe pour tout programmeur qui se respecte.
int i=0; void *pBuffer = NULL;

// VITAL !

229

Chapitre 12. Conventions de codage et techniques de base P3. Rinitialiser les variables dtruites. En complment de la rgle prcdente, une variable qui a t utilise et dont on ne se servira plus, mais qui reste accessible dans le reste du programme, doit toujours tre rinitialise une valeur invalide pour la smantique du programme ds que lon nen a plus besoin. En particulier, tout pointeur dont la mmoire a t libre doit tre immdiatement rinitialis sa valeur nulle. Dans le cas contraire, les cas derreurs prsents dans la rgle prcdente redeviennent possible aprs destruction de la variable.
delete[] pBuffer; pBuffer = NULL;

// Rinitialisation immdiate !

P4. Valider les entres. Toute donne provenant de lextrieur du programme doit tre considre comme non sre. Elles peuvent tre incorrectes, corrompues, ou tout simplement traques dans le but dobtenir du programme un comportement diffrent de celui pour lequel il est prvu. Il est donc essentiel de vrier la validit de ces donnes (ce qui suppose, bien entendu, que les formats dchange et de chiers soient conus pour permettre cette vrication facilement). Les donnes dont la validation devra tre effectue comprennent notamment :

les donnes lues partir dun chier ou rcupres via une connexion rseau ; les donnes fournies par lutilisateur ; les donnes fournies au travers des interfaces publiques.

En revanche, il est inutile de valider les donnes fournies en paramtre une fonction prive ou interne un programme, car on peut supposer dans ce cas que le contexte dappel est matris. Si lon dsire malgr tout faire des vrications dans ce cas, il est prfrable dutiliser la macro assert (voir plus bas). P5. Toujours coder default et else. Les branchements conditionnels correspondent diffrents cas dutilisation dun logiciel. Chaque branchement implique un choix, pour lequel il doit y avoir une rponse approprie. Le fait de ne pas donner de rponse lun de ces choix constitue un bogue qui peut se produire ou non, selon que la situation considre peut effectivement se prsenter ou non. Cependant, la possibilit quune situation se prsente ou non est un facteur extrieur au programme, et mme dans le cas de choix logiquement exclusifs, faire une impasse revient considrer que la logique du programme ne changera jamais. Par consquent, si lon veut sassurer quun programme fonctionnera toujours ou, au moins, signalera les situations pour lesquelles il ne peut plus assurer un comportement dtermin, il faut prvoir lensemble des possibilits ds le codage. Cela se traduit en pratique par lcriture systmatique dun else pour chaque if, et dun cas par dfaut pour chaque switch. Cette manire de procder force le programmeur se poser la question de la possibilit que la condition inverse puisse se produire. Bien entendu, dans de nombreux cas, cette condition ne ncessitera aucun traitement particulier. Lutilisation de linstruction vide peut alors simplement permettre de montrer que ce cas de conguration a bien t pris en compte par le programmeur.
if (i>10) i = 10; else ;

// On accepte les nombres ngatifs.

230

Chapitre 12. Conventions de codage et techniques de base P6. Utiliser assert. La macro assert (dclare dans le chier den-tte assert.h) permet de vrier une condition dont la vracit doit toujours tre assure lors de son excution. Elle prend en paramtre lexpression de la condition vrier, lvalue et interrompt le programme si cette expression est fausse. Ce comportement est en effet la meilleure des choses faire lorsque le programme se trouve assurment dans une situation non prvue. La macro assert est donc un outil de dbogage puissant, qui permet de garantir quun programme naura pas un comportement non prvu. Elle permet galement de signaler la condition qui nest plus vrie larrt du programme, permettant ainsi au programmeur de diagnostiquer lerreur plus facilement. Son usage est donc particulirement recommand. En pratique, assert sera utilis dans toutes les situations o un test nest pas fait car suppos comme toujours vrai, ou lorsquune branche du programme ne doit jamais tre atteinte. Par exemple, les paramtres dune fonction ou procdure interne peuvent tre valids avec assert an de dtecter les erreurs des fonctions appelantes. De mme, le cas par dfaut dun switch dont tous les cas ont t traits explicitement peut contenir un assert pour signaler quun cas a t oubli. Exemple 12-7. Utilisation de assert
switch (eColor) { case color_red: do_red(); break; case color_green: do_green(); break; case color_blue: ; break; default: assert(false); break; }

P7. Navoir quun seul return par fonction. Toute fonction et toute procdure ne doivent avoir quun seul point de sortie. En effet, le fait dutiliser return dans le corps dune fonction ou dune procdure a pour consquence de masquer la sortie du ux dexcution de manire prmature. De ce fait, toute modication ultrieure de la fonction risque dtre ralise en supposant que la n de la fonction sera excute, alors que cela peut ne pas tre le cas. Il peut sensuivre des erreurs graves dans la logique du programme, et gnralement des consommations de ressources ou des interblocages dus au fait que le code de libration des ressources nest pas excut dans des cas particuliers difciles reproduire.
int f(int i) { lock(); // Prise de ressource. if (i < 2) { do_job(); return 0; // Dangereux, on oublie facilement le unlock() ! } do_another_job();

231

Chapitre 12. Conventions de codage et techniques de base


unlock(); return 1; } // Libration de la ressource.

Note : En ralit, le seul cas dutilisation valide de return dans le corps dune fonction est tout au dbut de la fonction, pour sortir immdiatement en cas de dtction de paramtres incorrects dans le code de vrication des paramtres.

P8. Mettre les lvalues droite dans les tests dgalit. Les lvalues (abrviation de left values ) sont les expressions que lon peut placer gauche des oprations daffectation. De ce fait, ce sont des expressions qui reprsentent une variable ou une rfrence de variable. Du fait que lopration daffectation renvoie une valeur, les critures telles que celles-ci sont tout fait valides :
if (i = 2) { // Toujours excut, car 2 est vrai. // De plus, i a perdu sa valeur davant le test. }

Cela constitue gnralement une erreur, car les tests vrient gnralement une condition et pas la non nullit dune valeur affecte. De plus, ce type derreur se produit facilement, puisquil suft dune simple faute de frappe (un oubli de = en loccurrence). Par consquent, il est recommand de toujours placer les lvalues (i dans notre exemple) droite dans les tests dgalit :
if (2 == i) { // Si i vaut deux, alors... }

Dans le cas dun oubli dun =, le compilateur signalera cette fois une erreur, car on ne peut affecter une valeur une autre valeur. P9. Faire des constructeurs et des destructeurs srs. En raison de la grande difcult de la gestion des erreurs dans les constructeurs et de son impossibilit dans les destructeurs, il est recommand de ne faire que des oprations extrmement simples dans ces mthodes. En particulier, le constructeur doit en gnral se limiter linitialisation des donnes membres non statiques de la classe. Cette rgle est particulirement importante pour les classes qui peuvent tre instancies globalement. En effet, les objets globaux sont instancis trs tt dans la dure de vie du programme, avant lappel de la mthode main, et dans certains cas les ressources systmes ne sont pas toutes accessibles. Un constructeur complexe peut chouer, et provoquer ainsi une erreur fatale difcile diagnostiquer au lancement du processus. En gnral, les oprations relatives la gestion de la dure de vie des objets de classes complexes sont

232

Chapitre 12. Conventions de codage et techniques de base affectes des mthodes ddies ces tches. Par exemple, une mthode Init peut tre dnie pour linitialisation et une mthode Reset pour la destruction et la libration des ressources. De mme, les oprations de copie dune classe sont souvent identiques entre les constructeurs de copie et les oprateurs daffectation, aussi limplmentation dune mthode Clone ddie cette tche peut-elle tre utile.

12.1.4. Optimisations
Sans prcautions particulires, il est trs facile de produire un code source qui nest pas efcace ou qui est extrmement lourd alors que cela nest pas ncessaire. Pourtant, les rgles suivantes permettent souvent de prvenir une bonne partie des problmes de conception de bas niveau. Elles ont gnralement pour principe de forcer le programmeur se poser les bonnes questions lors de la conception ou lors dun choix technique.

12.1.4.1. Rgles doptimisation gnrales


Gnralement, les optimisations les plus efcaces sont les optimisations structurelles. De plus, les structures de donnes inadaptes un problme provoquent souvent un code plus complexe, donc moins lisible et moins able. Comme ce sont aussi les modications structurelles qui sont les plus coteuses dans un programme, il est important de bien rchir la structure dun programme ds le dbut du projet. Les rgles suivantes ont donc principalement pour but de mettre en valeur limportance de la structure et dviter les piges de conception qui peuvent conduire une structure de donnes inadapte. O1. Prfrer les optimisations structurelles aux optimisations locales. Comme il la dj t indiqu, les optimisations les plus efcaces sont les optimisations structurelles. Inversement, les optimisations locales induisent souvent une complexit accrue et lusage dastuces qui nuisent la lisibilit du programme et multiplient les risques de bogues. Il est donc particulirement important de rchir aux structures de donnes et la conception globale dun programme. Une bonne conception permet de garantir que les informations seront accessibles simplement et rapidement, rendant ainsi le programme efcace et le code source lisible et comprhensible. O2. Analyser les compromis complexit / temps / taille et choisir les structures de donnes en fonction de lusage. Les choix de conception, notamment au niveau des structures de donnes et des algorithmes qui les manipulent, impliquent souvent des compromis. Gnralement, il faut choisir entre simplicit des algorithmes, vitesse dexcution et consommation mmoire. Le choix doit se faire en fonction des objectifs recherchs du programme et de la nature des informations manipules et de leur nombre. Ne pas prendre en compte ces informations peut conduire un programme extrmement consommateur de ressources et trs lent. De plus, si les donnes utilises ne sont pas accessibles facilement, le code source du programme sera plus complexe et les principes dencapsulation seront plus facilement viols. Il est donc particulirement important de bien connatre les avantages et les inconvnients des diffrentes associations (associations, listes, tableaux, etc.) et de les utiliser bon escient, en fonction du but atteindre et des cas dutilisation pratiques. Les choix structurels et les algorithmes qui en dcoulent seront bien entendu documents an de donner une vue densemble du programme sans avoir parcourir lensemble du code source pour en comprendre les mcanismes.

233

Chapitre 12. Conventions de codage et techniques de base O3. Ne pas faire dabstractions contraires la ralit du problme. Les abstractions et les gnralisations permettent de raliser du code gnrique et donc la factorisation du code et la rduction des risques de bogues. Toutefois, les abstractions ne doivent pas devenir un objectif en soi et ne doivent tre ralises que pour servir la cause du programme : rsoudre un problme donn. Dans le cas contraire, la structure logique du programme sera peut-tre lgante, mais elle ne conviendra pas pour la rsolution du problme. De ce fait, les algorithmes utiliss, et donc le code au nal, devront aller lencontre de cette structure, devenant ainsi complexes, difcilement maintenables, et risquant de violer en permanence les encapsulations en cherchant obtenir des fonctionnalits non prvues pour les objets manipuls. O4. Considrer la drivation comme une agrgation. Un cas particulier important de la rgle prcdente est de bien prendre conscience quun hritage, en C++, est une agrgation de la structure de donnes de la classe de base avec celle de la classe drive. Ce nest en aucun cas une manire de rcuprer les fonctionnalits offertes par les interfaces de la classe de base. Effectuer un hritage pour des raisons fonctionnelles est donc le meilleur moyen de rcuprer des donnes inutiles et de rendre le programme plus complexe et plus consommateur de mmoire. O5. Ne pas faire de code inutilement gnrique. Il est inutile de dnir des interfaces complexes et des mcanismes gnriques pour les communications internes au programme. Ces mcanismes induisent en effet des communications moins directes, et donc plus complexes, moins lisibles et moins performantes, que des mcanismes plus spciques et moins gnriques. O6. Dnir les grandes entits du programmes et leurs interfaces. Toutefois, il faut savoir conserver une sparation correcte entre les grandes parties dun programme pour assurer son volutivit. Il est donc ncessaire de dnir ces grands blocs et dnir les interfaces et le niveau de gnricit de manire adquat chaque niveau de conception. Autrement dit, ce qui est bon au niveau des interfaces entre deux composants de haut niveau ne lest pas forcment pour des objets de base dun programme. O7. Ne pas utiliser daccesseurs inutiles. Dans le mme ordre dides, les accesseurs ne doivent pas tre utiliss dans limplmentation des mthodes de leur classe. En effet, lutilisation des accesseurs dans limplmentation ajoute une dpendance inutile envers les interfaces exposes dans limplmentation, complexie le code et le rend la fois moins performant et plus difcile dboguer en raison des appels de mthodes effectus au lieu des accs directs aux donnes. De plus, les accesseurs constituent une partie de linterface publique de leur classe, et nont pas pour but dtre utiliss dans limplmentation de la classe elle-mme. Autrement dit, limplmentation dune mthode de classe na aucune raison dutiliser les accesseurs de cette classe, tant donn quelle est elle-mme spcique la structure de donnes de cette classe.

12.1.4.2. Autres rgles doptimisation


Les rgles suivantes sont purement techniques et spciques au langage. Elles sont cependant sufsamment simples pour tre appliques en toute circonstances. O8. Aligner les membres des structures et des classes. Les donnes de taille infrieure la taille des registres du processeur sont gnralement alignes sur des adresses multiples de leur taille. Ceci est souvent impos par le matriel, et mme sur les

234

Chapitre 12. Conventions de codage et techniques de base architectures qui tolrent des donnes non-alignes, il est prfrable de les aligner pour des raisons de performances. De ce fait, des zones inutilises de la mmoire peuvent tre insres par le compilateur entre les diffrents membres des structures. Par exemple, un caractre et un entier 32 bits seront stocks dans 8 octets conscutifs, les quatre premiers octets tant consomms pour le caractre, qui nutilise pourtant effectivement quun seul dentre eux. Ces alignements consomment donc de la mmoire, et du fait quils sont spciques chaque architecture, ils peuvent rendre les structures non portables. Pour viter cela, tout en conservant des performances optimales, il est recommand de prendre en compte le problme de lalignement directement lors de la dnition dune structure. Pour cela, les donnes peuvent tre groupes par blocs de taille identique, ou par types identiques et de taille dcroissante. Par exemple, la structure suivante nest pas aligne :
struct S { char i; // align // Trois octets perdus int j; // non align ! char k; // align // Un octet perdu short l; // non align ! };

alors que celle-ci lest :


struct S { int j; short l; char i; char k; };

Moyennant quelques hypothses sur la taille des types de donnes, on aurait aussi pu crire cette structure comme ceci :
struct S { char i; char k; short l; int j; };

// align : sizeof(short) = 2*sizeof(char) // align : sizeof(int) = 2*sizeof(short)

O9. Utiliser la version prxe des oprateurs ++ et --. Dans la plupart des cas, les oprateurs ++ et -- sont utiliss pour incrmenter la valeur dun objet et manipuler ensuite le rsultat. La valeur prcdente est donc totalement inutile, et il est donc conseill dutiliser les versions prxes de ces oprateurs. Les versions sufxes doivent renvoyer la valeur avant modication, et sont donc obliges de conserver

235

Chapitre 12. Conventions de codage et techniques de base ou de recopier cette valeur avant deffectuer lincrment ou le dcrment. Cela peut nuire aux performances, car une recopie peut coter cher et la conservation de la valeur antrieure consomme de la mmoire ou pollue les caches des processeurs. Les versions sufxes devront donc ntre utilises que lorsque cela ne peut tre vit.
for (int i=0; i<10; ++i) { // Corps de la boucle. }

12.2. Mthodes et techniques classiques


Nous allons voir dans cette section quelques mthodes et techniques complmentaires qui permettent damliorer la qualit des programmes. Quelques grands principes de conception objet seront prsents, ainsi que la manire de raliser des programmes orients objets en C. Enn, les notions dAPI et dABI seront prsentes, suivies de quelques rgles permettant de dnir des API simples utiliser.

12.2.1. Mthodologie objet


Les technologies objet sont une avance indniable pour les langages impratifs. Elles sont galement passionnantes et trs intressantes. Toutefois, elles ne peuvent garantir un succs systmatique, et mme, mal utilises, elles peuvent tre la cause de programmes extrmement inefcaces. Cest pour cela que la programmation oriente objet ne doit pas se rduire de simples artices syntaxiques, mais sorienter dans une mthodologie plus large. Plusieurs mthodes ont donc t dnies, certaines sorientant plus sur certains aspects que dautres. Mais toutes ont pour but daider la conception des programmes, non seulement pour quils soient correctement structurs, mais aussi pour quils rpondent aux besoins. Ces mthodes doivent donc sintgrer dans un processus de dveloppement logiciel plus large que le simple niveau de la programmation, avec lequel elles doivent donc tre cohrentes pour tre efcaces. La plupart de ces mthodes sont itratives, et permettent une conception du logiciel par dcoupage structurel. chaque itration, la mthode complte est applique, dabord pour les grands composants du logiciel, puis pour les constituants plus petit, et ainsi de suite jusqu limplmentation des briques de base. Gnralement, toutefois, une seule itration suft, et seuls les grands projets ncessitent plus dune passe. Il nest videmment pas question de dcrire ici ces mthodes. Toutefois, les principaux concepts sont toujours utiles et peuvent tre prsents an de fournir un aperu de ce que ces mthodes prconisent.

12.2.1.1. Dnir le besoin et le primtre des fonctionnalits


Ltape la plus importante dans toute mthode de gnie logiciel est assurment ltape de spcication du besoin. Sans cette tape, il nest pas possible de savoir ce que lon doit faire, ni o sarrter. Ainsi, si la phase de spcication des besoins et de dlimitation des fonctionnalits du programme nest pas ralise, dans le meilleur des cas le programme en fera trop et aura cot trop cher, et dans le pire des cas il aura cot cher et ne rpondra malgr tout pas au besoin. Cest assurment les bogues de ce type qui sont les plus coteux !

236

Chapitre 12. Conventions de codage et techniques de base La spcication du besoin fait cho au cahier des charges du matre douvrage et en reprend les termes et les points un un. En cas dabsence de dnition claire du besoin par le matre douvrage, cette phase doit servir la fois dlimiter le projet et protger juridiquement les diffrentes parties. En effet, en cas de litige, il nest pas facile du tout de dterminer ce qui est d et ce qui ne lest pas. Le matre duvre a donc tout intrt effectuer cette formalisation, et au besoin daider le matre douvrage prciser son besoin. Le matre douvrage aussi y a intrt, puisquelle permet de lui donner lassurance que le logiciel satisfera son besoin. Dun point de vue plus technique, la dnition des besoins et du primtre est un prrequis pour passer ltape suivante. Sans elle, on ne sait a priori ni ce que doit faire le logiciel, ni son cadre dutilisation. Il ne faut pas oublier quau nal, lordinateur ne devinera pas ce quil doit faire, et arriv au niveau du codage, tout doit tre parfaitement spci. Comme il est relativement difcile de formaliser un besoin, les mthodes de conception travaillent souvent en terme dexemples concrets. Ces exemples sont appels des cas dutilisation ( Use Cases en anglais) dans les langages de modlisation tels que UML (abrviation de Unied Modeling Language ). Les cas dutilisation permettent donc de dcrire le comportement dans des cas bien prcis, et notamment dans le cas dutilisation nominal, ce qui est fondamental ! Toutefois, une spcication de besoins ne peut se rduire une liste de diagrammes de cas dutilisation, car ces diagrammes ne sont ni formels, ni, en gnral, exhaustifs. Ils ne servent donc qu fournir des exemples aisment comprhensibles dutilisation du produit nal, et au minimum en dcrire le cadre dutilisation nominal et les marches dgrades principales. En ce sens, ils ne sont essentiellement utiles que pour communiquer avec le matre douvrage, et pour laider mieux comprendre ce que fera le logiciel. Cest pour cela que, en gnral, les besoins sont ensuite formaliss en terme dexigences numrotes, et dont la couverture est assure dans la suite du projet par des matrices de traabilit. Chaque document de conception devra ensuite rfrencer ces exigences et y rpondre son niveau, et les plans de tests de validation fonctionnelle devront sassurer que toutes les exigences sont effectivement couvertes.

12.2.1.2. Identier les entits et leur nombre


Contrairement aux mthodes de conception fonctionnelles, qui identient en premier lieu les traitements qui doivent tre effectues, les mthodes objets sintressent plus aux entits auxquelles les traitements seront appliqus. Une fois les besoins clairement identis, les mthodes objet sattachent donc lidentication de ces entits. Ce seront ces entits qui constitueront au nal les objets du systme.
Note : Bien que lapproche fonctionnelle soit gnralement plus facile apprhender, car en gnral on sait ce que lon veut faire, elle ne garantit pas de trouver une structure correcte pour les donnes du programme, et est donc un facteur de risque trs fort deffets de bord. Inversement, les mthodes de conception objet sintressent plus aux entits auxquelles les traitements sappliquent, garantissant ainsi une cohrence des donnes forte et une rduction des risques des effets de bord. Laspect traitement nest envisag quau nal, une fois les entits et leurs interactions connues. Cette approche se justie par le fait que de toutes manires, les traitements qui doivent tre ralises doivent ltre sur des donnes, et que les exigences fonctionnelles seront bien traites, mais dans un contexte structurel sain et correspondant la ralit du problme.

La phase didentication nest en soi pas trop difcile raliser. Toutefois, il faut viter dentrer dans le dtail ds le dpart, car tous les objets ne sont pas dun mme niveau fonctionnel. Le processus est

237

Chapitre 12. Conventions de codage et techniques de base donc itratif, et chaque constituant peut tre analys plus prcisment une fois que tous les macroconstituants sont identis. Il est intressant de dterminer la cardinalit des entits ainsi identies. En effet, le nombre de ces entits va inuer directement sur les structures de donnes qui pourront ainsi tre choisies an dobtenir les meilleures performances pour les principaux cas dutilisation.

12.2.1.3. Analyser les interactions et la dynamique du systme


Les interactions entre les diffrentes entits peuvent ensuite tre dcrites. Ces interactions doivent permettre de raliser les cas dutilisation et doivent prendre en compte la cardinalit des entits. Les mthodes utilisent gnralement des diagrammes de classes ou de collaboration pour reprsenter les interactions. Ces diagrammes constituent donc une vue statique des relations entre les diffrents objets, vue dont les structures de donnes utilises lors de limplmentation seront dduites en phase de conception dtaille. Il est extrmement important de dcrire galement la dynamique du systme, cest--dire les messages changs entre les diffrents constituants. En effet, cest elle qui donne du sens aux diagrammes de classes, en indiquant la chronologie des changes entre ces classes dans les cas dutilisation. Cela permet galement de contrler que les relations entre les sous-systmes ont bien t toutes identies. Les diagrammes les plus utiles pour cette tche sont sans doute les diagrammes de squence.

12.2.1.4. Dnir les interfaces


La dnition des interfaces doit permettre de raliser les interactions identies prcdemment. Cest dans cette phase que les contrats de service , cest--dire les fonctionnalits quils exposent, sont dnis pour les objets. On notera que la description dynamique de lutilisation des mthodes des interfaces est tout aussi importante que la description de linterface elle-mme, mme si souvent lutilisation peut se dduire implicitement. Il faut bien garder lesprit que les principes objets minimisent les effets de bords et amliorent la rutilisabilit du code, mais que cela se fait au dtriment dun travail de dnition et dimplmentation des interface relativement lourd (ce qui est trs visible en regardant le moindre programme crit dans des langages Java ou .Net ou implmentant en C++ des composants COM ou Corba par exemple). Ainsi, il nest pas rare dans les programmes objets de voir plus de code pour grer les interfaces que pour grer la fonctionnalit elle-mme. Il est donc important, an de ne pas trop souffrir de ce revers de mdaille, de dnir les interfaces des classes dans le but de permettre les interactions de la manire la plus simple et la plus claire possible. Les deux axes dcrits ci-dessous peuvent tre suivis pour cela. Premirement, il faut savoir exploiter la gnricit bon escient. La gnricit est une bonne chose, quand elle est justie, mais elle peut devenir nfaste dans le cas contraire. Il est recommand de dnir des interfaces communes des objets de classe semblable, an de permettre leur manipulation de manire polymorphique. Cependant, cela ne doit pas tre un but en soi, et il ne faut pas sacharner faire rentrer des carrs dans des ronds. Par consquent, les interfaces doivent tre dnies en fonction de la ralit du problme, et si les classes dobjets manipuls ne sont pas semblables, et bien tant pis, cest que de toutes manires un traitement spcique doit leur tre appliqu. Deuximement, il faut chercher minimiser les changes entre objets. Il est important de sassurer que les interfaces permettent deffectuer les oprations le plus efcacement et le plus simplement possible.

238

Chapitre 12. Conventions de codage et techniques de base Pour cela, les informations ncessaires au traitements doivent tre communiques aux mthodes qui en ont besoin, ventuellement par lintermdiaire dune rfrence dobjet. Cette rgle est particulirement importante dans le cas o plusieurs donnes doivent tre rcupres sur un objet partag avec plusieurs autres objets, ou lorsquun objet doit signaler un vnement un autre objet. Ainsi, il est prfrable de dnir des mthodes permettant de rcuprer un ensemble de valeurs en une fois quune multitude daccesseurs (toujours trs lourds crire et implmenter). De mme, lensemble des paramtres relatifs un vnement doit tre fourni lors de la notication de cet vnement. Les raisons de cette dernire rgle sont multiples. En fait, ne pas fournir les informations relatives un vnement complexie le traitement des objets serveurs et nest pas able dans un systme complexe :

Cela impose de conserver les informations relatives lentit qui a gnr lvnement au niveau de lobjet serveur. Cela peut tre articiel, et la question de la dure de la conservation de ces informations se pose immdiatement. Lorsque la notication signale un changement dtat, il nest pas possible de garantir un client que ltat quil obtiendra en appelant un accesseur sera celui qui a provoqu la notication de changement, surtout dans un contexte multithread. Dans certaines circonstances, comme dans le cadre dune communication rseau par exemple, un appel supplmentaire peut tre plus coteux que le transfert de toutes les informations lors de la notication. Enn, la multiplication des appels tend rendre le programme plus complexe, plus difcile suivre, et plus risqu dans un environnement multithread.

Note : Il arrive quelquefois que lon ne soit intress que par le fait quun vnement se produise, et pas par le dtail de cet vnement. Toutefois, on ne peut pas, de manire gnrale, prjuger de lusage qui pourra tre fait dun vnement fourni par un objet. Par consquent, il faut prvoir de fournir toutes les informations, quitte perdre un peu de temps pour le cas o elles seraient ignores par le client.

12.2.1.5. Ralisation et codage


Si les tapes prcdentes sont correctement ralises, la phase de ralisation doit pouvoir se faire avec une bonne visibilit. Les seuls risques ce niveau sont les risques lis lenvironnement, aux outils ou aux technologies employes. En gnral, il nest jamais inutile de procder des tests simples sur les points cls pour valider une technologie que lon ne connat pas parfaitement. Cela peut viter bien de mauvaises surprises. De manire gnrale, une bonne technologie est une technologie approprie au problme rsoudre et son environnement.

12.2.1.6. Les tests


Les tests consistent vrier que les spcications sont bien implmentes. Cela implique naturellement que sans spcications, on ne peut pas faire de tests exhaustifs. Tout au plus peut-on tester les cas nominaux et les cas derreurs triviaux, au gr de limagination de celui qui les ralise.

239

Chapitre 12. Conventions de codage et techniques de base Il est possible de dnir des tests pour chacune des phases de conception. Les tests unitaires sont les tests qui se placent au niveau du codage, ils ont pour but de vrier que les objets se comportent correctement et sont utilisables via leurs interfaces. La ralisation de ces tests peut ncessiter le dveloppement doutils hte pour les objets tests. Il est vident quil est plus facile de tester unitairement des objets autonomes que des objets qui ncessitent un environnement complexe ou dont lencapsulation est imparfaite. Les tests dintgration se situent au niveau conception et dnition des interfaces. Ils ont pour but de vrier que les diffrentes entits identies en conception parviennent communiquer et sintgrer les uns avec les autres correctement. Ce sont assurment les tests les plus longs, puisquils reviennent faire fonctionner le systme. Enn, les tests de validation sont les plus importants, puisquils ont pour but de vrier que les exigences des spcications de besoin sont bien couvertes. Un logiciel qui ne vrie pas ces tests nest pas forcment inutilisable, mais pas pour faire ce pour quoi il a t developp.

12.2.1.7. Les design patterns


En gnral, la plupart des problmes rencontrs pendant la conception sont classiques et ont dj t rsolus maintes fois, dans de multiples circonstances. En fait, les problmes de conception sont presque toujours les mmes, et il est possible de les classer en quelques grandes catgories. Cela implique que, bien souvent, les solutions qui ont t adoptes dans certaines circonstances sont utilisables ou peuvent tre adaptes facilement pour rsoudre le mme problme dans dautres circonstances. Ces problmes de conception ont donc souvent dj t rsolus par dautres programmeurs, et les solutions trouves vries comme fonctionnant et apportant une rponse approprie. De ce fait, il nest en gnral pas ncessaire de rinventer la roue, au risque de faire une erreur de conception ou un mauvais choix que dautres programmeurs ont dj su viter (ou faite !). Ainsi, il suft de trouver la solution correspondante au problme rsoudre, et de lappliquer directement. Cest dans cet esprit que les design patterns ont t invents. Un design pattern (motif ou lment de conception de base) nest rien dautre quun modle de solution gnrique reconnu et applicable un type de problme donn. Les design patterns ne couvrent videmment pas toutes les situations, mais ils peuvent aider de diverses manires :

ils diminuent la charge du programmeur car ils apportent une rponse toute faite ; ils rduisent les risques de mauvaise conception, car ils assurent que cette rponse est correcte ; ils sont facilement identiables, et sont donc facilement reconnaissable dans un modle objet ou une application, permettant ainsi une comprhension rapide du modle objet ; ils assurent une certaine cohrence de conception entre diffrents projets, permettant potentiellement une meilleure intgration ou interchangeabilit.

On aura donc tout intrt utiliser les design patterns. Pour cela, il suft simplement den connatre les principaux, et de faire leffort didentier les problmes que lon peut rsoudre grce eux en phase de conception.
Note : Bien que les design patterns fournissent une solution gnrique un grand nombre de problmes, ils ne garantissent pas que leur implmentation sera correcte. Ainsi, un soin particulier doit tre apport lors de leur implmentation, et les considrations classiques de codage devront toujours tre prises en compte.

240

Chapitre 12. Conventions de codage et techniques de base

12.2.2. Programmation objet en C


Nombre de projets choisissent encore le langage C sans pour autant vouloir renoncer aux avantages des techniques objets. La raison de ce choix est gnralement de garantir la lgret du programme ou de pouvoir sabstraire des spcicits des compilateurs C++ (notamment en ce qui concerne le nommage des symboles) et raliser ainsi des bibliothques extrmement portables ou devant tre utilises avec dautres langages de programmation. Heureusement, il existe une technique lgante permettant de programmer des objets en C. Cette technique reproduit les constructions que les compilateurs C++ utilisent gnralement en interne pour grer les classes et les fonctions virtuelles. Comme elle se rencontre couramment, il nest pas inutile de la dcrire brivement.

12.2.2.1. Principe de base


Le principe de base est de stocker lensemble des informations des objets dans une structure, et de fournir systmatiquement cette structure en paramtre aux mthodes qui permettent de la manipuler. Cela permet de reproduire la notion dobjet simple et de mthodes, le premier paramtre faisant ofce de pointeur this pour les mthodes de la classe. Tous les objets de mme type tant des instances dune mme structure, les mthodes sont capables de travailler avec diffrentes instances simplement en passant les rfrences sur ces instances en paramtre chaque appel :
stringlist_add(liste, "Une chane"); stringlist_add(liste, "Une autre chane...");

Cette technique reprend bien les principes de base en rduisant la porte des variables manipules la structure de lobjet, tout en vitant de fournir un nombre gigantesque de paramtres locaux. Les objets passs aux mthodes qui les manipulent doivent tre dnis de la manire la plus opaque possible. Ainsi, ils ne peuvent tre modis que par les fonctions ddies cet effet. Cela permet bien sr de mieux contrler la manire dont les donnes du programme ou de la bibliothque sont manipules par les programmes clients. La manire la plus simple de raliser en C un type de donnes opaque est dutiliser des pointeurs sur des structures non dnies. Cela se fait simplement comme suit :
struct _ma_structure; typedef _ma_structure *ma_structure_t;

Les mthodes de manipulation nont ensuite utiliser plus que le type de donnes ma_structure_t. La structure struct _ma_structure na pas tre connue des programmes clients (elle est juste dclare), et peut ntre dnie que dans les chiers dimplmentation des mthodes de linterface de lobjet.

12.2.2.2. Dnition dinterfaces


Il est possible de pousser plus loin la technique prcdente, en dnissant des interfaces utilisables sur les objets. Pour cela, il suft dassocier les mthodes de ces interfaces chaque objet auxquelles elles peuvent tre appliques. Pratiquement, les interfaces sont dnies sous la forme de tableaux de pointeurs de fonctions contenant les adresses des mthodes de linterface. Ces tableaux sont ensuite rfrencs dans les objets qui implmentent ces interfaces. Si la rfrence linterface est stocke la

241

Chapitre 12. Conventions de codage et techniques de base mme position pour plusieurs objets (par exemple en premire position...), alors ceux-ci peuvent tre manipuls polymorphiquement. Par exemple, on peut dnir une interface pour des objets nommables de la manire suivante : Exemple 12-8. Dnition dinterface en C
/* Dclaration du type de base des objets nomms */ struct _namedobject; typedef struct _namedobject *namedobject_t; /* Prototypes des mthodes des objets nomms : */ typedef void (*destroy_namedobject)(namedobject_t); typedef void (*set_namedobject_name)(namedobject_t, const char *szName); typedef void (*print_namedobject_name)(namedobject_t); /* Structure de linterface des objets nomms : */ typedef struct { /* Mthodes applicables : */ destroy_namedobject destroy; set_namedobject_name set_name; print_namedobject_name print_name; } fnamedobject_t; struct _namedobject { /* Table des mthodes de linterface : */ fnamedobject_t *ops; };

Si lon veut implmenter cette interface pour une classe dobjet, on peut simplement dclarer des fonctions dont la signature est identique celle de linterface :
/* Dclaration du type des objets : */ struct _object1; typedef struct _object1 *object1_t; /* Dclaration du constructeur : */ extern object1_t create_object1(const char *szName); /* Mthodes extern void extern void extern void applicables : */ destroy_object1(object1_t object); set_object1_name(object1_t object, const char *szName); print_object1_name(object1_t object);

Limplmentation de la classe se fait alors de la manire suivante :

242

Chapitre 12. Conventions de codage et techniques de base Exemple 12-9. Implmentation dun objet en C
/* Implmentation de la classe object1. */ /* Dfinition de la classe object1 : */ struct _object1 { /* Implmentation de linterface des objets nomms : */ struct _namedobject m_base; /* Structure de donnes de la classe : */ char *m_szName; }; /* Table des mthodes de la classe object1 : */ static fnamedobject_t object1_methods = { (destroy_namedobject) &destroy_object1, (set_namedobject_name) &set_object1_name, (print_namedobject_name) &print_object1_name }; /* Implmentation des mthodes de la classe objet1 : */ object1_t create_object1(const char *szName) { object1_t obj = (object1_t) malloc(sizeof(struct _object1)); if (obj != NULL) { // Initialisation : obj->m_base.ops = &object1_methods; obj->m_szName = NULL; // Appel de la mthode set_name : set_object1_name(obj, szName); } return obj; } void destroy_object1(object1_t object) { free(object->m_szName); object->m_szName = NULL; free(object); } void set_object1_name(object1_t object, const char *szName) { char *szNewName = NULL; int len = strlen(szName); szNewName = (char *) malloc(len + 1); if (szNewName != NULL) { strcpy(szNewName, szName); if (object->m_szName != NULL) free(object->m_szName); object->m_szName = szNewName; } }

243

Chapitre 12. Conventions de codage et techniques de base


void print_object1_name(object1_t object) { if (object->m_szName != NULL) { printf("%s\n", object->m_szName); } else { printf("objet anonyme\n"); } }

On notera que le tableau de pointeurs des mthodes de la classe est commun tous les objets de cette classe. Plusieurs structures identiques peuvent partager des mthodes communes, mais en gnral, chaque structure a ses propres fonctions, ce qui correspond au mcanisme des fonctions virtuelles du C++. En particulier, la fonction destroy se charge de dtruire correctement les objets de la classe laquelle elle appartient, faisant ainsi lquivalence avec les destructeurs virtuels du C++. La manipulation des objets nommables est alors simple et peut se faire, moyennant un simple transtypage, de manire identique pour tous les objets qui implmentent linterface : Exemple 12-10. Utilisation dun objet en C
#include <stdlib.h> #include "object1.h" int main(void) { /* Cration dun objet nomm : */ object1_t o = create_object1("Un objet en C..."); /* Utilisation directe de cet objet nomm : */ print_object1_name(o); /* Utilisation de lobjet via son interface : */ namedobject_t named = (namedobject_t) o; named->ops->set_name(named, "Hello World !"); named->ops->print_name(named); named->ops->destroy(named); return EXIT_SUCCESS; }

12.2.2.3. Compatibilit binaire


Outre le fait que la structure des donnes nest pas expose dans les chiers den-tte, la technique qui vient dtre prsente permet de conserver la compatibilit source et binaire des programmes si limplmentation change, et permet daccrotre les performances en simpliant les passages de paramtres inutiles dans les fonctions. En effet, le seul code qui manipule la structure des objets se trouve dans les chiers dimplmentation de lobjet lui-mme. Les programmes clients nutilisent que des pointeurs sur ces objets, et ne font aucune hypothse sur leur structure. Notez toutefois que la conservation de la compatibilit binaire impose de ne pas modier les interfaces tant au niveau de lordre des mthodes des objets que du nombre et de la smantique de leurs

244

Chapitre 12. Conventions de codage et techniques de base paramtres. Les codes de retours ne peuvent gnralement pas tre modis non plus, pas mme tendus. En revanche, il est faisable de changer le nom des mthodes (incompatibilit au niveau source uniquement) ou den rajouter (extension dinterfaces).

12.2.3. ABI et API


Une API (abrviation de Application Programming Interface ) est la spcication descriptive dune interface dun programme ou dune bibliothque au niveau langage. Gnralement, les API sont dnies comme un jeu de fonctions et des diffrentes valeurs possibles pour leurs paramtres. Une ABI (abrviation de Application Binary Interface est la spcication dune interface bas niveau. LABI sintresse donc plus particulirement la manire dont les fonctions de linterface sont appeles au niveau binaire. Le choix dune ABI et la dnition de lAPI dune bibliothque sont des choix importants, qui sont conditionns par lusage qui sera fait de cette bibliothque et par les possibilits dvolution quelle doit avoir.

12.2.3.1. Choix de lABI


Les ABI dnissent lensemble des rgles qui permettent de savoir comment les fonctionnalits dune bibliothque sont utilises au niveau binaire, sur une plateforme donne. Elles dcrivent un certain nombre daspects, parmi lesquels on retrouve les conventions dappel et les noms utiliss dans les chiers objets pour ldition de liens. Les conventions dappel doivent tre parfaitement spcies lors de la dnition dune ABI. Les conventions dappel spcient en particulier la manire dont les paramtres sont passs une fonction, ainsi que qui, de lappelant ou de lappel, doit dtruire les paramtres. En gnral, la convention la plus utilise est la convention du langage C (les paramtres sont tous passs par la pile, dans lordre inverse de passage dans lcriture de lappel de fonction, et sont librs par lappelant). Il sagit galement de la convention utilise par dfaut pour le C++ et pour de nombreux autres langages. Toutefois, il existe dautres conventions, qui sont moins courantes et moins portables. Gnralement, les conventions dappel sont spcies laide dun attribut. La manire de xer les attributs sur les fonctions et les variables globales nest pas spcie et encore moins portable. Elle se fait souvent avec un mot-cl spcique, que lon place avant lidenticateur ainsi quali :
int __cdecl i; void __attribute__((stdcall)) f(int);

/* Spcification de convention dappel C en Visual /* Spcification de convention dappel STDCALL de Windows avec GCC */

LABI spcie galement les noms utiliss dans les chiers objets pour reprsenter les identicateurs. Ces noms sont utiliss pour ldition de liens, an de retrouver les emplacements mmoire utiliss par les variables et le code binaire des fonctions. Si les noms des identicateurs C sont standardiss et peuvent tre facilement connus partir de leurs dclarations (il suft de prxer le nom de lidenticateur par un _), il nen va pas de mme pour les identicateurs C++. En effet, les compilateurs C++ ajoutent des informations de typage aux noms des identicateurs du programme dans les chiers objets, an de permettre les surcharges de fonctions et les contrles de type. On dit que ces noms sont dcors . De ce fait, les noms des fonctions

245

Chapitre 12. Conventions de codage et techniques de base exportes par les bibliothques C++ sont rarement utilisables avec un autre compilateur que celui qui les a gnrs, et encore moins partir dun autre langage. La situation est en voie damlioration, puisquune norme prcisant lABI C++, et donc en particulier le format des noms dcors, a t diffuse et semble admise par la plupart des diteurs de compilateurs. Toutefois, les implmentations de cette ABI ne sont pas encore totalement stabilises et, surtout, les compilateurs sous Windows ne la respectent pas. Microsoft ne semble manifestement pas vouloir modier Visual C++ pour cela, nayant pas besoin ou intrt assurer une introprabilit avec les diteurs de compilateurs pour les autres systmes. Enn, mme en supposant que tous les compilateurs C++ utilisent les mmes dcorations pour les noms didenticateurs dans les chiers objets, ces noms ne seraient toujours pas facilement utilisables partir dautre langages que le C++. Il est donc important, lorsquon ralise une bibliothque ou des fonctions utilitaires, de dterminer les conditions dutilisation de cette bibliothque. Le choix du langage C++ restreint fortement son champ dapplication et linteroprabilit. Si cette bibliothque doit tre utilise partir dautres langages, il est prfrable dutiliser la directive ddition de liens extern "C" pour exposer les noms des identicateurs publics avec les conventions du C, qui sont reconnues universellement. Cela a galement pour avantage de rduire le risque dexposition des particularismes du langage C++ inaccessibles dautres langages, comme par exemple les surcharges des fonctions ou les exceptions.

12.2.3.2. Dnition de lAPI


La dnition dune API pour un programme est un exercice difcile, car une fois dnie, elle ne peut tre change sans provoquer de rupture de la compatibilit au niveau du code source, et mme souvent au niveau du code compil. De plus, une mauvaise API peut rendre la programmation lourde et complexe, voire inefcace et risque. Cette section se propose donc de souligner quelques points importants dans la dnition dune API. 12.2.3.2.1. Simplicit dutilisation Raliser une API simple demploi nest manifestement pas une chose facile. Quelques rgles peuvent cependant aider le programmeur dans cette tche. Avant tout, il faut que les fonctions de lAPI soient faciles appeler, et donc quelles aient un nombre rduit de paramtres. Pour cela, les techniques objet peuvent grandement aider, puisque toutes les donnes membres dun objet sont fournies implicitement en paramtre via le pointeur sur lobjet. Bien entendu, ces donnes membres doivent gnralement tre initialises lors de la cration dun objet, mais cela nest fait quune seule fois.
Note : Certaines API utilisent une notion de contexte global pour rduire le nombre des paramtres (par exemple, matrice de transformation dans OpenGL et masque des droits des chiers leur cration dans Posix). Toutefois, cela nest pas recommander, car ce contexte peut tre modi de manire implicite dune part, et ne permet pas dutiliser lAPI avec plusieurs clients dautre part. Si un contexte doit tre utilis pour simplier les appels de fonctions, alors autant que ce contexte soit contenu dans un objet et que les fonctions soient des mthodes de cet objet.

Un autre aspect important pour la simplicit dutilisation dune API est la possibilit dutiliser des entits diffrentes de manire uniforme. L encore, les notions objets sont utiles, car il est possible de manipuler plusieurs entits semblables par polymorphisme. Par exemple, il est possible de faire une synchronisation avec lensemble des objets systmes du noyau de Windows. Ces objets sont tous reprsents par un type dobjets unique du systme, et peuvent tre manipuls de manire uniforme

246

Chapitre 12. Conventions de codage et techniques de base via un jeu de fonctions travaillant sur ce type. De mme, la manipulation des chiers, des disques et des priphriques au sens gnral sous Unix se fait de manire uniforme via la notion de chier et les fonctions de manipulation des chiers (bien que les interfaces rseau fassent exception la rgle).
Note : On veillera ne pas faire les erreurs dcrites prcdemment dans la dnition dune API objet. Par exemple, un abus de gnricit peut tre trs nfaste. En effet, cela peut entraner une profusion de paramtres dans les mthodes pour en spcialiser le comportement lorsquelles sont appliques des objets qui ne se prtent pas au modle des interfaces utilises.

12.2.3.2.2. Interfaces synchrones et asynchrones Gnralement, les appels de mthodes sont bloquants et ne se terminent quune fois le traitement de la mthode termin. Cependant, il arrive des situations o ce traitement peut tre long, et lappelant peut ne pas vouloir en attendre la n pour excuter un autre traitement simultanment. Il est possible dans ce cas de faire en sorte que la mthode appele se contente damorcer le traitement et rende la main immdiatement, le composant cible effectuant alors le traitement en parallle grce un autre processus, un autre thread ou toute autre technique approprie dexcution concurrente. La mthode appele est alors dite asynchrone, et le rsultat du traitement (donnes fournies en retour ou compterendu dexcution) doit tre rcupr ultrieurement. Il existe plusieurs techniques pour rcuprer les informations relatives un traitement asynchrone :

soit la mthode appele fournit en retour un objet rponse grce auquel on peut attendre la n de lopration, et ventuellement rcuprer les rsultats ; soit lappelant fournit une fonction de rappel qui sera appele par le composant qui effectue le traitement une fois celui-ci ni ; soit un message est envoy lappelant par le composant qui effectue le traitement, via un canal de communication standard.

Les API synchrones sont souvent les plus simples utiliser. Toutefois, ce ne sont pas les plus performantes. Comme il est toujours possible de simuler une API synchrone facilement partir dune API asynchrone en attendant explicitement la n de son excution, il est recommand de proposer les deux modles. Ainsi, chaque programme pourra utiliser la version des mthodes la plus approprie ses besoins.

12.2.3.2.3. Gestion des allocations mmoire Il existe de nombreuses mthodes de gestion de la mmoire, et les blocs de donnes allous par un allocateur donn ne peuvent gnralement pas tre librs par un autre allocateur. Cela implique que les programmes clients dune API ne peuvent gnralement pas utiliser leur propre allocateur pour librer les ressources quils ont obtenues via cette API. Ce point est encore plus important pour les bibliothques utilitaires destines tre utilises dans un programme crit dans un autre langage que le C/C++. Le fait de documenter la manire dont les ressources sont alloues ne suft gnralement pas pour supprimer ce problme. En effet, sur certaines plateformes, lallocateur mmoire utilis dpend de la conguration du projet (Unicode ou non, Debug ou Release, dition de liens statique ou dynamique avec la bibliothque C, etc.). Ainsi, mme en sachant comment il faudrait librer une ressource, le programme client ne peut le raliser, car il na pas accs lallocateur qui la alloue.

247

Chapitre 12. Conventions de codage et techniques de base Il est donc recommand, an de sabstraire des problmes de conits entre allocateurs mmoire, de toujours fournir les fonctions qui permettent de librer les ressources que le programme peut obtenir via lAPI. Ainsi, la libration des ressources se fait dans le contexte o elles ont t alloues, par le code qui les a alloues. Cela peut complexier lgrement la gestion des ressources dans les programmes clients, surtout sils utilisent plusieurs bibliothques utilitaires simultanment. Toutefois, lutilisation des allocateurs mmoire des bibliothques peut tre rendue trs naturelle si la conception de lAPI est objet et que tous les objets fournissent une mthode de destruction.

12.2.3.2.4. Allocation des valeurs de retour Lorsquune fonction doit renvoyer un rsultat de taille non dterminable lors de la dnition de lAPI, il est ncessaire dutiliser une allocation dynamique de mmoire. Si cela ne pose pas problme particulier dans un programme classique, cela peut tre gnant dans le cadre de la dnition dune API. Classiquement, deux approches sont possibles pour grer ce genre de situation. La premire est de considrer que les rsultats retourns par la fonction appele sont stocks dans une zone mmoire alloue par cette dernire. Cette zone doit donc tre libre par le code appelant, de manire compatibles la mthode dallocation utilise. La fonction de libration de mmoire utiliser est dans ce cas, comme on la vu dans la section prcdente, la fonction fournie par lAPI cet effet. Par exemple, la fonction strdup, disponible sur les systmes Posix et Unix98, effectue une copie dune chane de caractres et retourne cette copie. La mmoire utilise pour stocke cette copie est alloue avec la fonction malloc et doit donc tre libre avec la fonction free :
// Libration de la mmoire alloue par strdup : free(szCopy);

Cette technique convient gnralement dans de nombreux cas. Toutefois, elle impose un allocateur mmoire spcique (en loccurrence, celui de la bibliothque C dans notre exemple). De ce fait, ce bloc mmoire ne peut tre manipul comme un bloc allou par le programme lui-mme, ce qui impose de recopier les donnes si lon veut conserver une indpendance du programme vis vis des bibliothques quil utilise, ou tout simplement une certaine cohrence. Par exemple, si un programme reoit une chane de caractres alloue dynamiquement par une bibliothque mylib , et doit les librer avec la fonction mylib_free , il ne peut se permettre de stocker cette chane directement, au risque dappeler free ou delete[] sur le pointeur de la chane et de provoquer ainsi une erreur mmoire. Par consquent, il doit en faire une copie dans un bloc allou avec son propre allocateur. Cela peut affecter les performances mais, comme on la dit, reste impratif dans le contexte dun grand programme pour conserver la cohrence du programme et viter les bogues. La deuxime approche consiste imposer lappelant de fournir une zone de mmoire de taille sufsante pour y stocker les rsultats. Dans ce cas, lappelant est matre de lallocateur utilis, et na donc pas recopier les donnes retournes par la fonction appele. Toutefois, la difcult est ici de contrler que la taille de la zone mmoire est sufsante pour y stocker les donnes. Cette taille doit donc toujours tre fournie en paramtre la fonction appele, et un code derreur spcique signalant que la taille est insufsante doit tre dni. Le problme se dcale alors vers celui de la dtermination de la taille ncessaire pour stocker les donnes avant lappel de la fonction. Gnralement, la fonction qui retourne le rsultat peut tre appele avec un pointeur nul sur le bloc de donnes, et elle renvoie dans ce cas la taille ncessaire pour y stocker les rsultats. Malheureusement, cette solution peut tre lente (dans le cas o la dtermination de la taille exige un

248

Chapitre 12. Conventions de codage et techniques de base traitement consquent, parfois mme lexcution de lopration blanc ) et non sufsante (si la taille peut varier dun appel un autre). Par exemple, la fonction GetComputerName de Windows permet de retourner le nom de lordinateur dans un tampon allou par lappelant. Elle prend en paramtre le pointeur sur ce tampon et un pointeur sur un entier contenant la longueur de ce tampon. Si cette longueur est insufsante, cet entier est modi pour recevoir la taille ncessaire pour que lappel russisse compltement, et un code derreur est retourn.
char *szBuffer = NULL; // Rcupration de la taille ncessaire lappel : unsigned long ulSize = 0; GetComputerName(szBuffer, &ulSize); // Allocation de la mmoire du tampon : ulSize++; // Pour le nul terminal szBuffer = new char[ulSize]; // Appel de la fonction : GetComputerName(szBuffer, &ulSize);

Ce simple exemple suft pour dmontrer que cette technique est nettement plus lourde, car elle impose dappeler plusieurs fois de suite la fonction. De plus, il faut gnralement contrler chaque appel si toutes les donnes ont bien t reues, dans lventualit o les donnes rcuprer changent de taille entre chaque appel. Ainsi, le code de lexemple prcdent ne fonctionne pas si le nom de lordinateur est chang entre le premier et le deuxime appel GetComputerName. En revanche, dans les situations o les donnes peuvent tre rcupres par blocs (par exemple pour des oprations de type entre/sortie), cette solution est extrmement pratique puisquelle permet de rcuprer les donnes directement dans la zone mmoire spcie par lappelant, y compris dans les couches systmes des priphriques dentre / sortie. Comme on peut le constater, chaque technique a ses avantages et ses inconvnients. Si lon peut garantir que lallocateur mmoire est unique dans tous le programme (par exemple lallocateur de la bibliothque C utilise sous forme de bibliothque de liaison dynamique), les copies de donnes imposes par la premire technique ne sont plus ncessaires et le choix est vite fait. Dans les mcanismes dentre / sortie, la deuxime solution est gnralement plus classiquement adopte car elle savre la plus performante globalement.
Note : En aucun cas une fonction de bibliothque ne doit retourner de donnes statiques. Cela est une porte ouverte aux effets de bords et nest pas utilisable dans un contexte multithread (un thread peut en effet modier le rsultat obtenu par un autre de manire imprvisible dans ce cas).

12.3. Considrations systme


Certaines considrations systme doivent tre prises en compte lors de la ralisation des programmes. Elles peuvent en effet inuer fortement sur sa portabilit ou sa conception. Les sections suivantes sattachent donc dcrire les plus importantes dentre elles.

249

Chapitre 12. Conventions de codage et techniques de base

12.3.1. La scurit
La scurit doit tre prise en compte ds le dbut de la conception dun programme, parce que les rgles de scurit sont de plus en plus appliques au pied de la lettre dune part, et parce que cest une notion transverse dautre part. Les politiques de scurit correctes impliquent de dnir plusieurs remparts, an de sassurer que si une faille est exploite lattaquant nobtiendra pas le contrle total du systme. Cela implique que les oprations qui traversent plusieurs couches systmes sont susceptibles de se conformer aux politiques de scurit de lensemble de ces couches. Cela donne autant de raisons une application de ne pas fonctionner, et de rendre son dploiement plus difcile. Inversement, une application constitue de diffrents composants est aussi sre que le plus faible dentre eux. Autrement dit, un rempart nest pas plus fort que le plus faible de ses murs ou la plus faible de ses portes. Cela implique encore une fois que les applications qui ont pour vocation de sintgrer dans un systme sr doivent prendre en compte la scurit tous les niveaux, faute de quoi il y aura sans doute une faille dans un de ses composants. Or les mcanismes de scurit sont souvent intrusifs. Dans le cas le plus strict, chaque appel systme peut tre soumis autorisation, et donc est susceptible dchouer si lapplication sexcute dans un contexte de scurit non privilgi. Cela signie que les erreurs doivent tre systmatiquement traites par lapplication. Bien entendu, une application correctement crite traite dj les erreurs et adopte un comportement adquat. Cependant, les erreurs provenant dun accs refus une fonction ne sont pas naturellement prises en compte par les programmeurs, car il est difcile dimaginer lensemble des oprations qui peuvent chouer pour des raisons de scurit. De plus, ce type derreur relve de lerreur de conguration ou du contexte, et non dun problme fonctionnel. La manire de ragir ces erreurs peut donc tre diffrente de celle dont la cause est, par exemple, un manque de ressources. En particulier, laide au diagnostic est toujours un plus pour lutilisateur. En effet, les erreurs de programmes dues un manque de droits sont trs mal perues des utilisateurs, qui se sentent fatalement frustrs, dautant plus que ce quils recherchent, cest avant tout que le programme fonctionne. La politique applique est donc souvent de dsactiver totalement la scurit, pour le pire et au dtriment de tout le monde ! Par ailleurs, ces erreurs sont la cause dune perte de temps considrable lors des phases de dploiement. Par consquent, si lapplication est capable dindiquer la raison de son dysfonctionnement avec prcision, lutilisateur sera plus enclin linstaller conformment aux rgles de lart et la faire fonctionner correctement, et les diagnostics seront plus aiss lors du dploiement. Le choix est donc simple : soit la scurit est prise en compte ds le dbut des spcications du logiciel, soit elle est ignore. Dans ce cas, une provision importante doit tre faite pour lala induit par une scurit mal matrise, et le risque des problmes qui en dcouleront couvert en consquence.

12.3.2. Le multithreading
Le multithreading est une fonctionnalit extrmement puissante et permettant dobtenir des programmes performants et ractifs. Cependant, comme nous allons le voir, il induit des contraintes supplmentaires, et requiert de le prendre en compte au niveau de la conception des programmes.

12.3.2.1. Gnralits
Le multithreading est la possibilit quont les programmes de disposer de plusieurs ux dexcution, appels classiquement threads , sexcutant de manire concurrentielle ou parallle.

250

Chapitre 12. Conventions de codage et techniques de base Gnralement, les systmes dexploitation sont capables dexcuter les threads de manire concurrente, en affectant chacun deux les ressources de calcul de la machine tour de rle. Chaque thread sexcute donc un court laps de temps appel time slice (littralement, tranche de temps ), puis redonne la main au systme, qui peut basculer vers un autre thread (on appelle cette opration un changement de contexte ). Ainsi, mme si une seule unit de calcul est prsente dans la machine, tout apparat comme si les threads sexcutaient en parallle. La manire dont le basculement dun thread un autre se fait est une caractristique du systme dexploitation. Si les threads doivent rendre la main volontairement pour provoquer un changement de contexte, il sagit de multitche coopratif. Si, en revanche, le systme soccupe de tout et suspend les threads qui ont puis leur quota de ressource de calcul pour excuter dautres threads, il sagit de multitche premptif. Dans ce cas, les ressources de calcul sont attribues en fonction de priorits, et les premptions se font chance du time slice ou lorsquun thread de priorit suprieur devient ligible. En pratique, le multithreading est utilis pour rendre indpendant les temps de raction dune partie dun programme vis vis dautres parties du mme programme. Ainsi, le programme peut effectuer les oprations longues ou susceptibles de bloquer lors de laccs des ressources lentes dans des ux dexcution ddis, et continuer fonctionner normalement en attendant que ces oprations se terminent. Par exemple, un programme peut copier des chiers, rcuprer des donnes sur le rseau, ou effectuer un calcul long tout en continuant interagir avec lutilisateur. Tout cela est ralisable sans multithreading, en utilisant des mcanismes dentre/sortie asynchrones ou en dcoupant les calculs en morceaux et en les excutant quand lutilisateur ne ragit pas, mais cela complique srieusement la programmation. En effet, les tches sont dans ce cas destructures et requirent de maintenir un tat courant pour chaque opration en cours dexcution. Le multithreading parvient au mme rsultat, mais en laissant au systme le soin de dcouper, si ncessaire, les ux dexcution des diffrents threads. Le multithreading est galement un moyen efcace daugmenter les performances. En effet, bloquer un programme en attendant une ressource lente nest gnralement pas efcace, puisque pendant ce temps lunit de calcul ne travaille pas. De mme, il est prfrable, quitte attendre, dattendre plusieurs ressources lentes simultanment (par exemple une criture sur disque et la rception de donnes provenant du rseau). Il est donc recommand, en gnral, dutiliser un thread pour communiquer avec chaque entit avec lequel le programme doit travailler. Bien entendu, les mcanismes dentre/sortie asynchrones sont l aussi utilisables, mais simplement moins pratiques. Enn, le multithreading et le multitche sont les seuls moyens mis la disposition des programmes pour bncier des ressources de calcul multiples des machines multiprocesseurs ou hyperthreades (machines dont le processeur simule plusieurs processeurs logiques pour optimiser le taux dutilisation de lunit de calcul par rapport aux units daccs la mmoire et de dcodage des instructions). En effet, sur ces machines, le systme dexploitation peut rpartir les threads sur les units de calcul et les faire fonctionner rellement en parallle. Ainsi, si les threads sont totalement indpendants, le gain thorique peut aller jusqu un facteur gal au nombre dunits de calcul disponibles. En pratique, le gain est toujours moindre car il y a toujours des accs des ressources communes nacceptant pas plusieurs accs simultans ou dont la bande passante ne permet pas dalimenter toutes les units de calcul en pleine vitesse.

12.3.2.2. Utilisation du multithreading


Classiquement, un programme monothread dnit une unique liste des instructions qui doivent tre excutes par la machine. Cette liste est dtermine par un point dentre, gnralement la fonction main. Dans les programmes multithreads, le programme peut demander au systme dexploitation

251

Chapitre 12. Conventions de codage et techniques de base la cration dautres ux dexcution qui sexcuteront de manire simultane. Pour cela, il fournit au systme ladresse de fonctions qui serviront de point dentre aux diffrents threads, et dont lexcution dnira la liste dinstructions de ces threads. Les fonctions permettant de crer de nouveaux threads sont respectivement _beginthreadex sous Windows et pthread_create sous Unix et Linux. La documentation respective de ces environnements vous indiquera comment les utiliser. Ainsi, un programme multithread peut excuter plusieurs oprations de manire simultane, et acqurir un comportement multitche semblable lexcution simultane de plusieurs processus dans les systmes multitches. La diffrence ici est que les diffrents threads appartiennent au mme processus, et partagent donc le mme espace dadressage. Cela signie que les donnes du programmes sont accdes par tous les threads du processus en parallle. Les communications entre les threads sont donc plus faciles mettre en uvre et plus performantes que dans le cas de plusieurs processus qui sexcutent de concert.

12.3.2.3. Les piges du multithreading


Le multithreading est donc une technique intressante quasiment incontournable dans tous les programmes non triviaux. Cependant, il apporte galement son lot de problmes, et provoque lapparition de nouveaux types de bogues, dont les principaux sont les suivants :

les concurrences daccs ; les indterminismes ; les interblocages.

Les concurrences daccs sont dues au fait mme que les donnes du programme sont accessibles de lensemble des threads. Ainsi, si un thread modie les donnes pendant quun ou plusieurs autres threads cherchent les lire, les donnes lues risquent dtre incohrentes. Par exemple, le dbut des donnes lues peut correspondre aux donnes avant modication par le thread crivain, et la n des donnes aux donnes aprs modication. De mme, si plusieurs threads cherchent crire des valeurs diffrentes en mmoire, le rsultat est gnralement indtermin. Ces concurrences daccs sont gnralement limines en utilisant des primitives dexclusion mutuelle qui sapparentent des verrous. Chaque thread devant accder aux donnes partages doit au pralable acqurir le verrou, qui est une ressource gre par le systme qui garantit quun seul thread un instant donn peut en disposer (voir la fonction EnterCriticalSection sous Windows et la fonction pthread_mutex_lock sur les systmes Posix). Si un autre thread dispose du verrou, le thread appelant est bloqu (cest--dire que son excution est suspendue) jusqu ce que le dtenteur du verrou le relche. Les portions de code excutes avec un verrou pris sont classiquement appeles des sections critiques . Les indterminismes sont une gnralisation des concurrences daccs et consistent en la production de rsultats diffrents chaque excution du programme, en raison des diffrences de vitesse dexcution des diffrents threads qui le constituent. Par exemple, un thread peut utiliser un algorithme qui effectue deux traitements diffrents en fonction de la valeur dune variable, et un autre thread peut modier cette valeur de manire simultane. Mme si les accs la variable sont contrles par un verrou, le comportement du premier thread peut tre diffrent selon que le deuxime thread a eu le temps ou non de modier la variable. Les indterminismes sont gnralement limins en utilisant des primitives de synchronisation entre threads (voir les fonctions CreateEvent, WaitForSingleObject et SetEvent sous Windows, et les fonctions pthread_cond_init, pthread_cond_wait et pthread_cond_signal sur les systmes Posix). Dans notre exemple, le premier thread peut attendre que le deuxime ait modi

252

Chapitre 12. Conventions de codage et techniques de base la variable. Cest donc au deuxime thread de signaler quil la fait, en indiquant au systme que la condition quattend le premier thread est vrie. Les interblocages sont caractriss par le fait quun ou plusieurs thread restent suspendus de manire permanente, en attendant une ressource qui ne peut se librer ou une condition qui ne peut se produire, car le thread qui est capable de lever la condition de la suspension est lui-mme en attente dune autre ressource ou dune autre condition dpendant du premier thread. Le cas dcole classique est celui o deux threads veulent accder deux verrous diffrents, et que chacun deux ont pris laccs lun des verrous et attend que lautre thread relche lautre. Il y a deux techniques fondamentales qui permettent dliminer les interblocages :

rduire la porte des sections critiques au code qui accde rellement aux donnes partages par les threads ; sassurer que les verrous pris successivement le sont toujours dans le mme ordre par tous les threads du programme.

Ces deux rgles ont un impact fort sur la conception des programmes orients objets.
Note : On notera quun thread peut sautobloquer (par exemple en attendant la n dun traitement et que ce traitement est sa charge). Il nest donc pas ncessaire, techniquement parlant, de disposer de plusieurs threads pour provoquer un blocage.

12.3.2.4. Multithreading et programmation objet


Le multithreading est relativement difcile mettre en uvre dans le contexte de la programmation objet, car les donnes membres des objets doivent tre protges contre les accs multithreads. Pratiquement, il faut sassurer que ces donnes membres sont bien protges quelles que soient les mthodes et les interfaces utilises, dune part, et quelles sont toutes cohrentes tout instant, dautre part. La technique la plus classique est de dnir un verrou pour lobjet et de le prendre pendant lexcution de chaque mthode de lobjet. Cette technique convient parfaitement dans le cadre dobjets simples, mais peut provoquer des interblocages dans le cas dinteractions complexes avec dautres objets. En effet, si lobjet utilise un autre objet disposant lui-mme dun verrou, il y a un fort risque que plusieurs threads ralisent un interblocage en utilisant ces objets de manire simultane (en accdant aux verrous de ces objets dans un ordre diffrent). Comme il est assez difcile de dterminer, la simple lecture de linterface dun objet, quels sont les autres objets quil va utiliser, la programmation multithreade en environnement objet est extrmement technique. De plus, dans les systmes composants, ou dans les programmes utilisant des plugins, certains objets utiliss peuvent avoir t dvelopps sans tenir compte du multithreading. De ce fait, ces objets ne peuvent tre accds que par un unique thread durant toute leur dure de vie. Cela implique la mise en place de threads de travail pour certains objets et de mcanismes de changement de contexte pour lexcution des appels de mthodes par ces threads de travail. Classiquement, cela conduit la dnition de modles de threading pour les objets et d appartements dans lesquels les objets dun certain modle sont cloisonns an de garantir une utilisation correcte (ces notions sont particulirement visibles dans les systmes composants tels que la technologie COM de Microsoft ou son concurrent CORBA). Malheureusement, ces notions sont complexes et imposent des restrictions fortes, trs souvent mal matrises par la plupart des programmeurs. De plus, elles ne peuvent tre prises en compte a posteriori, car elles imposent systmatiquement une architecture particulire pour les programmes, et les

253

Chapitre 12. Conventions de codage et techniques de base bogues lis aux modles de threading ne peuvent gnralement pas se corriger sans une modication structurelle importante du programme. Par consquent, et l je pse mes mots, il est extrmement important de prendre en compte les problmes relatifs au multithreading ds la conception du logiciel. Plus que jamais, en environnement multithread, les recommandations suivantes devront tre respectes :

il faut parfaitement identier les objets manipuls par le programme ; il faut parfaitement identier le contexte dutilisation de ces objets ; il faut parfaitement dnir les interfaces utilisables ; il faut parfaitement dnir la manire dutiliser ces interfaces et la dynamique du systme.

La premire rgle est un prrequis la deuxime et est impos par la mthode de conception objet. La deuxime rgle a pour but de forcer prcisment la dnition des threads susceptibles dutiliser les objets, an didentier les donnes protger et les verrous mettre en place, ainsi que les modles de threading des objets. La troisime rgle permet de contrler les points dentre des threads. Enn, la dernire permet de dterminer la liste des ressources utilises, comment les objets seront utiliss, et o les changements de contexte se feront.

12.3.2.5. Limitations et contraintes lies au multithreading


Si le multithreading peut savrer utile, il ne faut pas en abuser non plus. En effet, les threads restent des ressources systmes relativement lourdes, et ont malgr tout un certain cot. Premirement, ils induisent une surcharge du systme cause des changements de contexte. En effet, mme si les machines modernes sont capables de changer de thread rapidement, les caches dinstructions du processeur sont malgr tout invalids lorsquun tel changement se produit. Il est noter que le multithreading est sur ce point malgr tout prfrable au multiprocessus, puisquun changement de contexte interprocessus implique un changement despace dadressage et donc galement linvalidation de tous les caches systme. De plus, chaque thread dispose dune pile dexcution, an de mmoriser les variables locales et les appels des fonctions quil excute. Cette pile dappel consomme videmment de la mmoire, et mme si elle est agrandie la vole par le systme, elle implique de rserver une plage dadresses dans le processus. La cration dun grand nombre de threads implique donc une fragmentation de lespace dadressage, qui peut ensuite faire chouer les allocations dynamique de mmoire. titre dexemple, la taille rserve dans lespace dadressage pour la pile dun thread est gnralement de un mgaoctet (attention, il sagit bien dune plage dadresses rserves et non de mmoire effectivement alloue). Sur les systmes trente-deux bits, lespace dadressage fait quatre gigaoctets, ce qui limite de facto le nombre de threads quatre mille. De plus, lespace dadressage des processus tant gnralement divis en deux parties (la partie suprieure tant utilise par le systme pour y placer une image de la mmoire du noyau), le nombre maximal est en pratique souvent infrieur deux mille. Pour aller au del, il faut rduire la taille des piles des threads, mais il serait plus sain dans ce type de situation de se demander si le programme est bien conu... Une autre restriction courante dans les programmes multithreads est que les bibliothques et les environnement graphiques exigent, souvent pour des raisons de compatibilit avec danciennes bibliothques systme ou avec des composants graphiques monothreads, que toutes les oprations relatives linterface homme-machine soient effectues sur le thread principal (cest--dire le thread qui excute la fonction main). En pratique donc, il est recommand que ce thread ne prenne en charge que les oprations de gestion de linterface graphique, et dlgue tous les traitements des threads de

254

Chapitre 12. Conventions de codage et techniques de base travail. Cest une contrainte prendre en compte au niveau de la conception mme du logiciel. Il faut donc tablir des mcanismes de communication entre les threads, qui permettent au thread principal de poster une demande dexcution dune commande un thread de travail et den attendre le rsultat, tout en continuant grer linterface graphique (afchage dun sablier, redessin de la fentre, etc.).

12.3.3. Les signaux


Les signaux sont des mcanismes utiliss pour interrompre le ux dexcution des programmes et leur indiquer un vnement particulier. Le principe des signaux prend racine dans les fondements dUnix et fournit une forme de communication inter-processus rudimentaire. Lorsquun programme reoit un signal (soit de la part du systme, soit de la part dun autre programme, comme la commande kill par exemple), son excution est interrompue et une fonction particulire appele gestionnaire du signal est excute. Gnralement, si le programme na dni aucun gestionnaire de signal ou na pas demand les masquer, le systme larrte brutalement. Si en revanche ce gestionnaire existe, il est excut et le processus normal du programme reprend son cours. Les signaux fournissent donc un mcanisme semblable aux interruptions pour les systmes dexploitation. Cependant, ils souffrent de dfauts consquents, qui rendent leur emploi difcile :

il existe deux API, lune spcie dans la norme C mais trs peu pratique, lautre spcie dans la norme POSIX, mais non portable ; lAPI ANSI C na pas t interprte de la mme manire par tous les systmes et nest donc pas portable (selon limplmentation, les gestionnaires de signaux doivent tre rinstalls ou non aprs chaque occurrence du signal) ; les signaux peuvent se produire de manire asynchrone, donc virtuellement nimporte quand, et le programme doit tre en mesure de pouvoir les traiter en permanence ; de ce fait, les gestionnaires de signaux ne peuvent accder quasiment aucune ressource du programme (en particulier, il est interdit dappeler les fonctions de la bibliothque C, pas mme les fonctions malloc/free ou printf) ; selon la manire dont ils sont utiliss, les signaux peuvent interrompre les appels systmes, forant ainsi le programmeur effectuer tous les appels systmes dans une boucle pour les ressayer en cas dapparition dun signal ; les signaux ne se comportent pas de manire dterministe dans les programmes multithreads, car ils peuvent tre dlivrs un thread quelconque du programme.

Les signaux sont donc extrmement techniques manipuler. En pratique, ils ne sont utiles que pour les programmes extrmement simples. Dans les autres programmes, il est plus simple dadopter une des mthodes suivantes :

soit les signaux sont purement et simplement masqus ; soit le programme sinterrompt brutalement ds la rception dun signal ; soit les signaux sont traits dans un thread ddi cette tche.

Cette dernire solution se met classiquement en place en bloquant tous les signaux dans le thread principal avec la fonction pthread_sigmask avant de crer les autres threads du programme, puis en crant le thread ddi la gestion des signaux. Le rle de ce thread est ensuite de rcuprer tous

255

Chapitre 12. Conventions de codage et techniques de base les signaux en boucle laide de la fonction sigwait. Consultez la documentation des spcications Unix unies pour plus de dtails sur ces fonctions.
Note : Les signaux sont disponibles sous Windows, mais sont simuls par des appels de fonction de rappel sur des threads crs pour loccasion. Leur emploi est fortement dconseill, car les mcanismes des signaux ne sont absolument pas utiles et implments a minima sous Windows.

12.3.4. Les bibliothques de liaison dynamique


Les bibliothques de liaison dynamique ( DLL , en anglais, abrviation de Dynamic Link Library ) sont des bibliothques de programme qui ne sont pas intgres directement dans le chier excutable du programme. Cela signie que laccs leurs fonctionnalits ne peut se faire de manire directe, et ncessite une opration pralable de chargement et de rcupration des symboles de la bibliothque. Les bibliothques de liaison dynamique sont donc des bibliothques qui sont charges la demande par les programmes qui dsirent les utiliser. De ce fait, elles permettent de raliser des choses que les bibliothques de liaison statique nautorisent pas aussi facilement. Bien entendu, cela impose quelques contraintes supplmentaires, quil est important de connatre.

12.3.4.1. Les avantages des bibliothques de liaison dynamique


Les principaux avantages de ces bibliothques sont les suivants :

elles peuvent tre partages entre plusieurs programmes, conomisant ainsi espace disque et, parfois, espace mmoire (en permettant au systme dexploitation de factoriser le code de ces bibliothques entre tous les programmes) ; elles permettent de ne charger les fonctionnalits demandes qu la demande, rduisant ainsi la consommation mmoire ; elles permettent de rendre les programme plus modulaire, en le dcoupant en un programme principal et en plusieurs bibliothques de liaison dynamique secondaires.

Ce dernier point est sans doute le plus important, car il ouvre les possibilits suivantes :

il est possible de raliser des bibliothques de ressources , dont le but est de stocker des informations dpendantes du contexte (par exemple la langue des messages du programme) et de permettre den charger la version adapte au contexte courant ; la mise jour des programmes est simplie, car seuls les composants concerns par une correction de bogue doivent tre mis jour ; il est possible de rendre les programmes extensibles, en permettant le chargement dynamique de greffons ( plugins en anglais) utiliss via des interfaces standardises et en fonction de la conguration du programme.

256

Chapitre 12. Conventions de codage et techniques de base

12.3.4.2. Les mcanismes de chargement


Il existe classiquement deux manires daccder aux fonctions dune bibliothque de liaison dynamique. La premire mthode est totalement manuelle et fait appel des fonctions du systme qui permettent gnralement les oprations suivantes :

le chargement de la bibliothque dans lespace dadressage du processus (fonctions LoadLibrary sous Windows et dlopen sous Unix ou Linux) ; la recherche dun symbole (variable ou fonction) dans la bibliothque partir de son nom et lobtention de son adresse (fonctions GetProcAddress sous Windows et dlsym sous Unix ou Linux) ; lutilisation du symbole via le pointeur obtenu ; le dchargement de la bibliothque une fois quelle nest plus ncessaire (fonctions FreeLibrary sous Windows et dlcose sous Unix ou Linux).

Cette mthode est extrmement lourde, car elle ncessite de manipuler les symboles par leur nom. Cela peut tre relativement technique dans le cas des bibliothques de liaison dynamique crites en C++, en raison du fait que les noms des symboles sont dcors par les informations de typage du langage. Mais cette mthode est la base du chargement des fonctionnalits la vole par lapplicatif et permet la gestion des greffons de manire totalement dynamique (un greffon peut tre install et charg pendant que le programme fonctionne). Une autre mthode, gnralement plus utilise, est galement disponible. Elle consiste simplement raliser ldition de liens du programme avec la bibliothque de liaison dynamique comme sil sagit dune bibliothque statique normale. Les symboles sont alors marqus comme tant non rsolus dans le programme, et une table est ajoute pour indiquer comment le systme devra rsoudre ces symboles lexcution. Ainsi, lorsque le programme sexcutera, les symboles seront rsolus dynamiquement, lors dune phase appele dition de liens dynamique (et dont le nom de ces bibliothques provient). Cette technique tant prise en charge automatiquement par le systme dexploitation et par le chargeur de programme, il est vident quelle est plus simple demploi. En particulier, il nest plus ncessaire de connatre les noms des symboles, ce qui permet de faire des bibliothques de liaison dynamique en C++ facilement. En revanche, elle impose de connatre lensemble des bibliothques utilises par le programme lors de son dition de liens, et ne permet donc pas de faire de bibliothques de fonctions charges la demande par lapplicatif ni de faire des greffons. Lopration de rsolution des liens est relativement technique et dpend bien entendu beaucoup du systme dexploitation utilis. En gnral, les liens qui doivent tre rsolus sont enregistrs dans une table stocke dans les chiers binaires (la table des symboles imports), que le chargeur du systme consulte et met jour lors du chargement de ces chiers. Tous les symboles imports par les programmes sont utiliss indirectement, via ces tables. Ainsi, une fois la rsolution des liens effectue, tous les symboles peuvent tre accds via une simple indirection.

12.3.4.3. Relogement et code indpendant de la position


Lun des plus gros problmes des bibliothques de liaison dynamique est quil nest gnralement pas possible, lors de leur cration, de dterminer ladresse laquelle elles pourront tre charges en mmoire. Il est possible de xer une adresse de chargement privilgie, mais lutilisation de cette adresse ne peut tre garantie. En effet, lors du chargement, il se peut que le programme ait plac des

257

Chapitre 12. Conventions de codage et techniques de base ressources cette adresse ou quil ny ait pas assez despace disponible pour charger la bibliothque cet endroit. Selon le systme dexploitation et le processeur cible utilis, la non-constance de ladresse de chargement peut avoir des consquences importantes sur le code gnr par le compilateur ou les performances du programme. En effet, cela implique que tous les symboles de la bibliothque ne peuvent tre accds avec une adresse absolue. Il faut ncessairement les rfrencer par une adresse dnie par rapport ladresse de chargement de la bibliothque. Comme cette adresse ne peut tre connue avant le chargement de la bibliothque, et comme on ne peut imposer de manire sre ladresse de chargement de la bibliothque, le code gnr par le compilateur doit tre modi pour chaque utilisation dun symbole de la bibliothque, que ce soit une variable ou une fonction ! Cette opration, appele relogement ( relocation en anglais), est ralise soit par le compilateur, soit par le chargeur du systme. Sous Windows, cest le chargeur qui sen occupe. Toutes les bibliothques de liaison dynamique Windows disposent dune table des relogements effectuer une fois la bibliothque charge en mmoire. Cette table indique toutes les instructions du code de la bibliothque qui doivent tre corriges en fonction de ladresse effectivement utilise pour charger la bibliothque. La correction consiste simplement en une simple addition de cette adresse chaque offset utilis par les instructions dclares dans la table des relogements. Ainsi, une fois le relogement effectu, le code de la bibliothque est corrig pour accder ses donnes, comme si le compilateur avait devin ladresse de chargement de la bibliothque lors de sa cration. Inversement, sous les systmes Unix et Linux, le code gnr par le compilateur peut tre spcialement crit de manire ne jamais faire de rfrence directe aux symboles. Un tel code est dit indpendant de sa position ( Position Independent Code en anglais). Pratiquement, les adresses des symboles sont rcupres dans une table globale de la bibliothque (appele la Global Offset Table ). Cette table est mise jour lors du chargement de la bibliothque. Mais cela ne suft pas : pour y accder, le code de la bibliothque doit en plus conserver ladresse de cette table dans toutes les fonctions, ce qui se fait gnralement en rservant un registre du processeur cet effet ou en la stockant dans une mmoire accessible directement (par exemple sur la pile).
Note : Si la technique utilise par les systmes Unix est relativement lourde et peut avoir un lger impact sur les performances du code lexcution, celle utilise par Windows impose la modication du code des bibliothques. De ce fait, ce code ne peut gnralement plus tre partag entre plusieurs processus, et une consommation mmoire accrue du systme sensuit. Cela nest toutefois pas drangeant pour les bibliothques spciques un programme, qui nont pas t cres pour partager le code mais dans le but de bncier de la souplesse de dploiement ou des mcanismes de plugins. Les chargeurs de programmes Unix sont capables deffectuer le relogement des symboles dans les bibliothques dont le code nest pas compil de manire tre indpendant de la position. Cependant, cette technique cela nest pas recommand, en raison du fait que le code de ces bibliothques ne peut plus tre partag entre les processus.

12.3.4.4. Optimisation des bibliothques de liaison dynamique


Comme on la vu, les bibliothques de liaison dynamique sont trs pratiques, mais imposent un travail supplmentaire au chargeur du systme dexploitation lors de leur chargement. De plus, sur les plateformes Unix et Linux, laccs aux symboles exports par les bibliothques nest gnralement pas direct.

258

Chapitre 12. Conventions de codage et techniques de base Tout cela se traduit par un temps de chargement des programmes plus long, et parfois une excution lgrement plus lente. Si cela nest pas gnant pour les petits programmes, ce surcot peut devenir consquent pour les grands programmes, qui utilisent de nombreuses bibliothques pouvant exporter des milliers de symboles. La situation est critique pour les programmes C++ sous Unix et Linux car, par dfaut, tous les symboles sont exports (jusquau moindre accesseur de la moindre classe). Toutefois, si le compilateur peut savoir, ds la gnration du code, quun symbole ne sera pas export par la bibliothque, il peut raliser des optimisations. En effet, il peut dans ce cas faire en sorte que les instructions qui y accdent le fasse de manire relative leur adresse. Notez cependant que tous les processeurs ne disposent pas forcment des modes dadressage ncessaires pour cela (par exemple, les processeurs x86 ne permettent pas daccder aux donnes relativement ladresse du pointeur dinstruction, par contre, ils sont tout fait capables de faire des appels de fonctions et des sauts relatifs ladresse de linstruction courante). Dans ce cas, les symboles peuvent malgr tout tre rfrencs relativement ladresse de chargement de la bibliothque, ce qui vite de passer par la table des offsets globaux. La meilleure manire doptimiser les programmes qui utilisent intensivement les bibliothques de liaison dynamique est donc de rduire le nombre de symboles exports au strict ncessaire, cest-dire linterface publique des bibliothques. Cette technique permet de plus de mieux encapsuler les structures de donnes des bibliothques, et est donc intressante double titre. Il est donc intressant de pouvoir spcier explicitement quels sont les symboles exports directement au niveau du code source. Cela permet en effet :

de rduire la taille de la table des symboles exports, et dacclrer leur recherche lors de la rsolution des liens dynamiques ; de rduire la taille de la table des offsets des symboles globaux de la bibliothque, et dacclrer le chargement de la bibliothque et doptimiser le code en rduisant le nombres de symboles accds via cette table.

Note : La rduction du nombre de symboles exports na toutefois aucun impact sur la taille des tables de relogement des bibliothques Windows ou des bibliothques Unix/Linux dont le code nest pas indpendant de sa position. Le temps de lopration de relogement et le taux des pages mmoire contenant un code non modi, et donc partageable entre processus, sont donc inchangs.

Par dfaut, les bibliothques de liaison dynamiques Windows nexportent aucun symbole. La liste des symboles exports doit donc tre fournie lditeur de liens lors de la cration de la bibliothque. Cela se fait gnralement en lui fournissant dun chier de dnition des symboles exports sur sa ligne de commande (ces chiers portent gnralement lextension .def ). Sous Unix et Linux, tous les symboles externes sont exports par dfaut. Il possible de modier la nature des symboles lors de ldition de liens, en spciant, encore une fois, la liste des symboles exports lditeur de liens. Toutefois, la manire de procder pour dnir les symboles exports et en donner la liste lditeur de liens est extrmement technique. De plus, cette technique ne permet pas au compilateur doptimiser le code. Cette solution nest par ailleurs pas pratique du tout dans le cas des bibliothques de liaison dynamique crites en C++, du fait que la liste des symboles peut ne pas tre connue puisque leurs noms sont dcors par le compilateur. Par consquent, la seule vraie solution est de dnir les symboles exports directement au niveau du code source, par une construction syntaxique ddie cet effet.

259

Chapitre 12. Conventions de codage et techniques de base Or,le C standard ne dispose que de peu de constructions syntaxiques pour cela. En pratique, seul le mot clef static permet, par dnition, de ne pas exporter un symbole. Malheureusement, il ny a pas de solution standard permettant de ne pas exporter un symbole non statique, et il est rare que lon nutilise quun seul chier pour coder une bibliothque ! Des extensions ont donc t ajoutes aux compilateurs an de spcier quels sont les symboles qui doivent tre exports et quels symboles sont uniquement utiliss par la bibliothque. Les mcanismes utiliss sont relativement semblables sous Windows et sous Unix et Linux, et consistent simplement appliquer un attribut aux symboles exports lors de leur dclaration. Ainsi, sous Windows, si aucun chier dexport nest fourni lditeur de liens, seuls les symboles dclars avec lattribut dllexport sont exports :
extern int __declspec(dllexport) global; extern int __declspec(dllexport) get_new_int(); extern int private_fuction(int);

Sous Unix et Linux, les symboles disposent dun attribut de visibilit, permettant de spcier la porte de ces symboles lors de ldition de liens. La valeur default de cet attribut correspond lusage classique des symboles : ils sont exports sils sont externes, et sont privs sils sont statiques. Lattribut de visibilit peut aussi prendre la valeur hidden , ce qui a pour effet de masquer ces symboles une fois ldition de liens ralise. Le compilateur peut galement raliser les optimisations lors de la gnration du code indpendant de sa position. Avec le compilateur GCC, la valeur de lattribut de visibilit peut tre xe avec la syntaxe suivante :
extern int __attribute__((visibility("default"))) global; extern int __attribute__((visibility("default"))) get_new_int(); extern int __attribute__((visibility("hidden"))) private_fuction(int);

Les symboles dont la visibilit nest pas explicitement spcie prendront la visibilit spcie par loption -fvisibility de GCC. Si cette option nest elle-mme pas spcie, la valeur par dfaut de lattribut de visibilit est default . Par consquent, il est recommand de marquer explicitement les symboles exporter avec la visibilit default laide dun attribut, et de forcer la visibilit par dfaut des autres symboles hidden laide de loption -fvisibility .
Note : Sous Windows, les symboles imports doivent tre dclars avec lattribut dllimport . Par consquent, les chiers den-tte doivent fournir deux dclarations pour les symboles des bibliothques de liaison dynamique, une avec lattribut dllexport, qui sera utilise par la bibliothque elle-mme, et une avec lattribut dllimport, qui sera utilise par les programmes clients. Il est courant de dnir une macro DLLSYMBOL conditionne par une option de compilation fournie en ligne de commande et dpendant du contexte dutilisation du chier den-tte :
/* Dfinition de la macro DLLSYMBOL : */ #if defined(MA_DLL) /* Utilisation dans la DLL MA_DLL, compile avec loption MA_DLL (dfinie sur la ligne de commande du compilateur) : */ #define DLLSYMBOL __declspec(dllexport) #else /* Utilisation dans un programme client : */ #define DLLSYMBOL __declspec(dllimport) #endif /* Dfinition des symboles de la DLL : */

260

Chapitre 12. Conventions de codage et techniques de base


int DLLSYMBOL global; int DLLSYMBOL get_new_int();

Cette technique peut galement tre utilise pour simplier la spcication de la visibilit des objets sous Unix et Linux.

12.3.4.5. Initialisation des bibliothques de liaison dynamique


Les bibliothques de liaison dynamique peuvent dnir des fonctions dinitialisation et de nettoyage, que le chargeur du systme peut appeler respectivement aprs le chargement et avant le dchargement des bibliothques de liaison dynamique. La manire dont ces fonctions sont dnies dpend du systme dexploitation. En gnral, ces informations sont fournies lditeur de liens statiques lors de la cration du programme. Classiquement, le point dentre des bibliothques de liaison dynamique sous Windows se nomme DllMain :
BOOL DllMain(HINSTANCE, DWORD dwReaon, LPVOID lpvReserved);

Cette fonction est appele lors du chargement et lors du dchargement de la bibliothque dans le processus et, sauf conguration explicite, lors de la cration et lors de la terminaison dun thread dans le processus. La raison de lappel est indique par un paramtre de la fonction. Lappel se fait dans le contexte du thread concern par lappel. Autrement dit, les appels indiquant que la bibliothque est charge ou dcharge sont raliss dans le contexte du thread qui effectue le chargement ou le dchargement, et les appels indiquant quun thread est cr ou se termine sont faits dans le contexte de ce thread. On notera que les initialisations relatives aux threads ne sont ralises que pour les threads qui se crent ou se dtruisent aprs que la bibliothque a t charge. DllMain nest donc pas appele pour les threads dj existants dans le processus lors du chargement ou du dchargement. Il est donc vivement dconseill de raliser un traitement spcique aux threads dans cette mthode, car il ne peut tre ralise de manire symtrique au chargement et au dchargement. Sous Unix et Linux, les mcanismes ne sont pas spcis de manire standard. Si lon utilise le compilateur GCC, il est possible de dnir deux fonctions pour linitialisation et la libration des ressources, qui seront appeles dans le contexte du chargement et du dchargement (contrairement Windows, il ny a pas de mcanismes pour prvenir une bibliothque de lapparition ou de la disparition dun thread une fois celle-ci charge). Ces deux fonctions doivent tre identies respectivement laide des attributs spciaux __attribute__((constructor)) et __attribute__((destructor)) :
void __attribute__((constructor)) init_lib(void); void __attribute__((destructor)) release_lib(void);

Quel que soit le systme utilis et pour quelque raison que ce soit, lutilisation des fonction dinitialisation et de libration des bibliothques devra se faire avec la plus grande prcaution. En effet, le chargement dune bibliothque dans un processus est un vnement trs particulier dans la vie du processus. Du fait que ces oprations modient lespace dadressage du processus, elles se font dans un contexte extrmement sensible. Gnralement, lappel de ces fonctions se fait souvent

261

Chapitre 12. Conventions de codage et techniques de base au sein dune section critique. Tous les threads de lapplication peuvent galement tre suspendus, sauf bien entendu le thread qui effectue le chargement ou le dchargement. Les ressources des autres bibliothques peuvent ne pas tre accessibles, ainsi mme que la plupart des ressources de la bibliothque elle-mme. Par consquent, ces fonctions ne peuvent raliser que des oprations extrmement simples. Toute opration complexe (allocation dynamique de mmoire, lancement dexception, cration de thread, synchronisation, etc.) sont absolument interdites. Les objets globaux constituent un cas particulier important. Ces objets sont toujours initialiss, normalement dans lordre de leurs dnitions, par le thread qui effectue le chargement du module qui les contient, et avant lexcution du point dentre. Par exemple, les constructeurs des objets statiques des programmes sont appels dans le contexte du thread principal de lapplication, avant lexcution de la fonction main. Dans le cas des bibliothques de liaison dynamique, ils sont appels avant lappel des fonctions dinitialisation de ces bibliothques. Il va de soi que ces constructeurs ne peuvent pas raliser doprations sensibles, et doivent se conformer aux mmes rgles que celles imposes aux fonctions dinitialisation et de libration des ressources des bibliothques.

12.3.5. Les communications rseau


Les communications rseau requirent une attention toute particulire. Cela est du au fait que les informations peuvent tre non ables, que les temps daccs aux informations sont plus lents, et que les liaisons peuvent tre coupes. De plus, les protocoles rseau peuvent rvler des piges quil est prfrable de connatre ds la conception du logiciel.

12.3.5.1. Fiabilit des informations


En gnral, les donnes qui proviennent dune communication ne peuvent pas tre considres comme ables. En effet, les rseaux ne sont pas conus pour transmettre des informations sans dgradation des donnes. Il est donc ncessaire de mettre en place des mcanismes daccus rception, de contrle dintgrit des donnes, et de rmission en cas de dtection dune erreur ou de la perte dune information. Avec ces mcanismes, les interlocuteurs peuvent sassurer quils obtiennent bien les donnes et que ce sont les bonnes. Cependant, ces mcanismes ne sont implments que par certains protocoles (notamment le protocole de transmission TCP/IP utilis sur Internet), mais pas par tous (par exemple, le protocole dchange de datagrammes UDP/IP et IP lui-mme ne garantissent ni la dlivraison, ni lintgrit des informations transmises). Mais mme lorsquun protocole suppos able comme TCP/IP est utilis, les donnes ne peuvent pas tre considres comme sres. En effet, les mcanismes de vrication dintgrit de la plupart des protocoles se basent sur des sommes de contrle sur les donnes transmises. Or des paquets diffrents peuvent parfaitement produire une somme de contrle identique, bien que cela soit trs peu probable si la diffrence est due une simple erreur de communication. En particulier, les mcanismes de type CRC, souvent utiliss, nont pas t conus pour garantir quune donne est intgre, mais pour permettre de la restaurer lorsquune erreur simple sest produite ! Pour garantir lintgrit des donnes transmises, il faudrait utiliser des algorithmes de calcul de condenss cryptographiques. De plus, un paquet parfaitement valide, transmis sans perte de donnes, peut ne pas tre un paquet correct pour lapplication. En effet, il est parfaitement possible de forger des paquets tout fait valides, mais construits spcialement pour faire planter une application ou en prendre le contrle. Il sagit bien entendu l de piratage, mais toute application communiquant via un rseau non protg avec un protocole non chiffr se doit de prendre en considration ce cas de gure.

262

Chapitre 12. Conventions de codage et techniques de base Il est donc particulirement important, lors de la spcication des protocoles rseau applicatifs, ainsi que lors de la dnition des messages changs, de sassurer que lapplication peut contrler la validit des informations. Cela signie en particulier quelle ne doit en aucun cas prendre pour argent comptant les donnes qui proviennent de lextrieur (rgle de validation des entres), et que les messages changs doivent contenir toutes les informations ncessaires leur interprtation sans avoir recours des hypothses externes. Ainsi, tout compteur, tout indice et toute rfrence doivent pouvoir tre vris, et ltre. Par exemple, la dtermination de la longueur des donnes ne doit pas se faire sur la seule information de la quantit dinformations reues du rseau. Inversement, si un champ longueur est transmis dans un message, il faut sassurer que la taille des donnes du message est sufsante pour en interprter la suite.

12.3.5.2. Performances des communications


Un autre aspect important des communications rseau est quelles sont beaucoup plus lentes que les autres traitements que les programmes peuvent raliser. De ce fait, ce facteur doit tre pris en compte lors de la conception des logiciels et des protocoles rseau. Dans un premier temps, les logiciels ont intrt ne pas se bloquer en attendant que les oprations rseau se terminent. Le minimum est de fournir lutilisateur un signe de vie et, lorsquune opration longue est en cours, de le lui indiquer. Il est rarement possible de dterminer la dure dune communication rseau, car les conditions de dbit peuvent changer dans le temps suivant la charge des rseaux traverss par le ux dinformation, mais il est souvent possible dindiquer la quantit de travail restant faire (par exemple, la taille des donnes restant recevoir). Enn, idalement, donner lutilisateur la possibilit de faire dautres oprations pendant que les communications se font est un plus incontestable. Du point de vue des communications elles-mmes prsent, il faut sassurer que les changes soient rduits au strict minimum. En effet, chaque requte et chaque rponse sont soumises aux rgles de gestion du protocole de communication sous-jacent, et donc aux mcanismes daccuss rception et de r-missions en cas derreur. De plus, certains mcanismes doptimisation des protocoles peuvent amplier les latences lors des changes, au prot de la bande passante. Ces mcanismes ont donc pour effet de bord que le temps de traitement des requtes rseau est considrablement plus important que le temps de transfert des informations elles-mmes. De ce fait, il est gnralement plus rapide denvoyer une requte complexe que de multiples requtes simples. Par consquent, il est recommand de privilgier des mthodes plus complexes au niveau des interfaces logicielles, mais qui sont adaptes aux besoins des clients et qui leur vite ainsi de faire dautres requtes. De mme, il est recommand de grouper les informations lors des communications, quitte en envoyer quelquefois plus que ncessaire. titre dexemple, le protocole TCP/IP cherche, par dfaut, augmenter la bande passante en rduisant la proportion des informations du protocole (en-ttes des paquets TCP et IP) par rapport aux informations utiles (donnes utilises par lapplication). Pour cela, il utilise un tampon pour les donnes en sortie, et nenvoie ces donnes que lorsquune quantit sufsante est disponible. Cela implique quil nenvoie pas les donnes immdiatement, mais temporise leur mission an de pouvoir les envoyer de manire groupe. Bien entendu, sil ny a pas dautres donnes, il les enverra malgr tout, mais aprs expiration dun dlai. De ce fait, si une application cherche mettre des petits paquets en grande quantit, TCP/IP optimisera les communications pour elle. Cela ne fonctionne toutefois pas dans un cas : lorsque lapplication attend, pour envoyer les donnes suivantes, une rponse de la part de son interlocuteur. Dans ce cas, les deux programmes attendront systmatiquement deux fois le dlai dmission du protocole TCP/IP. Il est possible de xer les options sur les canaux de communication qui permettent de dsactiver cette fonctionnalit de TCP/IP (option TCP_NODELAY), mais cela peut ne pas fonctionner. En effet, les protocoles rseau peuvent

263

Chapitre 12. Conventions de codage et techniques de base tre encapsuls (par exemple dans un tunnel HTTP pour passer au travers dun pare-feu), et cette encapsulation nest a priori pas dtectable et encore moins congurable. Il est donc prfrable dans ce cas de revoir le protocole de communication pour mettre les informations de manire groupe et recevoir ensuite les rponses en bloc, quitte ce que certaines de ces rponses soient je navais pas besoin de cette donne .

12.3.5.3. Pertes de connexion


Qui dit rseau dit perte de connexion. Les programmes qui communiquent en rseau doivent prendre en compte limpossibilit de se connecter et la possibilit dune perte de connexion, et ce quasiment tous les niveaux. Cela va jusqu lutilisateur nal, qui doit pouvoir tre prvenu quune erreur ventuellement irrcuprable sest produite (soit par un message, soit par une trace). Prendre en compte les pertes de connexion peut tre difcile, mais les dtecter lest encore plus. Les pertes de connexion se dtectent gnralement par le fait quaprs un certain nombre de tentatives, les informations nont toujours par t reues par linterlocuteur (ou, quinversement, aprs un certain dlai, aucune information na t reue). La dtection des pertes de connexion implique donc une notion de dlai pendant lequel aucune donne ne peut tre change. Cela signie quil nest pratiquement pas possible de garantir la fois la abilit des communications et une grande ractivit dans la dtection des pertes de connexion. En particulier, les protocoles ables comme TCP/IP peuvent utiliser des dures trs longues avant de se rsoudre dclarer forfait et signaler une perte de connexion. Lutilisation de ces protocoles impose donc daccepter cet tat de fait. Par exemple, en raison des mcanismes doptimisation et de r-mission, il nest pas possible de savoir si une information a t effectivement transmise ou non lorsquil y a une perte de communication sur un canal TCP/IP. Du fait de lutilisation dun tampon de sortie, les critures se font toujours dans le tampon, et lorsque la liaison est coupe, on ne peut pas savoir si les donnes sont encore dans le tampon ou si elles ont atteint lautre bout du canal. De plus, mme en cas de coupure de connexion, il reste encore possible denvoyer des donnes dans le tampon de sortie, et ce pendant une dure a priori trs longue, jusqu ce que TCP/IP abandonne les r-missions des informations. Si la perte de connexion est avre, il est tout fait possible que le programme ne sen rende compte que trs tardivement... Il est mme possible quil ne sen rende compte jamais, sil a ferm le canal ! Il est donc particulirement important, lors de la conception dun programme rseau, de choisir les protocoles rseau utiliss en fonction des besoins de abilit, de performances et de ractivit.
Note : Il est classique de mettre en place des mcanismes de chiens de garde pour surveiller les liaisons pendant toute la dure des connexions. Ces chiens de garde fonctionnent simplement en envoyant un message auquel linterlocuteur doit rpondre dans un temps imparti. Si cela nest pas vri, la liaison peut tre dclare coupe. Cette technique est tout fait valable, et permet au moins un contrle applicatif de ltat des liaisons rseau. Toutefois, elle peut galement se rvler difcile mettre en place. En particulier, si les threads qui prennent en charge les messages de chien de garde ont dautre tches effectuer, ou si le canal utilis pour le chien de garde peut tre utilis pour transfrer de grandes quantits dinformation, le mcanisme devient sensible la charge des systmes surveiller. Dans ce cas, les chiens de garde ne contrlent donc plus uniquement la liaison, mais aussi la disponibilit des applications. En cas de charge de travail importante, ce qui se produit notamment lors du dmarrage et lors des connexions initiales, le chien de garde peut se dclencher de manire intempestive et induire un travail supplmentaire indsirable. Si le traitement du chien de garde est une reconnexion, le systme peut ne pas arriver se stabiliser.

264

II. La bibliothque standard C++


Tout comme pour le langage C, pour lequel un certain nombre de fonctions ont t dnies et standardises et constituent la bibliothque C, une bibliothque de classes et de fonctions a t spcie pour le langage C++. Cette bibliothque est le rsultat de lvolution de plusieurs bibliothques, parfois dveloppes indpendamment par plusieurs fournisseurs denvironnements C++, qui ont t fusionnes et normalises an de garantir la portabilit des programmes qui les utilisent. Une des principales briques de cette bibliothque est sans aucun doute la STL (abrviation de Standard Template Library ), tel point quil y a souvent confusion entre les deux. Cette partie a pour but de prsenter les principales fonctionnalits de la bibliothque standard C++. Bien entendu, il est hors de question de dcrire compltement chaque fonction ou chaque dtail du fonctionnement de la bibliothque standard, car cela rendrait illisibles et incomprhensibles les explications. Cependant, les informations de base vous seront donnes an de vous permettre dutiliser efcacement la bibliothque standard C++ et de comprendre les fonctionnalits les plus avances lorsque vous vous y intresserez. La bibliothque standard C++ est rellement un sujet de taille. titre indicatif, sa description est aussi volumineuse que celle du langage lui-mme dans la norme C++. Mais ce nest pas tout, il faut imprativement avoir compris en profondeur les fonctionnalits les plus avances du C++ pour apprhender correctement la bibliothque standard. En particulier, tous les algorithmes et toutes les classes fournies par la bibliothque sont susceptibles de travailler sur des donnes de type arbitraire. La bibliothque utilise donc compltement la notion de template, et se base sur plusieurs abstractions des donnes manipules et de leurs types an de rendre gnrique limplmentation des fonctionnalits. De plus, la bibliothque utilise le mcanisme des exceptions an de signaler les erreurs qui peuvent se produire lors de lexcution des mthodes de ses classes et de ses fonctions. Enn, un certain nombre de notions algorithmiques avances sont utilises dans toute la bibliothque. La prsentation qui sera faite sera donc progressive, tout en essayant de conserver un ordre logique. Tout comme pour la partie prcdente, il est probable que plusieurs lectures seront ncessaires aux dbutants pour assimiler toutes les subtilits de la bibliothque. Le premier chapitre de cette partie (Chapitre 13) prsente les notions de base qui sont utilises dans toute la libraire : encapsulation des fonctions de la bibliothque C classique, classes de traits pour les types de base, notion ditrateurs, de foncteurs, dallocateurs mmoire et de complexit algorithmique. Le Chapitre 14 prsente les types complmentaires que la bibliothque standard C++ dnit pour faciliter la vie du programmeur. Le plus important de ces types est sans doute la classe de gestion des chanes de caractres basic_string. Le Chapitre 15 prsente les notions de ux dentre / sortie standards, et la notion de tampon pour ces ux. Les mcanismes de localisation (cest--dire les fonctions de paramtrage du programme en fonction des conventions et des prfrences nationales) seront dcrits dans le Chapitre 16. Le Chapitre 17 est sans doute lun des plus importants, puisquil prsente tous les conteneurs fournis par la bibliothque standard. Enn, le Chapitre 18 dcrit les principaux algorithmes de la bibliothque, qui permettent de manipuler les donnes stockes dans les conteneurs. Les informations dcrites ici sont bases sur la norme ISO 14882 du langage C++, et non sur la ralit des environnements C++ actuels. Il est donc fortement probable que bon nombre dexemples fournis ici ne soient pas utilisables tels quels sur les environnements de dveloppement existants sur le march, bien que lon commence voir apparatre des environnements presque totalement respectueux de la norme maintenant. De lgres diffrences dans linterface des classes dcrites peuvent galement apparatre et ncessiter la modication de ces exemples. Cependant, terme, tous les environnements de dveloppement respecteront les interfaces spcies par la norme, et les programmes utilisant la bibliothque standard seront rellement portables au niveau source.

Chapitre 13. Services et notions de base de la bibliothque standard


La bibliothque standard C++ fournit un certain nombre de fonctionnalits de base sur lesquelles toutes les autres fonctionnalits de la bibliothque sappuient. Ces fonctionnalits apparaissent comme des classes dencapsulation de la bibliothque C et des classes dabstraction des principales constructions du langage. Ces dernires utilisent des notions trs volues pour permettre une encapsulation rellement gnrique des types de base. Dautre part, la bibliothque standard utilise la notion de complexit algorithmique pour dnir les contraintes de performance des oprations ralisables sur ses structures de donnes ainsi que sur ses algorithmes. Bien que complexes, toutes ces notions sont omniprsentes dans toute la bibliothque, aussi est-il extrmement important de les comprendre en dtail. Ce chapitre a pour but de vous les prsenter et de les claircir.

13.1. Encapsulation de la bibliothque C standard


La bibliothque C dnit un grand nombre de fonctions C standards, que la bibliothque standard C++ reprend son compte et complte par toutes ses fonctionnalits avances. Pour bncier de ces fonctions, il suft simplement dinclure les chiers den-tte de la bibliothque C, tout comme on le faisait avec les programmes C classiques. Toutefois, les fonctions ainsi dclares par ces en-ttes apparaissent dans lespace de nommage global, ce qui risque de provoquer des conits de noms avec des fonctions homonymes (rappelons que les fonctions C ne sont pas surchargeables). Par consquent, et dans un souci dhomognit avec le reste des fonctionnalits de la bibliothque C++, un jeu den-ttes complmentaires a t dni pour les fonctions de la bibliothque C. Ces en-ttes dnissent tous leurs symboles dans lespace de nommage std::, qui est rserv pour la bibliothque standard C++. Ces en-ttes se distinguent des chiers den-tte de la bibliothque C par le fait quils ne portent pas dextension .h et par le fait que leur nom est prx par la lettre c. Les en-ttes utilisables ainsi sont donc les suivants :
cassert cctype cerrno cfloat ciso646 climits clocale cmath csetjmp csignal cstdarg cstddef cstdio cstdlib cstring ctime cwchar cwctype

Par exemple, on peut rcrire notre tout premier programme que lon a fait la Section 1.2 de la manire suivante :
#include <cstdio>

267

Chapitre 13. Services et notions de base de la bibliothque standard


long double x, y; int main(void) { std::printf("Calcul de moyenne\n"); std::printf("Entrez le premier nombre : "); std::scanf("%Lf", &x); std::printf("\nEntrez le deuxime nombre : "); std::scanf("%Lf", &y); std::printf("\nLa valeur moyenne de %Lf et de %Lf est %Lf.\n", x, y, (x+y)/2); return 0; }

Note : Lutilisation systmatique du prxe std:: peut tre nervante sur les grands programmes. On aura donc intrt soit utiliser les chiers den-tte classiques de la bibliothque C, soit inclure une directive using namespace std; pour intgrer les fonctionnalits de la bibliothque standard dans lespace de nommage global. Remarquez que la norme ne suppose pas que ces en-ttes soient des chiers physiques. Les dclarations quils sont supposs faire peuvent donc tre ralises la vole par les outils de dveloppement, et vous ne les trouverez pas forcment sur votre disque dur.

Certaines fonctionnalits fournies par la bibliothque C ont t encapsules dans des fonctionnalits quivalentes de la bibliothque standard C++. Cest notamment le cas pour la gestion des locales et la gestion de certains types de donnes complexes. Cest galement le cas pour la dtermination des limites de reprsentation que les types de base peuvent avoir. Classiquement, ces limites sont dnies par des macros dans les en-ttes de la bibliothque C, mais elles sont galement accessibles au travers de la classe template numeric_limits, dnie dans len-tte limits :
// Types darrondis pour les flottants : enum float_round_style { round_indeterminate = -1, round_toward_zero = 0, round_to_nearest = 1, round_toward_infinity = 2, round_toward_neg_infinity = 3 }; template <class T> class numeric_limits { public: static const bool is_specialized = false; static T min() throw(); static T max() throw(); static const int digits = 0; static const int digits10 = 0; static const bool is_signed = false; static const bool is_integer = false; static const bool is_exact = false; static const int radix = 0; static T epsilon() throw();

268

Chapitre 13. Services et notions de base de la bibliothque standard


static T round_error() throw(); static const int min_exponent = 0; static const int min_exponent10 = 0; static const int max_exponent = 0; static const int max_exponent10 = 0; static const bool has_infinity = false; static const bool has_quiet_NaN = false; static const bool has_signaling_NaN = false; static const bool has_denorm = false; static const bool has_denorm_loss = false; static T infinity() throw(); static T quiet_NaN() throw(); static T signaling_NaN() throw(); static T denorm_min() throw(); static const bool is_iec559 = false; static const bool is_bounded = false; static const bool is_modulo = false; static const bool traps = false; static const bool tinyness_before = false; static const float_round_style round_style = round_toward_zero; };

Cette classe template ne sert rien en soi. En fait, elle est spcialise pour tous les types de base du langage, et ce sont ces spcialisations qui sont rellement utilises. Elles permettent dobtenir toutes les informations pour chaque type grce leurs donnes membres et leurs mthodes statiques. Exemple 13-1. Dtermination des limites dun type
#include <iostream> #include <limits> using namespace std; int main(void) { cout << numeric_limits<int>::min() << endl; cout << numeric_limits<int>::max() << endl; cout << numeric_limits<int>::digits << endl; cout << numeric_limits<int>::digits10 << endl; return 0; }

Ce programme dexemple dtermine le plus petit et le plus grand nombre reprsentable avec le type entier int, ainsi que le nombre de bits utiliss pour coder les chiffres et le nombre maximal de chiffres que les nombres en base 10 peuvent avoir en tant sr de pouvoir tre stocks tels quels.

13.2. Dnition des exceptions standards


La bibliothque standard utilise le mcanisme des exceptions du langage pour signaler les erreurs qui peuvent se produire lexcution au sein de ses fonctions. Elle dnit pour cela un certain nombre de classes dexceptions standards, que toutes les fonctionnalits de la bibliothque sont susceptibles dutiliser. Ces classes peuvent tre utilises telles quelles ou servir de classes de base des classes dexceptions personnalises pour vos propres dveloppements.

269

Chapitre 13. Services et notions de base de la bibliothque standard Ces classes dexception sont presque toutes dclares dans len-tte stdexcept, et drivent de la classe de base exception. Cette dernire nest pas dclare dans le mme en-tte et nest pas utilise directement, mais fournit les mcanismes de base de toutes les exceptions de la bibliothque standard. Elle est dclare comme suit dans len-tte exception :
class exception { public: exception() throw(); exception(const exception &) throw(); exception &operator=(const exception &) throw(); virtual ~exception() throw(); virtual const char *what() const throw(); };

Outre les constructeurs, oprateurs daffectation et destructeurs classiques, cette classe dnit une mthode what qui retourne une chane de caractres statique. Le contenu de cette chane de caractres nest pas normalis. Cependant, il sert gnralement dcrire la nature de lerreur qui sest produite. Cest une mthode virtuelle, car elle est bien entendu destine tre rednie par les classes dexception spcialises pour les diffrents types derreurs. Notez que toutes les mthodes de la classe exception sont dclares comme ne pouvant pas lancer dexceptions elle-mmes, ce qui est naturel puisque lon est dj en train de traiter une exception lorsquon manipule des objets de cette classe. Len-tte exception contient galement la dclaration de la classe dexception bad_exception. Cette classe nest, elle aussi, pas utilise en temps normal. Le seul cas o elle peut tre lance est dans le traitement de la fonction de traitement derreur qui est appele par la fonction std::unexpected lorsquune exception a provoqu la sortie dune fonction qui navait pas le droit de la lancer. La classe bad_exception est dclare comme suit dans len-tte exception :
class bad_exception : public exception { public: bad_exception() throw(); bad_exception(const bad_exception &) throw(); bad_exception &operator=(const bad_exception &) throw(); virtual ~bad_exception() throw(); virtual const char *what() const throw(); };

Notez que lexception bad_alloc lance par les gestionnaires de mmoire lorsque loprateur new ou loprateur new[] na pas russi faire une allocation nest pas dclare dans len-tte stdexcept non plus. Sa dclaration a t place avec celle des oprateurs dallocation mmoire, dans len-tte new. Cette classe drive toutefois de la classe exception, comme le montre sa dclaration :
class bad_alloc : public exception { public: bad_alloc() throw(); bad_alloc(const bad_alloc &) throw(); bad_alloc &operator=(const bad_alloc &) throw(); virtual ~bad_alloc() throw(); virtual const char *what() const throw(); };

270

Chapitre 13. Services et notions de base de la bibliothque standard

Les autres exceptions sont classes en deux grandes catgories. La premire catgorie regroupe toutes les exceptions dont lapparition traduit sans doute une erreur de programmation dans le programme, car elles ne devraient jamais se produire lexcution. Il sagit des exceptions dites derreurs dans la logique du programme et, en tant que telles, drivent de la classe dexception logic_error. Cette classe est dclare comme suit dans len-tte stdexcept :
class logic_error : public exception { public: logic_error(const string &what_arg); };

Elle ne contient quun constructeur, permettant de dnir la chane de caractres qui sera renvoye par la mthode virtuelle what. Ce constructeur prend en paramtre cette chane de caractres sous la forme dun objet de la classe string. Cette classe est dnie par la bibliothque standard an de faciliter la manipulation des chanes de caractres et sera dcrite plus en dtail dans la Section 14.1. Les classes dexception qui drivent de la classe logic_error disposent galement dun constructeur similaire. Ces classes sont les suivantes :

la classe domain_error, qui spcie quune fonction a t appele avec des paramtres sur lesquels elle nest pas dnie. Il faut contrler les valeurs des paramtres utilises lors de lappel de la fonction qui a lanc cette exception ; la classe invalid_argument, qui spcie quun des arguments dune mthode ou dune fonction nest pas valide. Cette erreur arrive lorsquon utilise des valeurs de paramtres qui nentrent pas dans le cadre de fonctionnement normal de la mthode appele ; cela traduit souvent une mauvaise utilisation de la fonctionnalit correspondante ; la classe length_error, qui indique quun dpassement de capacit maximale dun objet a t ralis. Ces dpassements se produisent dans les programmes bogus, qui essaient dutiliser une fonctionnalit au del des limites qui avaient t xes pour elle ; la classe out_of_range, qui spcie quune valeur situe en dehors de la plage de valeurs autorises a t utilise. Ce type derreur signie souvent que les paramtres utiliss pour un appel de fonction ne sont pas corrects ou pas initialiss, et quil faut vrier leur validit.

La deuxime catgorie dexceptions correspond aux erreurs qui ne peuvent pas toujours tre corriges lors de lcriture du programme, et qui font donc partie des vnements naturels qui se produisent lors de son excution. Elles caractrisent les erreurs dexcution, et drivent de la classe dexception runtime_error. Cette classe est dclare de la manire suivante dans len-tte stdexcept :
class runtime_error : public exception { public: runtime_error(const string &what_arg); };

Elle sutilise exactement comme la classe logic_error. Les exceptions de la catgorie des erreurs dexcution sont les suivantes :

la classe range_error, qui signie quune valeur est sortie de la plage de valeurs dans laquelle elle devait se trouver suite un dbordement interne la bibliothque ;

271

Chapitre 13. Services et notions de base de la bibliothque standard

la classe overow_error, qui signie quun dbordement par valeurs suprieures sest produit dans un calcul interne la bibliothque ; la classe underow_error, qui signie quun dbordement par valeurs infrieures sest produit dans un calcul interne la bibliothque.

13.3. Abstraction des types de donnes : les traits


Un certain nombre de classes ou dalgorithmes peuvent manipuler des types ayant une signication particulire. Par exemple, la classe string, que nous verrons plus loin, manipule des objets de type caractre. En ralit, ces classes et ces algorithmes peuvent travailler avec nimporte quels types pourvu que tous ces types se comportent de la mme manire. La bibliothque standard C++ utilise donc la notion de traits , qui permet de dnir les caractristiques de ces types. Les traits sont dnis dans des classes prvues cet usage. Les classes et les algorithmes standards nutilisent que les classes de traits pour manipuler les objets, garantissant ainsi une abstraction totale vis--vis de leurs types. Ainsi, il suft de coder une spcialisation de la classe des traits pour un type particulier an de permettre son utilisation dans les algorithmes gnriques. La bibliothque standard dnit bien entendu des spcialisations pour les types de base du langage. Par exemple, la classe de dnition des traits des types de caractres est la classe template char_traits. Elle contient les dnitions des types suivants :

le type char_type, qui est le type reprsentant les caractres eux-mmes ; le type int_type, qui est un type capable de contenir toutes les valeurs possibles pour les caractres, y compris la valeur spciale du marqueur de n de chier ; le type off_type, qui est le type permettant de reprsenter les dplacements dans une squence de caractres, ainsi que les positions absolues dans cette squence. Ce type est sign car les dplacements peuvent tre raliss aussi bien vers le dbut de la squence que vers la n ; le type pos_type, qui est un sous-type du type off_type, et qui nest utilis que pour les dplacements dans les fonctions de positionnement des ux de la bibliothque standard ; le type state_type, qui permet de reprsenter ltat courant dune squence de caractres dans les fonctions de conversion. Ce type est utilis dans les fonctions de transcodage des squences de caractres dun encodage vers un autre.

Note : Pour comprendre lutilit de ce dernier type, il faut savoir quil existe plusieurs manires de coder les caractres. La plupart des mthodes utilisent un encodage taille xe, o chaque caractre est reprsent par une valeur entire et une seule. Cette technique est trs pratique pour les jeux de caractres contenant moins de 256 caractres, pour lesquels un seul octet est utilis par caractre. Elle est galement utilise pour les jeux de caractres de moins de 65536 caractres, car lutilisation de 16 bits par caractres est encore raisonable. En revanche, les caractres des jeux de caractres orientaux sont cods avec des valeurs numriques suprieures 65536 par les encodages standards (Unicode et ISO 10646), et ne peuvent donc pas tre stocks dans les types char ou wchar_t. Pour ces jeux de caractres, on utilise donc souvent des encodages taille variable, o chaque caractre peut tre reprsent par un ou plusieurs octets selon sa nature et ventuellement selon sa position dans la chane de caractres. Pour ces encodages taille variable, il est vident que le positionnement dans les squences de caractres se fait en fonction du contexte de la chane, savoir en fonction de la position du caractre prcdent et parfois en fonction des caractres dj analyss. Les algorithmes de

272

Chapitre 13. Services et notions de base de la bibliothque standard


la bibliothque standard qui manipulent les squences de caractres doivent donc stocker le contexte courant lors de lanalyse de ces squences. Elles le font grce au type state_type de la classe des traits de ces caractres.

Lexemple suivant vous permettra de vrier que le type char_type de la classe de dnition des traits pour le type char est bien entendu le type char lui-mme :
#include <iostream> #include <typeinfo> #include <string> using namespace std; int main(void) { // Rcupre les informations de typage des traits : const type_info &ti_trait = typeid(char_traits<char>::char_type); // Rcupre les informations de typage directement : const type_info &ti_char = typeid(char); // Compare les types : cout << "Le nom du type caractre des traits est : " << ti_trait.name() << endl; cout << "Le nom du type char est : " << ti_char.name() << endl; if (ti_trait == ti_char) cout << "Les deux types sont identiques." << endl; else cout << "Ce nest pas le mme type." << endl; return 0; }

La classe char_traits dnit galement un certain nombre de mthodes travaillant sur les types de caractres et permettant de raliser les oprations de base sur ces caractres. Ces mthodes permettent essentiellement de comparer, de copier, de dplacer et de rechercher des caractres dans des squences de caractres, en tenant compte de toutes les caractristiques de ces caractres. Elle contient galement la dnition de la valeur spciale utilise dans les squences de caractres pour marquer les n de ux ( EOF , abrviation de langlais End Of File ). Par exemple, le programme suivant permet dafcher la valeur utilise pour spcier une n de chier dans une squence de caractres de type wchar_t :
#include <iostream> #include <string> using namespace std; int main(void) { char_traits<wchar_t>::int_type wchar_eof = char_traits<wchar_t>::eof(); cout << "La valeur de fin de fichier pour wchar_t est : " << wchar_eof << endl;

273

Chapitre 13. Services et notions de base de la bibliothque standard


return 0; }

Les autres mthodes de la classe de dnition des traits des caractres, ainsi que les classes de dnition des traits des autre types, ne seront pas dcrites plus en dtail ici. Elles sont essentiellement utilises au sein des algorithmes de la bibliothque standard et nont donc quun intrt limit pour les programmeurs, mais il est important de savoir quelles existent.

13.4. Abstraction des pointeurs : les itrateurs


La bibliothque standard dnit un certain nombre de structures de donnes volues, qui permettent de stocker et de manipuler les objets utilisateur de manire optimale, vitant ainsi au programmeur davoir rinventer la roue. On appelle ces structures de donnes des conteneurs. Ces conteneurs peuvent tre manipuls au travers de fonctions spciales, selon un grand nombre dalgorithmes possibles dont la bibliothque dispose en standard. Lensemble des fonctionnalits fournies par la bibliothque permet de subvenir au besoin des programmeurs dans la majorit des cas. Nous dtaillerons la notion de conteneur et les algorithmes disponibles plus loin dans ce document. La manire daccder aux donnes des conteneurs dpend bien entendu de leur nature et de leur structure. Cela signie quen thorie, il est ncessaire de spcialiser les fonctions permettant dappliquer les algorithmes pour chaque type de conteneur existant. Cette technique nest ni pratique, ni extensible, puisque les algorithmes fournis par la bibliothque ne pourraient dans ce cas pas travailler sur des conteneurs crits par le programmeur. Cest pour cette raison que la bibliothque standard utilise une autre technique pour accder aux donnes des conteneurs. Cette technique est base sur la notion ditrateur.

13.4.1. Notions de base et dnition


Un itrateur nest rien dautre quun objet permettant daccder tous les objets dun conteneur donn, souvent squentiellement, selon une interface standardise. La dnomination ditrateur provient donc du fait que les itrateurs permettent ditrer sur les objets dun conteneur, cest--dire den parcourir le contenu en passant par tous ses objets. Comme les itrateurs sont des objets permettant daccder dautres objets, ils ne reprsentent pas eux-mmes ces objets, mais plutt le moyen de les atteindre. Ils sont donc comparables aux pointeurs, dont ils ont exactement la mme smantique. En fait, les concepteurs de la bibliothque standard se sont bass sur cette proprit pour dnir linterface des itrateurs, qui sont donc une extension de la notion de pointeur. Par exemple, il est possible dcrire des expressions telles que *i ou ++i avec un itrateur i. Tous les algorithmes de la bibliothque, qui travaillent normalement sur des itrateurs, sont donc susceptibles de fonctionner avec des pointeurs classiques. Bien entendu, pour la plupart des conteneurs, les itrateurs ne sont pas de simples pointeurs, mais des objets qui se comportent comme des pointeurs et qui sont spciques chaque conteneur. Ainsi, les algorithmes sont crits de manire uniforme, et ce sont les conteneurs qui fournissent les itrateurs qui leur sont appropris an de permettre laccs leurs donnes. Il ny a que trois manires dobtenir un itrateur. Les itrateurs qui sont effectivement des pointeurs peuvent tre obtenus naturellement en prenant ladresse de llment auquel ils donnent accs. Les pointeurs ne doivent tre utiliss en tant quitrateurs que pour accder aux donnes dun tableau, car la smantique de larithmtique des pointeurs suppose que les lments rfrencs successivement par un pointeur sont stocks en des emplacements contigus de la mmoire. Pour les itrateurs de conte-

274

Chapitre 13. Services et notions de base de la bibliothque standard neurs en revanche, il faut imprativement utiliser des mthodes spciques du conteneur pour obtenir des itrateurs. La plupart des conteneurs fournissent une mthode pour obtenir un itrateur initial, qui rfrence le premier lment du conteneur, et une mthode pour obtenir la valeur de litrateur lorsque le parcours du conteneur est achev. Enn, certains algorithmes et certaines mthodes des conteneurs peuvent retourner un itrateur lissu de leur traitement. Quelle que soit la manire dobtenir les itrateurs, leur validit est soumise des limites. Premirement, ils deviennent obligatoirement invalides ds lors que le conteneur auquel ils permettent daccder est dtruit. De plus, les conteneurs grent leur structure de donnes de manire dynamique, et sont susceptibles de la rorganiser ds quon les manipule. On veillera donc ne plus utiliser les itrateurs dun conteneur ds quune mthode permettant de le modier aura t appele. Ne pas respecter cette rgle conduirait, dans le meilleur des cas, ne pas parcourir compltement lensemble des objets du conteneur, et dans le pire des cas, planter immdiatement le programme.

13.4.2. Classication des itrateurs


La bibliothque dnit plusieurs catgories ditrateurs qui contiennent des itrateurs plus ou moins puissants. Le comportement des itrateurs les plus puissants se rapproche beaucoup des pointeurs classiques, et quasiment toutes les oprations applicables aux pointeurs peuvent ltre ces itrateurs. En revanche, les itrateurs des classes plus restrictives ne dnissent quun sous-ensemble des oprations que les pointeurs supportent, et ne peuvent donc tre utiliss que dans le cadre de ce jeu doprations rduit. Les algorithmes de la bibliothque nutilisent que les itrateurs des classes les plus faibles permettant de raliser leur travail. Ils simposent ces restrictions an de garantir leur utilisation correcte mme avec les itrateurs les plus simples. Bien entendu, comme les pointeurs disposent de toutes les fonctionnalits dnies par les itrateurs, mme les plus puissants, les algorithmes standards fonctionnent galement avec des pointeurs. Autrement dit, la bibliothque standard est crite de faon nutiliser quune partie des oprations applicables aux pointeurs, an de garantir que ce qui fonctionne avec des itrateurs fonctionne avec des pointeurs. Les itrateurs de chaque catgorie possdent toutes les proprits des itrateurs des catgories infrieures. Il existe donc une hirarchie dans la classication des itrateurs. Les catgories dnies par la bibliothque standard sont les suivantes :

les itrateurs de la catgorie Output sont utiliss pour effectuer des affectations de valeurs aux donnes quils rfrencent. Ces itrateurs ne peuvent donc tre drfrencs par loprateur * que dans le cadre dune affectation. Il est impossible de lire la valeur dun itrateur de type Output, et on ne doit crire dans la valeur quils rfrencent quune fois au plus. Les algorithmes qui utilisent ces itrateurs doivent donc imprativement ne faire quune seule passe sur les donnes itres ; les itrateurs de la catgorie Input sont similaires aux itrateurs de type Output, ceci prs quils ne peuvent tre drfrencs que pour lire une valeur. Contrairement aux itrateurs de type Output, il est possible de comparer deux itrateurs. Cependant, le fait que deux itrateurs soient gaux ne signie aucunement que leurs successeurs le seront encore. Les algorithmes qui utilisent les itrateurs de type Input ne peuvent donc faire aucune hypothse sur lordre de parcours utilis par litrateur. Ce sont donc ncessairement des algorithmes en une passe ; les itrateurs de la catgorie Forward possdent toutes les fonctionnalits des itrateurs de type Input et de type Output. Comme ceux-ci, ils ne peuvent passer que dune valeur la suivante, et jamais reculer ou revenir une valeur dj itre. Les algorithmes qui utilisent des itrateurs de cette catgorie simposent donc de ne parcourir les donnes des conteneurs que dans un seul sens. Cependant, la restriction impose sur lgalit des oprateurs de type Input est leve, ce qui signie que plusieurs parcours successifs se feront dans le mme ordre. Les algorithmes peuvent

275

Chapitre 13. Services et notions de base de la bibliothque standard effectuer plusieurs parcours, par exemple en copiant la valeur initiale de litrateur et en parcourant le conteneur plusieurs fois avec chaque copie ;

les itrateurs de la catgorie Bidirectionnal disposent de toutes les fonctionnalits des itrateurs de type Forward, mais lvent la restriction sur le sens de parcours. Ces itrateurs peuvent donc revenir sur les donnes dj itres, et les algorithmes qui les utilisent peuvent donc travailler en plusieurs passes, dans les deux directions ; enn, les itrateurs de la catgorie RandomAccess sont les plus puissants. Ils fournissent toutes les fonctionnalits des itrateurs de type Bidirectionnal, plus la possibilit daccder aux lments des conteneurs par lintermdiaire dun index en un temps constant. Il ny a donc plus de notion de sens de parcours, et les donnes peuvent tre accdes comme les donnes dun tableau. Il est galement possible deffectuer les oprations classiques de larithmtique des pointeurs sur ces itrateurs.

Tous les itrateurs de la bibliothque standard drivent de la classe de base suivante :


template <class Category, class T, class Distance = ptrdiff_t, class Pointer = T*, class Reference = T &> struct iterator { typedef T value_type; typedef Distance difference_type; typedef Pointer pointer; typedef Reference reference; typedef Category iterator_category; };

Cette classe est dclare dans len-tte iterator. Cette classe dnit les types de base des itrateurs, savoir : le type des valeurs rfrences, le type de la diffrence entre deux itrateurs dans les calculs darithmtique des pointeurs, le type des pointeurs des valeurs rfrences par litrateur, le type des rfrences pour ces valeurs et la catgorie de litrateur. Ce dernier type doit tre lune des classes suivantes, galement dnies par la bibliothque standard :

input_iterator_tag, pour les itrateurs de la catgorie des itrateurs de type Input ; output_iterator_tag, pour les itrateurs de la catgorie des itrateurs de type Output ; forward_iterator_tag, pour les itrateurs de la catgorie des itrateurs de type Forward ; bidirectionnal_iterator_tag, pour les itrateurs de la catgorie des itrateurs bidirectionnels ; random_access_iterator_tag, pour les itrateurs de la catgorie des itrateurs accs complet.

Notez que le type par dfaut pour la diffrence entre deux pointeurs est le type ptrdiff_t, qui est utilis classiquement pour les pointeurs normaux. De mme, le type pointeur et le type rfrence correspondent respectivement, par dfaut, aux types T * et T &. Pour les itrateurs pour lesquels ces types nont pas de sens, le type utilis est void, ce qui permet de provoquer une erreur de compilation si on cherche les utiliser. Ces types sont utiliss par les itrateurs nativement, cependant, ils ne le sont gnralement pas par les algorithmes. En effet, ceux-ci sont susceptibles dtre appeles avec des pointeurs normaux, et les pointeurs ne dnissent pas tous ces types. Cest pour cette raison quune classe de traits a t dnie

276

Chapitre 13. Services et notions de base de la bibliothque standard pour les itrateurs par la bibliothque standard. Cette classe est dclare comme suit dans len-tte iterator :
template <class Iterator> struct iterator_traits { typedef Iterator::value_type typedef Iterator::difference_type typedef Iterator::pointer typedef Iterator::reference typedef Iterator::iterator_category };

value_type; difference_type; pointer; reference; iterator_category;

La classe des traits permet donc dobtenir de manire indpendante de la nature de litrateur la valeur des types fondamentaux de litrateur. Comme ces types nexistent pas pour les pointeurs classiques, cette classe est spcialise de la manire suivante :
template <class T> struct iterator_traits<T *> { typedef T value_type; typedef ptrdiff_t difference_type; typedef T *pointer; typedef T &reference; typedef random_access_iterator_tag iterator_category; };

Ainsi, le type iterator_traits<itrateur>::difference_type renverra toujours le type permettant de stocker la diffrence entre deux itrateurs, que ceux-ci soient des itrateurs ou des pointeurs normaux. Pour comprendre limportance des traits des itrateurs, prenons lexemple de deux fonctions fournies par la bibliothque standard permettant davancer un itrateur dun certain nombre dtapes, et de calculer la diffrence entre deux itrateurs. Il sagit respectivement des fonctions advance et distance. Ces fonctions devant pouvoir travailler avec nimporte quel itrateur, et nimporte quel type de donne pour exprimer la diffrence entre deux itrateurs, elles utilisent la classe des traits. Elles sont dclares de la manire suivante dans len-tte iterator :
template <class InputIterator, class Distance> void advance(InputIterator &i, Distance n); template <class InputIterator> iterator_traits<InputIterator>::difference_type distance(InputIterator first, InputIterator last);

Notez que le type de retour de la fonction distance est Iterator::difference_type pour les itrateurs normaux, et ptrdiff_t pour les pointeurs.
Note : Ces deux mthodes ne sont pas trs efcaces avec les itrateurs de type Forward, car elles doivent parcourir les valeurs de ces itrateurs une une. Cependant, elles sont spcialises pour les itrateurs de type plus volus (en particulier les itrateurs accs complet), et sont donc plus efcaces pour eux. Elles permettent donc de manipuler les itrateurs de manire uniforme, sans pour autant compromettre les performances.

277

Chapitre 13. Services et notions de base de la bibliothque standard

13.4.3. Itrateurs adaptateurs


Les itrateurs sont une notion extrmement utilise dans toute la bibliothque standard, car ils regroupent toutes les fonctionnalits permettant deffectuer un traitement squentiel des donnes. Cependant, il nexiste pas toujours ditrateur pour les sources de donnes que lon manipule. La bibliothque standard fournit donc ce que lon appelle des itrateurs adaptateurs, qui permettent de manipuler ces structures de donnes en utilisant la notion ditrateur mme si ces structures ne grent pas elles-mmes la notion ditrateur.

13.4.3.1. Adaptateurs pour les ux dentre / sortie standards


Les ux dentre / sortie standards de la bibliothque sont normalement utiliss avec les oprations >> et <<, respectivement pour recevoir et pour envoyer des donnes. Il nexiste pas ditrateur de type Input et de type Output permettant de lire et dcrire sur ces ux. La bibliothque dnit donc des adaptateurs permettant de construire ces itrateurs. Litrateur adaptateur pour les ux dentre est implment par la classe template istream_iterator. Cet adaptateur est dclar comme suit dans len-tte iterator :
template <class T, class charT, class traits = char_traits<charT>, class Distance=ptrdiff_t> class istream_iterator : public iterator<input_iterator_tag, T, Distance, const T *, const T &> { public: typedef charT char_type; typedef traits trait_type; typedef basic_istream<char, traits> istream_type; istream_iterator(); istream_iterator(istream_iterator &flux); istream_iterator(const istream_iterator<T, charT, traits, Distance> &flux); ~istream_iterator(); const T &operator*() const; const T *operator->() const; istream_iterator<T, charT, traits, Distance> &operator++(); istream_iterator<T, charT, traits, Distance> operator++(int); };

Les oprateurs dgalit et dingalit sont galement dnis pour cet itrateur. Comme vous pouvez le constater daprs cette dclaration, il est possible de construire un itrateur sur un ux dentre permettant de lire les donnes de ce ux une une. Sil ny a plus de donnes lire sur ce ux, litrateur prend la valeur de litrateur de n de chier pour le ux. Cette valeur est celle qui est attribue tout nouvel itrateur construit sans ux dentre. Lexemple suivant prsente comment faire la somme de plusieurs nombres lus sur le ux dentre, et de lafcher lorsquil ny a plus de donnes lire. Exemple 13-2. Itrateurs de ux dentre
#include <iostream> #include <iterator> using namespace std;

278

Chapitre 13. Services et notions de base de la bibliothque standard


int main(void) { double somme = 0; istream_iterator<double, char> is(cin); while (is != istream_iterator<double, char>()) { somme = somme + *is; ++is; } cout << "La somme des valeurs lue est : " << somme << endl; return 0; }

Vous pourrez essayer ce programme en tapant plusieurs nombres successivement puis en envoyant un caractre de n de chier avec la combinaison de touches CTRL + Z. Ce caractre provoquera la sortie de la boucle while et afchera le rsultat. Litrateur adaptateur pour les ux de sortie fonctionne de manire encore plus simple, car il ny a pas faire de test sur la n de chier. Il est dclar comme suit dans len-tte iterator :
template <class T, class charT = char, class traits = char_traits<charT> > class ostream_iterator : public iterator<output_iterator_tag, void, void, void, void> { public: typedef charT char_type; typedef traits trait_type; typedef basic_ostream<charT, traits> ostream_type; ostream_iterator(ostream_type &flux); ostream_iterator(ostream_type &flux, const charT *separateur); ostream_iterator(const ostream_iterator<T, charT, traits> &flux); ~ostream_iterator(); ostream_iterator<T, charT, traits> &operator=(const T &valeur); ostream_iterator<T, charT, traits> &operator*(); ostream_iterator<T, charT, traits> &operator++(); ostream_iterator<T, charT, traits> &operator++(int); };

Cet itrateur est de type Output, et ne peut donc tre drfrenc que dans le but de faire une criture dans lobjet ainsi obtenu. Ce drfrencement retourne en fait litrateur lui-mme, si bien que lcriture provoque lappel de loprateur daffectation de litrateur. Cet oprateur envoie simplement les donnes sur le ux de sortie que litrateur prend en charge et renvoie sa propre valeur an de raliser une nouvelle criture. Notez que les oprateurs dincrmentation existent galement, mais ne font strictement rien. Ils ne sont l que pour permettre dutiliser ces itrateurs comme de simples pointeurs. Litrateur ostream_iterator peut envoyer sur le ux de sortie un texte intercalaire entre chaque donne quon y crit. Ce texte peut servir insrer des sparateurs entre les donnes. Cette fonctionnalit peut savrer trs pratique pour lcriture de donnes formates. Le texte insrer automatiquement doit tre pass en tant que deuxime argument du constructeur de litrateur.

279

Chapitre 13. Services et notions de base de la bibliothque standard Exemple 13-3. Itrateur de ux de sortie
#include <iostream> #include <iterator> using namespace std; const char *texte[6] = { "Bonjour", "tout", "le", "monde", "!", NULL }; int main(void) { ostream_iterator<const char *, char> os(cout, " "); int i = 0; while (texte[i] != NULL) { *os = texte[i]; // Le drfrencement est facultatif. ++os; // Cette ligne est facultative. ++i; } cout << endl; return 0; }

Il existe galement des adaptateurs pour les tampons de ux dentre / sortie basic_streambuf. Le premier adaptateur est implment par la classe template istreambuf_iterator. Il permet de lire les donnes provenant dun tampon de ux basic_streambuf aussi simplement quen manipulant un pointeur et en lisant la valeur de lobjet point. Le deuxime adaptateur, ostreambuf_iterator, permet quant lui dcrire dans un tampon en affectant une nouvelle valeur lobjet rfrenc par litrateur. Ces adaptateurs fonctionnent donc exactement de la mme manire que les itrateurs pour les ux dentre / sortie formats. En particulier, la valeur de n de chier que prend litrateur dentre peut tre rcupre laide du constructeur par dfaut de la classe istreambuf_iterator, instancie pour le type de tampon utilis.
Note : Loprateur de dincrmentation sufx des itrateurs istreambuf_iterator a un type de retour particulier qui permet de reprsenter la valeur prcdente de litrateur avant incrmentation. Les objets de ce type sont toujours drfrenables laide de loprateur *. La raison de cette particularit est que le contenu du tampon peut tre modi aprs lappel de loprateur operator ++(int), mais lancienne valeur de cet itrateur doit toujours permettre daccder lobjet quil rfrenait. La valeur retourne par litrateur contient donc une sauvegarde de cet objet et peut se voir appliquer loprateur de drfrencement * par lappelant an den rcuprer la valeur. La notion de tampon de ux sera prsente en dtail dans la Section 15.2.

13.4.3.2. Adaptateurs pour linsertion dlments dans les conteneurs


Les itrateurs fournis par les conteneurs permettent den parcourir le contenu et dobtenir une rfrence sur chacun de leurs lments. Ce comportement est tout fait classique et constitue mme une des bases de la notion ditrateur. Toutefois, linsertion de nouveaux lments dans un conteneur ne peut se faire que par lintermdiaire des mthodes spciques aux conteneurs. La bibliothque standard C++ dnit donc des adaptateurs pour des itrateurs dits dinsertion, qui permettent dinsrer

280

Chapitre 13. Services et notions de base de la bibliothque standard des lments dans des conteneurs par un simple drfrencement et une criture. Grce ces adaptateurs, linsertion des lments dans les conteneurs peut tre ralise de manire uniforme, de la mme manire quon crirait dans un tableau qui se redimensionnerait automatiquement, chaque criture. Il est possible dinsrer les nouveaux lments en plusieurs endroits dans les conteneurs. Ainsi, les lments peuvent tre placs au dbut du conteneur, sa n, ou aprs un lment donn. Bien entendu, ces notions nont de sens que pour les conteneurs qui ne sont pas ordonns, puisque dans le cas contraire, la position de llment insr est dtermine par le conteneur lui-mme. La classe template back_insert_iterator est la classe de ladaptateur dinsertion en n de conteneur. Elle est dclare comme suit dans len-tte iterator :
template <class Container> class back_insert_iterator : public iterator<output_iterator_tag, void, void, void, void> { public: typedef Container container_type; explicit back_insert_iterator(Container &conteneur); back_insert_iterator<Container> & operator=(const typename Container::value_type &valeur); back_insert_iterator<Container> &operator*(); back_insert_iterator<Container> &operator++(); back_insert_iterator<Container> operator++(int); };

Comme vous pouvez le constater, les objets des instances cette classe peuvent tre utiliss comme des itrateurs de type Output. Loprateur de drfrencement * renvoie litrateur lui-mme, si bien que les affectations sur les itrateurs drfrencs sont traites par loprateur operator= de litrateur lui-mme. Cest donc cet oprateur qui ajoute llment affecter la n du conteneur auquel litrateur permet daccder, en utilisant la mthode push_back de ce dernier. De mme, la classe template front_insert_iterator de ladaptateur dinsertion en tte de conteneur est dclare comme suit dans len-tte iterator :
template <class Container> class front_insert_iterator : public iterator<output_iterator_tag, void, void, void, void> { public: typedef Container container_type; explicit front_insert_iterator(Container &conteneur); front_insert_iterator<Container> & operator=(const typename Container::value_type &valeur); front_insert_iterator<Container> &operator*(); front_insert_iterator<Container> &operator++(); front_insert_iterator<Container> operator++(int); };

Son fonctionnement est identique celui de back_insert_iterator, ceci prs quil effectue les insertions des lments au dbut du conteneur, par lintermdiaire de sa mthode push_front. Enn, la classe template de ladaptateur ditrateur dinsertion une position donne est dclare comme suit :
template <class Container> class insert_iterator : public iterator<output_iterator_tag, void, void, void, void>

281

Chapitre 13. Services et notions de base de la bibliothque standard


{ public: typedef Container container_type; insert_iterator(Container &conteneur, typename Container::iterator position); insert_iterator<Container> & operator=(const typename Container::value_type &valeur); insert_iterator<Container> &operator*(); insert_iterator<Container> &operator++(); insert_iterator<Container> operator++(int); };

Le constructeur de cette classe prend en paramtre, en plus du conteneur sur lequel litrateur dinsertion doit travailler, un itrateur spciant la position laquelle les lments doivent tre insrs. Les lments sont insrs juste avant llment rfrenc par litrateur fourni en paramtre. De plus, ils sont insrs squentiellement, les uns aprs les autres, dans leur ordre daffectation via litrateur. La bibliothque standard C++ fournit trois fonctions template qui permettent dobtenir les trois types ditrateur dinsertion pour chaque conteneur. Ces fonctions sont dclares comme suit dans len-tte iterator :
template <class Container> back_insert_iterator<Container> back_inserter(Container &conteneur); template <class Container> front_insert_iterator<Container> front_inserter(Container &conteneur); template <class Container, class Iterator> insert_iterator<Container> inserter(Container &conteneur, Iterator position);

Le programme suivant utilise un itrateur dinsertion pour remplir une liste dlment, avant den afcher le contenu. Exemple 13-4. Itrateur dinsertion
#include <iostream> #include <list> #include <iterator> using namespace std; // Dfinit le type liste dentier : typedef list<int> li_t; int main() { // Cre une liste : li_t lst; // Insre deux lments dans la liste de la manire classique : lst.push_back(1); lst.push_back(10);

282

Chapitre 13. Services et notions de base de la bibliothque standard


// Rcupre un itrateur rfrenant le premier lment : li_t::iterator it = lst.begin(); // Passe au deuxime lment : ++it; // Construit un itrateur dinsertion pour insrer de nouveaux // lments avant le deuxime lment de la liste : insert_iterator<li_t> ins_it = inserter(lst, it); // Insre les lments avec cet itrateur : for (int i = 2; i < 10; ++i) { *ins_it = i; ++ins_it; } // Affiche le contenu de la liste : it = lst.begin(); while (it != lst.end()) { cout << *it << endl; ++it; } return 0; }

La manire dutiliser le conteneur de type list sera dcrite en dtail dans le Chapitre 17.

13.4.3.3. Itrateur inverse pour les itrateurs bidirectionnels


Les itrateurs bidirectionnels et les itrateurs accs alatoire peuvent tre parcourus dans les deux sens. Pour ces itrateurs, il est donc possible de dnir un itrateur associ dont le sens de parcours est invers. Le premier lment de cet itrateur est donc le dernier lment de litrateur associ, et inversement. La bibliothque standard C++ dnit un adaptateur permettant dobtenir un itrateur inverse facilement dans len-tte iterator. Il sagit de la classe template reverse_iterator :
template <class Iterator> class reverse_iterator : public iterator< iterator_traits<Iterator>::iterator_category, iterator_traits<Iterator>::value_type, iterator_traits<Iterator>::difference_type, iterator_traits<Iterator>::pointer, iterator_traits<Iterator>::reference> { public: typedef Iterator iterator_type; reverse_iterator(); explicit reverse_iterator(Iterator iterateur); Iterator base() const; Reference operator*() const; Pointer operator->() const; reverse_iterator &operator++(); reverse_iterator operator++(int); reverse_iterator &operator--(); reverse_iterator operator--(int); reverse_iterator operator+(Distance delta) const;

283

Chapitre 13. Services et notions de base de la bibliothque standard


reverse_iterator &operator+=(Distance delta); reverse_iterator operator-(Distance delta) const; reverse_iterator &operator-=(Distance delta); Reference operator[](Distance delta) const; };

Les oprateurs de comparaison classiques et darithmtique des pointeurs externes operator+ et operator- sont galement dnis dans cet en-tte. Le constructeur de cet adaptateur prend en paramtre litrateur associ dans le sens inverse duquel le parcours doit se faire. Litrateur inverse pointera alors automatiquement sur llment prcdent llment point par litrateur pass en paramtre. Ainsi, si on initialise litrateur inverse avec la valeur de n de litrateur direct, il rfrencera le dernier lment que litrateur direct aurait rfrenc avant dobtenir sa valeur nale dans un parcours des lments du conteneur. La valeur de n de litrateur inverse peut tre obtenue en construisant un itrateur inverse partir de la valeur de dbut de litrateur direct.
Note : Notez que le principe spciant que ladresse suivant celle du dernier lment dun tableau doit toujours tre une adresse valide est galement en vigueur pour les itrateurs. La valeur de n dun itrateur est assimilable cette adresse, pointant sur lemplacement suivant le dernier lment dun tableau, et nest pas plus drfrenable, car elle se trouve en dehors du tableau. Cependant, elle peut tre utilise dans les calculs darithmtique des pointeurs, et cest exactement ce que fait ladaptateur reverse_iterator.

La mthode base permet de rcuprer la valeur de litrateur direct associ litrateur inverse. On prendra garde que litrateur renvoy par cette mthode ne rfrence pas le mme lment que celui rfrenc par litrateur inverse. En effet, llment rfrenc est toujours llment suivant llment rfrenc par litrateur inverse, en raison de la manire dont cet itrateur est initialis. Par exemple, litrateur inverse rfrence le dernier lment du conteneur lorsquil vient dtre intialis avec la valeur de n de litrateur directe, valeur qui reprsente le dernier lment pass. De mme, lorsque litrateur inverse a pour valeur sa valeur de n ditration (ce qui reprsente llment prcdent le premier lment du conteneur en quelque sorte), litrateur direct rfrence le premier lment du conteneur. En fait, les itrateurs inverses sont utiliss en interne par les conteneurs pour fournir des itrateurs permettant de parcourir leurs donnes dans le sens inverse. Le programmeur naura donc gnralement pas besoin de construire des itrateurs inverses lui-mme, il utilisera plutt les itrateurs fournis par les conteneurs. Exemple 13-5. Utilisation dun itrateur inverse
#include <iostream> #include <list> #include <iterator> using namespace std; // Dfinit le type liste dentier : typedef list<int> li_t; int main(void) { // Cre une nouvelle liste : li_t li;

284

Chapitre 13. Services et notions de base de la bibliothque standard


// Remplit la liste : for (int i = 0; i < 10; ++i) li.push_back(i); // Affiche le contenu de la liste lenvers : li_t::reverse_iterator rev_it = li.rbegin(); while (rev_it != li.rend()) { cout << *rev_it << endl; ++rev_it; } return 0; }

13.5. Abstraction des fonctions : les foncteurs


La plupart des algorithmes de la bibliothque standard, ainsi que quelques mthodes des classes quelle fournit, donnent la possibilit lutilisateur dappliquer une fonction aux donnes manipules. Ces fonctions peuvent tre utilises pour diffrentes tches, comme pour comparer deux objets par exemple, ou tout simplement pour en modier la valeur. Cependant, la bibliothque standard nutilise pas ces fonctions directement, mais a plutt recours une abstraction des fonctions : les foncteurs. Un foncteur nest rien dautre quun objet dont la classe dnit loprateur fonctionnel (). Les foncteurs ont la particularit de pouvoir tre utiliss exactement comme des fonctions puisquil est possible de leur appliquer leur oprateur fonctionnel selon une criture similaire un appel de fonction. Cependant, ils sont un peu plus puissants que de simples fonctions, car ils permettent de transporter, en plus du code de loprateur fonctionnel, des paramtres additionnels dans leurs donnes membres. Les foncteurs constituent donc une fonctionnalit extrmement puissante qui peut tre trs pratique en de nombreux endroits. En fait, comme on le verra plus loin, toute fonction peut tre transforme en foncteur. Les algorithmes de la bibliothque standard peuvent donc galement tre utiliss avec des fonctions classiques moyennant cette petite transformation. Les algorithmes de la bibliothque standard qui utilisent des foncteurs sont dclars avec un paramtre template dont la valeur sera celle du foncteur permettant de raliser lopration appliquer sur les donnes en cours de traitement. Au sein de ces algorithmes, les foncteurs sont utiliss comme de simples fonctions, et la bibliothque standard ne fait donc pas dautre hypothse sur leur nature. Cependant, il est ncessaire de ne donner que des foncteurs en paramtres aux algorithmes de la bibliothque standard, pas des fonctions. Cest pour cette raison que la bibliothque standard dnit un certain nombre de foncteurs standards an de faciliter la tche du programmeur.

13.5.1. Foncteurs prdnis


La bibliothque nutilise, dans ses algorithmes, que des foncteurs qui ne prennent quun ou deux paramtres. Les foncteurs qui prennent un paramtre et un seul sont dits unaires , alors que les foncteurs qui prennent deux paramtres sont qualis de binaires . An de faciliter la cration de foncteurs utilisables avec ses algorithmes, la bibliothque standard dnit deux classes de base qui pour les foncteurs unaires et binaires. Ces classes de base sont les suivantes :
template <class Arg, class Result> struct unary_function

285

Chapitre 13. Services et notions de base de la bibliothque standard


{ typedef Arg argument_type; typedef Result result_type; }; template <class Arg1, class Arg2, class Result> struct binary_function { typedef Arg1 first_argument_type; typedef Arg2 second_argument_type; typedef Result result_type; };

Ces classes sont dnies dans len-tte functional. La bibliothque dnit galement un certain nombre de foncteurs standards qui encapsulent les oprateurs du langage dans cet en-tte. Ces foncteurs sont les suivants :
template <class T> struct plus : binary_function<T, T, T> { T operator()(const T &operande1, const T &operande2) const; }; template <class T> struct minus : binary_function<T, T, T> { T operator()(const T &operande1, const T &operande2) const; }; template <class T> struct multiplies : binary_function<T, T, T> { T operator()(const T &operande1, const T &operande2) const; }; template <class T> struct divides : binary_function<T, T, T> { T operator()(const T &operande1, const T &operande2) const; }; template <class T> struct modulus : binary_function<T, T, T> { T operator()(const T &operande1, const T &operande2) const; }; template <class T> struct negate : unary_function<T, T> { T operator()(const T &operande) const; }; template <class T> struct equal_to : binary_function<T, T, bool> { bool operator()(const T &operande1, const T &operande2) const;

286

Chapitre 13. Services et notions de base de la bibliothque standard


}; template <class T> struct not_equal_to : binary_function<T, T, bool> { bool operator()(const T &operande1, const T &operande2) const; }; template <class T> struct greater : binary_function<T, T, bool> { bool operator()(const T &operande1, const T &operande2) const; }; template <class T> struct less : binary_function<T, T, bool> { bool operator()(const T &operande1, const T &operande2) const; }; template <class T> struct greater_equal : binary_function<T, T, bool> { bool operator()(const T &operande1, const T &operande2) const; }; template <class T> struct less_equal : binary_function<T, T, bool> { bool operator()(const T &operande1, const T &operande2) const; };

Ces foncteurs permettent dutiliser les principaux oprateurs du langage comme des fonctions classiques dans les algorithmes de la bibliothque standard. Exemple 13-6. Utilisation des foncteurs prdnis
#include <iostream> #include <functional> using namespace std; // Fonction template prenant en paramtre deux valeurs // et un foncteur : template <class T, class F> T applique(T i, T j, F foncteur) { // Applique loprateur fonctionnel au foncteur // avec comme arguments les deux premiers paramtres : return foncteur(i, j); } int main(void) { // Construit le foncteur de somme : plus<int> foncteur_plus; // Utilise ce foncteur pour faire faire une addition

287

Chapitre 13. Services et notions de base de la bibliothque standard


// la fonction "applique" : cout << applique(2, 3, foncteur_plus) << endl; return 0; }

Dans lexemple prcdent, la fonction template applique prend en troisime paramtre un foncteur et lutilise pour raliser lopration faire avec les deux premiers paramtres. Cette fonction ne peut thoriquement tre utilise quavec des objets disposant dun oprateur fonctionnel (), et pas avec des fonctions normales. La bibliothque standard fournit donc les adaptateurs suivants, qui permettent de convertir respectivement nimporte quelle fonction unaire ou binaire en foncteur :
template <class Arg, class Result> class pointer_to_unary_function : public unary_function<Arg, Result> { public: explicit pointer_to_unary_function(Result (*fonction)(Arg)); Result operator()(Arg argument1) const; }; template <class Arg1, Arg2, Result> class pointer_to_binary_function : public binary_function<Arg1, Arg2, Result> { public: explicit pointer_to_binary_function(Result (*fonction)(Arg1, Arg2)); Result operator()(Arg1 argument1, Arg2 argument2) const; }; template <class Arg, class Result> pointer_to_unary_function<Arg, Result> ptr_fun(Result (*fonction)(Arg)); template <class Arg, class Result> pointer_to_binary_function<Arg1, Arg2, Result> ptr_fun(Result (*fonction)(Arg1, Arg2));

Les deux surcharges de la fonction template ptr_fun permettent de faciliter la construction dun foncteur unaire ou binaire partir du pointeur dune fonction du mme type. Exemple 13-7. Adaptateurs de fonctions
#include <iostream> #include <functional> using namespace std; template <class T, class F> T applique(T i, T j, F foncteur) { return foncteur(i, j); } // Fonction classique effectuant une multiplication : int mul(int i, int j) { return i * j;

288

Chapitre 13. Services et notions de base de la bibliothque standard


} int main(void) { // Utilise un adaptateur pour transformer le pointeur // sur la fonction mul en foncteur : cout << applique(2, 3, ptr_fun(&mul)) << endl; return 0; }

Note : En ralit le langage C++ est capable dappeler une fonction directement partir de son adresse, sans drfrencement. De plus le nom dune fonction reprsente toujours sont adresse, et est donc converti implicitement par le compilateur en pointeur de fonction. Par consquent, il est tout fait possible dutiliser la fonction template applique avec une autre fonction deux paramtres, comme dans lappel suivant :

applique(2, 3, mul);

Cependant, cette criture provoque la conversion implicite de lidenticateur mul en pointeur de fonction prenant deux entiers en paramtres et renvoyant un entier, dune part, et lappel de la fonction mul par lintermdiaire de son pointeur sans drfrencement dans la fonction template applique dautre part. Cette criture est donc accepte par le compilateur par tolrance, mais nest pas rigoureusement exacte.

La bibliothque standard C++ dnit galement des adaptateurs pour les pointeurs de mthodes non statiques de classes. Ces adaptateurs se construisent comme les adaptateurs de fonctions statiques classiques, ceci prs que leur constructeur prend un pointeur de mthode de classe et non un pointeur de fonction normale. Ils sont dclars de la manire suivante dans len-tte functional :
template <class Result, class Class> class mem_fun_t : public unary_function<Class *, Result> { public: explicit mem_fun_t(Result (Class::*methode)()); Result operator()(Class *pObjet); }; template <class Result, class Class, class Arg> class mem_fun1_t : public binary_function<Class *, Arg, Result> { public: explicit mem_fun1_t(Result (Class::*methode)(Arg)); Result operator()(Class *pObjet, Arg argument); }; template <class Result, class Class> class mem_fun_ref_t : public unary_function<Class, Result> { public: explicit mem_fun_ref_t(Result (Class::*methode)());

289

Chapitre 13. Services et notions de base de la bibliothque standard


Result operator()(Class &objet); }; template <class Result, class Class, class Arg> class mem_fun1_ref_t : public binary_function<Class, Arg, Result> { public: explicit mem_fun1_ref_t(Result (Class::*methode)(Arg)); Result operator()(Class &objet, Arg argument); }; template <class Result, class Class> mem_fun_t<Result, Class> mem_fun(Result (Class::*methode)()); template <class Result, class Class, class Arg> mem_fun1_t<Result, Class> mem_fun(Result (Class::*methode)(Arg)); template <class Result, class Class> mem_fun_ref_t<Result, Class> mem_fun_ref(Result (Class::*methode)()); template <class Result, class Class, class Arg> mem_fun1_ref_t<Result, Class> mem_fun_ref(Result (Class::*methode)(Arg));

Comme vous pouvez le constater daprs leurs dclarations les oprateurs fonctionnels de ces adaptateurs prennent en premier paramtre soit un pointeur sur lobjet sur lequel le foncteur doit travailler (adaptateurs mem_fun_t et mem_fun1_t), soit une rfrence sur cet objet (adaptateurs mem_fun_ref_t et mem_fun1_ref_t). Le premier paramtre de ces foncteurs est donc rserv pour lobjet sur lequel la mthode encapsule doit tre appele. En fait, la liste des adaptateurs prsente ci-dessus nest pas exhaustive. En effet, chaque adaptateur prsent est doubl dun autre adaptateur, capable de convertir les fonctions membres const. Il existe donc huit adaptateurs au total permettant de construire des foncteurs partir des fonctions membres de classes. Pour diminuer cette complexit, la bibliothque standard dnit plusieurs surcharges pour les fonctions mem_fun et mem_fun_ref, qui permettent de construire tous ces foncteurs plus facilement, sans avoir se soucier de la nature des pointeurs de fonction membre utiliss. Il est fortement recommand de les utiliser plutt que de chercher construire ces objets manuellement.

13.5.2. Prdicats et foncteurs doprateurs logiques


Les foncteurs qui peuvent tre utiliss dans une expression logique constituent une classe particulire : les prdicats. Un prdicat est un foncteur dont loprateur fonctionnel renvoie un boolen. Les prdicats ont donc un sens logique, et caractrisent une proprit qui ne peut tre que vraie ou fausse. La bibliothque standard fournit des prdicats prdnis qui effectuent les oprations logiques des oprateurs logiques de base du langage. Ces prdicats sont galement dclars dans len-tte functional :
template <class T> struct logical_and : binary_function<T, T, bool> { bool operator()(const T &operande1, const T &operande2) const;

290

Chapitre 13. Services et notions de base de la bibliothque standard


}; template <class T> struct logical_or : binary_function<T, T, bool> { bool operator()(const T &operande1, const T &operande2) const; };

Ces foncteurs fonctionnent exactement comme les foncteurs vus dans la section prcdente. La bibliothque standard dnit aussi deux foncteurs particuliers, qui permettent deffectuer la ngation dun autre prdicat. Ces deux foncteurs travaillent respectivement sur les prdicats unaires et sur les prdicats binaires :
template <class Predicate> class unary_negate : public unary_function<typename Predicate::argument_type, bool> { public: explicit unary_negate(const Predicate &predicat); bool operator()(const argument_type &argument) const; }; template <class Predicate> class binary_negate : public binary_function<typename Predicate::first_argument_type, typename Predicate::second_argument_type, bool> { public: explicit binary_negate(const Predicate &predicat); bool operator()(const first_argument_type &argument1, const second_argument_type &argument2) const; }; template <class Predicate> unary_negate<Predicate> not1(const Predicate &predicat); template <class Predicate> binary_negate<Predicate> not2(const Predicate &predicat);

Les fonctions not1 et not2 servent faciliter la construction dun prdicat inverse pour les prdicats unaires et binaires.

13.5.3. Foncteurs rducteurs


Nous avons vu que la bibliothque standard ne travaillait quavec des foncteurs prenant au plus deux arguments. Certains algorithmes nutilisant que des foncteurs unaires, ils ne sont normalement pas capables de travailler avec les foncteurs binaires. Toutefois, si un des paramtres dun foncteur binaire est x une valeur donne, celui-ci devient unaire, puisque seul le deuxime paramtre peut varier. Il est donc possible dutiliser des foncteurs binaires mme avec des algorithmes qui nutilisent que des foncteurs unaires, la condition de xer lun des paramtres. La bibliothque standard dnit des foncteurs spciaux qui permettent de transformer tout foncteur binaire en foncteur unaire partir de la valeur de lun des paramtres. Ces foncteurs effectuent une opration dite de rduction car ils rduisent le nombre de paramtres du foncteur binaire un. Pour

291

Chapitre 13. Services et notions de base de la bibliothque standard cela, ils dnissent un oprateur fonctionnel un argument, qui applique loprateur fonctionnel du foncteur binaire cet argument et une valeur xe quils mmorisent en donne membre. Ces foncteurs rducteurs sont dclars, comme les autres foncteurs, dans len-tte fonctional :
template <class Operation> class binder1st : public unary_function<typename Operation::second_argument_type, typename Operation::result_type> { protected: Operation op; typename Operation::first_argument_type value; public: binder1st(const Operation &foncteur, const typename Operation::first_argument_type &valeur); result_type operator()(const argument_type &variable) const; }; template <class Operation> class binder2nd : public unary_function<typename Operation::first_argument_type, typename Operation::result_type> { protected: Operation op; typename Operation::second_argument_type value; public: binder2nd(const Operation &foncteur, const typename Operation::second_argument_type &valeur); result_type operator()(const argument_type &variable) const; }; template <class Operation, class T> binder1st<Operation> bind1st(const Operation &foncteur, const T &valeur); template <class Operation, class T> binder2nd<Operation> bind2nd(const Operation &foncteur, const T &valeur);

Il existe deux jeux de rducteurs, qui permettent de rduire les foncteurs binaires en xant respectivement leur premier ou leur deuxime paramtre. Les rducteurs qui gent le premier paramtre peuvent tre construits laide de la fonction template bind1st, et ceux qui gent la valeur du deuxime paramtre peuvent ltre laide de la fonction bind2nd. Exemple 13-8. Rduction de foncteurs binaires
#include <iostream> #include <functional> using namespace std; // Fonction template permettant dappliquer une valeur // un foncteur unaire. Cette fonction ne peut pas // tre utilise avec un foncteur binaire. template <class Foncteur> typename Foncteur::result_type applique(

292

Chapitre 13. Services et notions de base de la bibliothque standard


Foncteur f, typename Foncteur::argument_type valeur) { return f(valeur); } int main(void) { // Construit un foncteur binaire daddition dentiers : plus<int> plus_binaire; int i; for (i = 0; i < 10; ++i) { // Rduit le foncteur plus_binaire en fixant son // premier paramtre 35. Le foncteur unaire obtenu // est ensuite utilis avec la fonction applique : cout << applique(bind1st(plus_binaire, 35), i) << endl; } return 0; }

13.6. Gestion personnalise de la mmoire : les allocateurs


Lune des plus grandes forces de la bibliothque standard est de donner aux programmeurs le contrle total de la gestion de la mmoire pour leurs objets. En effet, les conteneurs peuvent tre amens crer un grand nombre dobjets, dont le comportement peut tre trs diffrent selon leur type. Si, dans la majorit des cas, la gestion de la mmoire effectue par la bibliothque standard convient, il peut parfois tre ncessaire de prendre en charge soi-mme les allocations et les librations de la mmoire pour certains objets. La bibliothque standard utilise pour cela la notion dallocateur. Un allocateur est une classe C++ disposant de mthodes standards que les algorithmes de la bibliothque peuvent appeler lorsquelles dsirent allouer ou librer de la mmoire. Pour cela, les conteneurs de la bibliothque standard C++ prennent tous un paramtre template reprsentant le type des allocateurs mmoire quils devront utiliser. Bien entendu, la bibliothque standard fournit un allocateur par dfaut, et ce paramtre template prend par dfaut la valeur de cet allocateur. Ainsi, les programmes qui ne dsirent pas spcier un allocateur spcique pourront simplement ignorer ce paramtre template. Les autres programmes pourront dnir leur propre allocateur. Cet allocateur devra videmment fournir toutes les fonctionnalits de lallocateur standard, et satisfaire quelques contraintes particulires. Linterface des allocateurs est fournie par la dclaration de lallocateur standard, dans len-tte memory :
template <class T> class allocator { public: typedef size_t typedef ptrdiff_t typedef T typedef const T

size_type; difference_type; *pointer; *const_pointer;

293

Chapitre 13. Services et notions de base de la bibliothque standard


typedef T &reference; typedef const T &const_reference; typedef T value_type; template <class U> struct rebind { typedef allocator<U> other; }; allocator() throw(); allocator(const allocator &) throw(); template <class U> allocator(const allocator<U> &) throw(); ~allocator() throw(); pointer address(reference objet); const_pointer address(const_reference objet) const; pointer allocate(size_type nombre, typename allocator<void>::const_pointer indice); void deallocate(pointer adresse, size_type nombre); size_type max_size() const throw(); void construct(pointer adresse, const T &valeur); void destroy(pointer adresse); }; // Spcialisation pour le type void pour liminer les rfrences : template <> class allocator<void> { public: typedef void *pointer; typedef const void *const_pointer; typedef void value_type; template <class U> struct rebind { typedef allocator<U> other; }; };

Vous noterez que cet allocateur est spcialis pour le type void, car certaines mthodes et certains typedef nont pas de sens pour ce type de donne. Le rle de chacune des mthodes des allocateurs est trs clair et nappelle pas beaucoup de commentaires. Les deux surcharges de la mthode address permettent dobtenir ladresse dun objet allou par cet allocateur partir dune rfrence. Les mthodes allocate et deallocate permettent respectivement de raliser une allocation de mmoire et la libration du bloc correspondant. La mthode allocate prend en paramtre le nombre dobjets qui devront tre stocks dans le bloc allouer et un pointeur fournissant des informations permettant de dterminer lemplacement o lallocation doit se faire de prfrence. Ce dernier paramtre peut ne pas tre pris en compte par limplmentation de la bibliothque standard que vous utilisez et, sil lest, son rle nest pas spci. Dans tous les cas, sil nest pas nul, ce pointeur doit tre un pointeur sur un bloc dj allou par cet allocateur et non encore libr. La plupart des implmentations chercheront allouer un bloc adjacent celui fourni en paramtre, mais ce nest pas toujours le cas. De mme, notez que le nombre dobjets spci la mthode deallocate doit exactement tre le mme que celui utilis pour lallocation dans lappel correspondant allocate. Autrement dit, lallocateur ne mmorise pas lui-mme la taille des blocs mmoire quil a fourni.

294

Chapitre 13. Services et notions de base de la bibliothque standard


Note : Le pointeur pass en paramtre la mthode allocate nest ni libr, ni rallou, ni rutilis par lallocateur. Il ne sagit donc pas dune modication de la taille mmoire du bloc fourni en paramtre, et ce bloc devra toujours tre libr indpendamment de celui qui sera allou. Ce pointeur nest utilis par les implmentations que comme un indice fourni lallocateur an doptimiser les allocations de blocs dans les algorithmes et les conteneurs internes. La mthode allocate peut lancer lexception bad_alloc en cas de manque de mmoire ou si le nombre dobjets spci en paramtre est trop gros. Vous pourrez obtenir le nombre maximal que la mthode allocate est capable daccepter grce la mthode max_size de lallocateur.

Les deux mthodes construct et destroy permettent respectivement de construire un nouvel objet et den dtruire un ladresse indique en paramtre. Elles doivent tre utilises lorsquon dsire appeler le constructeur ou le destructeur dun objet stock dans une zone mmoire alloue par cet allocateur et non par les oprateurs new et delete du langage (rappelons que ces oprateurs effectuent ce travail automatiquement). Pour effectuer la construction dun nouvel objet, construct utilise loprateur new avec placement, et pour le dtruire, destroy appelle directement le destructeur de lobjet.
Note : Les mthodes construct et destroy neffectuent pas lallocation et la libration de la mmoire elles-mmes. Ces oprations doivent tre effectues avec les mthodes allocate et deallocate de lallocateur.

Exemple 13-9. Utilisation de lallocateur standard


#include <iostream> #include <memory> using namespace std; class A { public: A(); A(const A &); ~A(); }; A::A() { cout << "Constructeur de A" << endl; } A::A(const A &) { cout << "Constructeur de copie de A" << endl; } A::~A() { cout << "Destructeur de A" << endl; } int main(void)

295

Chapitre 13. Services et notions de base de la bibliothque standard


{ // Construit une instance de lallocateur standard pour la classe A : allocator<A> A_alloc; // Alloue lespace ncessaire pour stocker cinq instances de A : allocator<A>::pointer p = A_alloc.allocate(5); // Construit ces instances et les initialise : A init; int i; for (i=0; i<5; ++i) A_alloc.construct(p+i, init); // Dtruit ces instances : for (i=0; i<5; ++i) A_alloc.destroy(p+i); // Reconstruit ces 5 instances : for (i=0; i<5; ++i) A_alloc.construct(p+i, init); // Destruction finale : for (i=0; i<5; ++i) A_alloc.destroy(p+i); // Libre la mmoire : A_alloc.deallocate(p, 5); return 0; }

Vous voyez ici lintrt que peut avoir les allocateurs de la bibliothque standard. Les algorithmes peuvent contrler explicitement la construction et la destruction des objets, et surtout les dissocier des oprations dallocation et de libration de la mmoire. Ainsi, un algorithme devant effectuer beaucoup dallocations mmoire pourra, sil le dsire, effectuer ces allocations une bonne fois pour toutes grce lallocateur standard, et neffectuer les oprations de construction et de destruction des objets que lorsque cela est ncessaire. En procdant ainsi, le temps pass dans les routines de gestion de la mmoire est limin et lalgorithme est dautant plus performant. Inversement, un utilisateur expriment pourra dnir son propre allocateur mmoire adapt aux objets quil voudra stocker dans un conteneur. En imposant au conteneur de la bibliothque standard dutiliser cet allocateur personnalis, il obtiendra des performances optimales. La dnition dun allocateur maison consiste simplement implmenter une classe template disposant des mmes mthodes et types que ceux dnis par lallocateur allocator. Toutefois, il faut savoir que la bibliothque impose des contraintes sur la smantique de ces mthodes :

toutes les instances de la classe template de lallocateur permettent daccder la mme mmoire. Ces instances sont donc interchangeables et il est possible de passer de lune lautre laide de la structure template rebind et de son typedef other. Notez que le fait dencapsuler ce typedef dans une structure template permet de simuler la dnition dun type template ; toutes les instances dun allocateur dun type donn permettent galement daccder la mme mmoire. Cela signie quil nest pas ncessaire de disposer dune instance globale pour chaque allocateur, il suft simplement de crer un objet local dune des instances de la classe template de lallocateur pour allouer et librer de la mmoire. Notez ici la diffrence avec la contrainte prcdente : cette contrainte porte ici sur les objets instances des classes template instancies, alors que la contrainte prcdente portait sur les instances elles-mmes de la classe template de lallocateur ;

296

Chapitre 13. Services et notions de base de la bibliothque standard

toutes les mthodes de lallocateur doivent sexcuter dans un temps amorti constant (cela signie que le temps dexcution de ces mthodes est major par une borne suprieure xe, qui ne dpend pas du nombre dallocation dj effectues ni de la taille du bloc de mmoire demand) ; les mthodes allocate et deallocate sont susceptibles dutiliser les oprateurs new et delete du langage. Ce nest pas une obligation, mais cette contrainte signie que les programmes qui rednissent ces deux oprateurs doivent tre capable de satisfaire les demandes de lallocateur standard ; les types pointer, const_pointer, size_type et difference_type doivent tre gaux respectivement aux types T *, const T*, size_t et ptrdiff_t. En fait, cette contrainte nest impose que pour les allocateurs destins tre utiliss par les conteneurs de la bibliothque standard, mais il est plus simple de la gnraliser tous les cas dutilisation.

Pour terminer ce tour dhorizon des allocateurs, sachez que la bibliothque standard dnit galement un type itrateur spcial permettant de stocker des objets dans une zone de mmoire non initialise. Cet itrateur, nomm raw_storage_iterator, est de type Output et nest utilis quen interne par la bibliothque standard. De mme, la bibliothque dnit des algorithmes permettant deffectuer des copies brutes de blocs mmoire et dautres manipulations sur les blocs allous par les allocateurs. Ces algorithmes sont galement utiliss en interne, et ne seront donc pas dcrits plus en dtail ici.

13.7. Notion de complexit algorithmique


En aucun endroit la norme C++ ne spcie la manire de raliser une fonctionnalit. En effet, elle nimpose ni les structures de donnes, ni les algorithmes utiliser. Les seules choses qui sont spcies par la norme sont les interfaces bien entendu (cest--dire les noms des classes, des objets et les signatures des fonctions et des mthodes) et la smantique des diverses oprations ralisables. Cependant, la norme C++ ne permet pas de raliser toutes ces fonctionnalits nimporte comment, car elle impose galement des contraintes de performances sur la plupart de ses algorithmes ainsi que sur les mthodes des conteneurs. Ces contraintes sont exprimes gnralement en terme de complexit algorithmique, aussi est-il ncessaire de prciser un peu cette notion.
Note : En pratique, les contraintes de complexit imposes par la bibliothque standard sont tout simplement les plus fortes ralisables. En dautres termes, on ne peut pas faire mieux que les algorithmes de la bibliothque standard.

13.7.1. Gnralits
La nature des choses veut que plus un programme a de donnes traiter, plus il prend du temps pour le faire. Cependant, certains algorithmes se comportent mieux que dautres lorsque le nombre des donnes traiter augmente. Par exemple, un algorithme mal crit peut voir son temps dexcution crotre exponentiellement avec la quantit de donnes traiter, alors quun algorithme bien tudi aurait naurait t plus lent que proportionnellement ce mme nombre. En pratique, cela signie que cet algorithme est tout simplement inutilisable lorsque le nombre de donnes augmente. Par exemple, le fait de doubler la taille de lensemble des donnes traiter peut engendrer un temps de calcul quatre fois plus long, alors que le temps dexcution de lalgorithme bien crit naurait t que du double seulement. Et si le nombre de donnes est tripl et non doubl, cet algorithme demandera huit fois plus de temps, l o le triple seulement est ncessaire. Si lon prend quatre fois plus de donnes, le temps

297

Chapitre 13. Services et notions de base de la bibliothque standard sera multipli par soixante-quatre. On voit clairement que les choses ne vont pas en samliorant quand le nombre de donnes traiter augmente... En ralit, il est relativement rare de considrer le temps dexcution pour qualier les performances dun algorithme. En effet, le calcul du temps dexcution nest pas toujours possible dune part, parce quil se base sur des paramtres a priori inconnus, et nest pas toujours ce qui est intressant au niveau du cot dautre part. Pour illustrer ce dernier point, supposons que chaque opration effectue par lalgorithme cote une certaine quantit dnergie. Dans certains contextes, il est plus important de sintresser lnergie dpense quau temps pass pour effectuer lensemble des traitements. Or certaines oprations peuvent prendre relativement peu de temps, mais coter trs cher nergtiquement parlant, et loptimisation du temps dexcution ne donne pas forcment la meilleure solution. Un autre exemple est tout simplement celui de la lecture de secteurs sur un disque dur. La lecture de ces secteurs en soi ne prend pas tellement de temps, en revanche le dplacement de la tte de lecture se fait en un temps daccs considrablement plus grand. Les algorithmes de gestion des entres / sorties sur disque des systmes dexploitation cherchent donc naturellement diminuer au maximum ces dplacements en rorganisant en consquence les requtes de lecture et dcriture. Il est donc gnralement beaucoup plus simple de compter le nombre doprations que les algorithmes effectuent lors de leur droulement, car cette donne est bien moins spcique au contexte dutilisation de lalgorithme. Bien entendu, toutes les oprations effectues par un algorithme nont pas le mme cot dans un contexte donn, et de plus ce cot varie dun contexte dutilisation un autre. La complexit dun algorithme doit donc toujours sexprimer en nombre doprations lmentaires dun certain type, tant entendu que les oprations de ce type sont celles qui cotent le plus cher selon les critres choisis... Remarquez que les oprations qui sont ralises par un algorithme peuvent tre elles-mmes relativement complexes. Par exemple, un algorithme qui applique une fonction sur chaque donne traiter peut utiliser une fonction inimaginablement complexe. Cependant, cela ne nous intresse pas dans la dtermination de la complexit de cet algorithme. Bien entendu, ce quil faut compter, cest le nombre de fois que cette fonction est appele, et la complexit de lalgorithme doit se calculer indpendamment de celle de cette fonction. Lopration lmentaire de lalgorithme est donc ici tout simplement lappel de cette fonction, aussi complexe soit-elle.

13.7.2. Notions mathmatiques de base et dnition


Le nombre des oprations lmentaires effectues par un algorithme est une fonction directe du nombre de donnes traiter. La complexit dun algorithme est donc directement relie cette fonction : plus elle crot rapidement avec le nombre de donnes traiter, plus la complexit de lalgorithme est grande. En ralit, la fonction exacte donnant le nombre doprations lmentaires effectues par un algorithme nest pas toujours facile calculer. Cependant, il existe toujours une fonction plus simple qui dispose du mme comportement que la fonction du nombre doprations de lalgorithme quand le nombre de donnes traiter augmente. Cette forme simplie nest en fait rien dautre que la partie croissant le plus vite avec le nombre de donnes, car lorsque celui-ci tend vers linni, cest elle qui devient prdominante. Cela signie que si lon trace le graphe de la fonction, sa forme nit par ressembler celle de sa forme simplie lorsque le nombre de donnes traiter devient grand. La formulation complte de la fonction du nombre doprations ralises par un algorithme nimporte donc pas tant que cela, ce qui est intressant, cest sa forme simplie. En effet, non seulement elle est plus simple ( exprimer, manipuler et bien videmment retenir), mais en plus elle caractrise correctement le comportement de lalgorithme sur les grands nombres. La complexit dun algorithme est donc, par dnition, le terme prpondrant dans la fonction donnant le nombre doprations lmentaires effectues par lalgorithme en fonction du nombre des donnes traiter.

298

Chapitre 13. Services et notions de base de la bibliothque standard Mathmatiquement parlant, le fait que la forme simplie dune fonction se comporte comme la fonction elle-mme linni se traduit simplement en disant que les termes dordre infrieurs sont crass par le terme de premier ordre. Par consquent, si lon divise une fonction par lautre, les termes dordre infrieur deviennent ngligeables et la valeur du rapport tend se stabiliser vers les grand nombres. Autrement dit, il est possible de trouver deux constantes A et B positives et non nulles telles que, partir dune certaine valeur de n, la triple inquation 0 Ac(n) f(n) Bc(n), dans laquelle c(n) est la forme simplie de la fonction f(n), est toujours vrie. La fonction f(n) est donc, en quelque sortes, encadre par deux gendarmes qui suivent le mme trajet : celui de la fonction c(n).
Note : Notez que cette formulation nutilise pas le rapport des fonctions f(n) et c(n) directement. Elle est donc toujours valide, mme lorsque ces deux fonctions sont nulles, ce qui aurait pos des problmes si lon avait utilis un rapport.

En fait, la limite infrieure Ac(n) ne nous intresse pas spcialement. En effet, seul le cot maximal dun algorithme est intressant, car sil cote moins cher que prvu, personne ne sen plaindra... Il est donc courant dutiliser une formulation plus simple et plus connue des mathmaticiens, dans laquelle seule la dernire inquation est utilise. On dit alors que la fonction f(n) est en grand O de c(n) (ce qui se note O(c(n)) ). Cela signie quil existe une constante A telle que, pour toutes les valeurs de n suprieures une valeur sufsamment grande, la double inquation 0 f(n) Ac(n) est toujours vrie.
Note : La notion de grand O permet donc de donner une borne suprieure de la complexit de la fonction. En fait, si f(n) est en O(c(n)), elle lest pour toutes les fonctions plus grandes que c(n). Toutefois, en gnral, on cherche dterminer la plus petite fonction c(n) qui est un grand O de f(n). Il est vident que si une fonction f(n) dispose dune forme simplie c(n), elle est en O(c(n)). En effet, linquation suprieure est toujours vrie, on ne fait ici quignorer la deuxime inquation de la dnition de la forme simplie.

13.7.3. Interprtation pratique de la complexit


Toutes ces notions peuvent vous paratre assez abstraites, mais il est important de bien comprendre ce quelles signient. Il est donc peut-tre ncessaire de donner quelques exemples de complexit parmi celles que lon rencontre le plus couramment. Tout dabord, une complexit de 1 pour un algorithme signie tout simplement que son cot dexcution est constant, quel que soit le nombre de donnes traiter. Notez bien ici que lon parle de cot dexcution et non de dure. Le cot est ici le nombre doprations lmentaires effectues par cet algorithme. Les algorithmes de complexit 1 sont videmment les plus intressants, mais ils sont hlas assez rares ou tout simplement triviaux. Gnralement, les algorithmes ont une complexit de n, leur cot dexcution est donc proportionnel au nombre de donnes traiter. Cest encore une limite acceptable, et gnralement accepte comme une consquence logique de laugmentation du nombre de donnes traiter. Certains algorithmes sont en revanche nettement moins performants et ont une complexit en n2 , soit le carr du nombre des lments traiter. Cette fois, cela signie que leur cot dexcution a tendance crotre trs rapidement lorsquil y a de plus en plus de donnes. Par exemple, si lon double le nombre de donnes, le cot dexcution de lalgorithme ne double pas, mais quadruple. Et si lon triple le nombre de

299

Chapitre 13. Services et notions de base de la bibliothque standard donnes, ce cot devient neuf fois plus grand. Ne croyez pas pour autant que les algorithmes de ce type soient rares ou mauvais. On ne peut pas toujours, hlas, faire autrement... Il existe mme des algorithmes encore plus coteux, qui utilisent des exposants bien suprieurs 2. Inversement, certains algorithmes extrmement astucieux permettent de rduire les complexits n ou n2 en ln(n) ou nln(n), ils sont donc nettement plus efcaces.
Note : La fonction ln(n) est la fonction logarithmique, qui est la fonction inverse de lexponentielle, bien connue pour sa croissance dmesure. La fonction logarithme volue beaucoup moins vite que son argument, en loccurrence n dans notre cas, et a donc tendance craser le cot des algorithmes qui lont pour complexit.

Enn, pour terminer ces quelques notions de complexit algorithmique, sachez que lon peut valuer la difcult dun problme partir de la complexit du meilleur algorithme qui permet de le rsoudre. Par exemple, il a t dmontr que le tri dun ensemble de n lments ne peut pas se faire en mieux que nln(n) oprations (et on sait le faire, ce qui est sans doute le plus intressant de laffaire). Malheureusement, il nest pas toujours facile de dterminer la complexit dun problme. Il existe mme toute une classe de problmes extrmement difciles rsoudre pour lesquels on ne sait mme pas si leur solution optimale est polynomiale ou non. En fait, on ne sait les rsoudre quavec des algorithmes de complexit exponentielle (si vous ne savez pas ce que cela signie, en un mot, cela veut dire que cest une vritable catastrophe). Cependant, cela ne veut pas forcment dire quon ne peut pas faire mieux, mais tout simplement quon na pas pu trouver une meilleure solution, ni mme prouver quil y en avait une ! Toutefois, tous ces problmes sont lis et, si on trouve une solution polynomiale pour lun dentre eux, on saura rsoudre aussi facilement tous ses petits camarades. Ces problmes appartiennent tous la classe des problmes dits NP-complets .

300

Chapitre 14. Les types complmentaires


Le C++ tant un langage bas sur le C, il souffre des mmes limitations concernant les types de donnes avancs que celui-ci. Pour pallier cet inconvnient, la bibliothque standard C++ dnit des types complmentaires sous forme de classes C++, ventuellement template, et permettant de satisfaire aux besoins les plus courants. Parmi ces types, on notera avant tout le type basic_string, qui permet de manipuler les chanes de caractres de manire plus simple et plus sre quavec des pointeurs et des tableaux de caractres. Mais la bibliothque standard dnit galement des classes utilitaires qui permettent de manipuler les autres types plus facilement, ainsi que des types capables dutiliser toutes les ressources de la machine pour les calculs numriques avancs.

14.1. Les chanes de caractres


La classe template basic_string de la bibliothque standard, dclare dans len-tte string, facilite le travail du programmeur et permet dcrire du code manipulant des textes de manire beaucoup plus sre. En effet, cette classe encapsule les chanes de caractres C classiques et fournissent des services extrmement intressants qui ntaient pas disponibles auparavant. En particulier, la classe basic_string dispose des caractristiques suivantes :

compatibilit quasi-totale avec les chanes de caractres C standards ; gestion des chanes taille variable ; prise en charge de lallocation dynamique de la mmoire et de sa libration en fonction des besoins et de la taille des chanes manipules ; dnition des oprateurs de concatnation, de comparaison et des principales mthodes de recherche dans les chanes de caractres ; intgration totale dans la bibliothque standard, en particulier au niveau des ux dentre / sortie.

Comme il la t dit plus haut, la classe basic_string est une classe template. Cela signie quelle est capable de prendre en charge des chanes de nimporte quel type de caractre. Pour cela, elle ne se base que sur la classe des traits du type de caractre manipul. Il est donc parfaitement possible de lutiliser avec des types dnis par lutilisateur, pourvu que la classe des traits des caractres soit dnie pour ces types. Bien entendu, la classe basic_string peut tre utilise avec les types de caractres du langage, savoir char et wchar_t. Les dclarations de len-tte string sont essentiellement les suivantes :
template <class charT, class traits = char_traits<charT>, class Allocator = allocator<charT> > class basic_string { public: // Types typedef traits traits_type; typedef typename traits::char_type value_type; typedef Allocator allocator_type; typedef typename Allocator::size_type size_type; typedef typename Allocator::difference_type difference_type; typedef typename Allocator::reference reference_type; typedef typename Allocator::const_reference const_reference;

301

Chapitre 14. Les types complmentaires


typedef typename Allocator::pointer typedef typename Allocator::const_pointer pointer; const_pointer;

// Constante utilise en interne et reprsentant la valeur maximale // du type size_type : static const size_type npos = static_cast<size_type>(-1); // Constructeurs et destructeur : explicit basic_string(const Allocator &allocateur = Allocator()); basic_string(const basic_string &source, size_type debut = 0, size_type longueur = npos, const Allocator &allocateur = Allocator()); basic_string(const charT *chaine, size_type nombre, const Allocator &allocateur = Allocator()); basic_string(const charT *chaine, const Allocator &allocateur = Allocator()); basic_string(size_type nombre, charT caractere, const Allocator &allocateur = Allocator()); template <class InputIterator> basic_string(InputIterator debut, InputIterator fin, const Allocator &allocateur = Allocator()); ~basic_string(); // Itrateurs : typedef type_priv iterator; typedef type_priv const iterator; typedef std::reverse_iterator<iterator> reverse_iterator; typedef std::reverse_iterator<const_iterator> const_reverse_iterator; iterator begin(); const_iterator begin() const; iterator end(); const_iterator end() const; reverse_iterator rbegin(); const_reverse_iterator rbegin() const; reverse_iterator rend(); const_reverse_iterator rend() const; // Accesseurs : size_type size() const; size_type length() const; size_type max_size() const; size_type capacity() const; bool empty() const; allocator_type get_allocator() const; // Manipulateurs : void resize(size_type taille, charT caractere); void resize(size_type taille); void reserve(size_type taille = 0); // Accs aux donnes de la chane : const_reference operator[](size_type index) const; reference operator[](size_type index); const_reference at(size_type index) const; reference at(size_type index); const charT *c_str() const; const charT *data() const; size_type copy(charT *destination, size_type taille,

302

Chapitre 14. Les types complmentaires


size_type debut = 0) const; basic_string substr(size_type debut = 0, size_type taille = npos) const; // Affectation : basic_string &operator=(const basic_string &source); basic_string &operator=(const charT *source); basic_string &operator=(charT caractere); basic_string &assign(const basic_string &source); basic_string &assign(const basic_string &source, size_type position, size_type nombre); basic_string &assign(const charT *chaine, size_type nombre); basic_string &assign(const charT *chaine); basic_string &assign(size_type nombre, charT caractere); template <class InputIterator> basic_string &assign(InputIterator debut, InputIterator fin); // Concatnation et ajout : basic_string &operator+=(const basic_string &source); basic_string &operator+=(const charT *chaine); basic_string &operator+=(charT caractere); basic_string &append(const basic_string &source); basic_string &append(const basic_string &source, size_type position, size_type nombre); basic_string &append(const charT *chaine, size_type nombre); basic_string &append(const charT *chaine); basic_string &append(size_type nombre, charT caractere); template <class InputIterator> basic_string &append(InputIterator debut, InputIterator fin); // Insertion et extraction : basic_string &insert(size_type position, const basic_string &source); basic_string &insert(size_type position, const basic_string &source, size_type debut, size_type nombre); basic_string &insert(size_type position, const charT *chaine, size_type nombre); basic_string &insert(size_type position, const charT *chaine); basic_string &insert(size_type position, size_type nombre, charT caractere); iterator insert(iterator position, charT caractere = charT()); void insert(iterator position, size_type nombre, charT caractere); template <class InputIterator> void insert(iterator position, InputIterator debut, InputIterator fin); // Suppression : basic_string &erase(size_type debut = 0, size_type longueur = npos); iterator erase(iterator position); iterator erase(iterator debut, iterator fin); void clear(); // Remplacement et change : basic_string &replace(size_type position, size_type longueur, const basic_string &remplacement); basic_string &replace(size_type position, size_type longueur, const basic_string &remplacement, size_type debut, size_type taille); basic_string &replace(size_type position, size_type longueur, const charT *remplacement, size_type taille);

303

Chapitre 14. Les types complmentaires


basic_string &replace(size_type position, size_type longueur, const charT *remplacement); basic_string &replace(size_type position, size_type longueur, size_type nombre, charT caractere); basic_string &replace(iterator debut, iterator fin, const basic_string &remplacement); basic_string &replace(iterator debut, iterator fin, const charT *remplacement, size_type taille); basic_string &replace(iterator debut, iterator fin, const charT *remplacement); basic_string &replace(iterator debut, iterator fin, size_type nombre, charT caractere); template <class InputIterator> basic_string &replace(iterator debut, iterator fin, InputIterator debut_remplacement, InputIterator fin_remplacement); void swap(basic_string<charT, traits, Allocator> &chaine); // Comparaison : int compare(const basic_string &chaine) const; int compare(size_type debut1, size_type longueur1, const basic_string &chaine, size_type debut2, size_type longueur2) const; int compare(const charT *chaine) const; int compare(size_type debut, size_type longueur, const charT *chaine, size_type taille = npos) const; // Recherche : size_type find(const basic_string &motif, size_type position = 0) const; size_type find(const charT *motif, size_type position, size_type taille) const; size_type find(const charT *motif, size_type position = 0) const; size_type find(charT caractere, size_type position = 0) const; size_type rfind(const basic_string &motif, size_type position = npos) const; size_type rfind(const charT *motif, size_type position, size_type taille) const; size_type rfind(const charT *motif, size_type position = npos) const; size_type rfind(charT caractere, size_type position = npos) const; size_type find_first_of(const basic_string &motif, size_type position = 0) const; size_type find_first_of(const charT *motif, size_type position, size_type taille) const; size_type find_first_of(const charT *motif, size_type position = 0) const; size_type find_first_of(charT caractere, size_type position = 0) const; size_type find_last_of(const basic_string &motif, size_type position = npos) const; size_type find_last_of(const charT *motif, size_type position, size_type taille) const; size_type find_last_of(const charT *motif, size_type position = npos) const; size_type find_last_of(charT caractere, size_type position = npos) const; size_type find_first_not_of(const basic_string &motif, size_type position = 0) const; size_type find_first_not_of(const charT *motif, size_type position,

304

Chapitre 14. Les types complmentaires


size_type taille) const; size_type find_first_not_of(const charT *motif, size_type position = 0) const; size_type find_first_not_of(charT caractere, size_type position = 0) const; size_type find_last_not_of(const basic_string &motif, size_type position = npos) const; size_type find_last_not_of(const charT *motif, size_type position, size_type taille) const; size_type find_last_not_of(const charT *motif, size_type position = npos) const; size_type find_last_not_of(charT caractere, size_type position = npos) const; }; typedef basic_string<char> string; typedef basic_string<wchar_t> wstring;

Les oprateurs de concatnation, de comparaison et de srialisation dans les ux dentre / sortie sont galement dnis dans cet en-tte et nont pas t reports ici par souci de clart. Comme vous pouvez le voir, la classe basic_string dispose dun grand nombre de mthodes. Nous allons prsent les dtailler dans les paragraphes suivants. La bibliothque standard dnit deux types chanes de caractres pour les types standards de caractres du langage : le type string pour les char, et le type wstring pour les wchar_t. En pratique, ce seront donc ces types qui seront utiliss dans les programmes. Les exemples de la suite de ce document utiliseront donc le type string, mais vous tes libre dutiliser des instances de la classe basic_string pour dautres types de caractres.

14.1.1. Construction et initialisation dune chane


La manire la plus simple de construire une basic_string est simplement de la dclarer, sans paramtres. Cela a pour consquence dappeler le constructeur par dfaut, et dinitialiser la chane la chane vide. En revanche, si vous dsirez initialiser cette chane, plusieurs possibilits soffrent vous. Outre le constructeur de copie, qui permet de copier une autre basic_string, il existe plusieurs surcharges du constructeur permettant dinitialiser la chane de diffrentes manires. Le constructeur le plus utilis sera sans aucun doute le constructeur qui prend en paramtre une chane de caractres C classique :
string chaine("Valeur initiale");

Il existe cependant une variante de ce constructeur, qui prend en paramtre le nombre de caractres de la chane source utiliser pour linitialisation de la basic_string. Ce constructeur devra tre utilis dans le cas des tableaux de caractres, qui contiennent des chanes de caractres qui ne sont pas ncessairement termines par un caractre nul :
string chaine("Valeur initiale", 6);

La ligne prcdente initialise la chane chaine avec la chane de caractres "Valeur", car seuls les six premiers caractres de la chane dinitialisation sont utiliss. Il est galement possible dinitialiser une basic_string avec une partie de chane de caractres seulement. Pour cela, il faut utiliser le constructeur template, qui prend en paramtre un itrateur rfrenant le premier caractre utiliser lors de linitialisation de la basic_string et un itrateur rfrenant le

305

Chapitre 14. Les types complmentaires caractre suivant le dernier caractre utiliser. Bien entendu, ces deux itrateurs sont de simples pointeurs de caractres si les caractres devant servir linitialisation sont dans une chane de caractres C ou dans un tableau de caractres. Cependant, ce peut tre des itrateurs dun conteneur quelconque, pourvu que celui-ci contienne bien une squence de caractres et que le deuxime itrateur se trouve bien aprs le premier dans cette squence. Notez que le deuxime itrateur ne rfrence pas le dernier caractre de la squence dinitialisation, mais bien le caractre suivant. Il peut donc valoir la valeur de n de litrateur du conteneur source. Ainsi, le code suivant :
char *source = "Valeur initiale"; string s(source, source+6);

a strictement le mme effet que celui de lexemple prcdent. Enn, il existe un constructeur dont le but est dinitialiser une basic_string avec une suite de caractres identiques dune certaine longueur. Ce constructeur nest rellement pas difcile utiliser, puisquil suft de lui fournir en paramtre le nombre de caractres que la basic_string devra contenir et la valeur du caractre qui devra tre rpt dans cette chane. Vous remarquerez que tous ces constructeurs prennent en dernier paramtre une instance dallocateur mmoire utiliser pour les oprations dallocation et de libration de la mmoire que la chane est susceptible davoir faire. Vous pouvez spcier une instance quelconque, ou utiliser la valeur par dfaut fournie par les dclarations des constructeurs. Cette valeur par dfaut est tout simplement une instance temporaire de lallocateur spci en paramtre template de la classe basic_string. Par dfaut, cet allocateur est lallocateur standard, pour lequel toutes les instances permettent daccder la mme mmoire. Il nest donc pas ncessaire de spcier linstance utiliser, puisquelles sont toutes fonctionnellement identiques. En pratique donc, il est trs rare davoir spcier un allocateur mmoire dans ces constructeurs.

14.1.2. Accs aux proprits dune chane


La classe basic_string fournit un certain nombre daccesseurs permettant dobtenir des informations sur son tat et sur la chane de caractres quelle contient. Lune des informations les plus intressantes est sans nul doute la longueur de cette chane. Elle peut tre obtenue laide de deux accesseurs, qui sont strictement quivalents : size et length. Vous pouvez utiliser lun ou lautre, selon votre convenance. Par ailleurs, si vous dsirez simplement savoir si la basic_string est vide, vous pouvez appeler la mthode empty.
Note : Attention, contrairement ce que son nom pourrait laisser penser, la mthode empty ne vide pas la basic_string !

La taille maximale quune basic_string peut contenir est souvent directement lie la quantit de mmoire disponible, puisque la chane de caractres quelle contient est alloue dynamiquement. Il ny a donc souvent pas beaucoup dintrt obtenir cette taille, mais vous pouvez malgr tout le faire, grce la mthode max_size. La quantit de mmoire rellement alloue par une basic_string peut tre suprieure la longueur de la chane de caractres contenue. En effet, la classe basic_string peut conserver une marge de manuvre, pour le cas o la chane devrait tre agrandie la suite dune opration particulire. Cela permet de rduire les rallocations de mmoire, qui peuvent tre trs coteuses lorsque la mmoire se fragmente (la chane doit tre recopie vers un nouvel emplacement si un autre bloc mmoire se trouve juste aprs le bloc mmoire rallouer). Cette quantit de mmoire peut tre obtenue laide

306

Chapitre 14. Les types complmentaires de la mthode capacity. Nous verrons comment rserver de la place mmoire en prvision dun redimensionnement ultrieur dans la section suivante. Dans le cas o vous utiliseriez un allocateur diffrent de lallocateur par dfaut, vous pouvez obtenir une copie de cet allocateur grce la mthode get_allocator. Il est relativement rare davoir utiliser cette mthode.

14.1.3. Modication de la taille des chanes


Une fois quune basic_string a t construite, il est possible de la modier a posteriori pour la rduire, lagrandir ou augmenter sa capacit. Ces oprations peuvent tre ralises laide de mthodes fournies cet effet. La mthode resize permet de modier la taille de la chane de caractres stocke dans la basic_string. Dans sa version la plus simple, elle prend en paramtre la nouvelle taille que la chane doit avoir. Si cette taille est infrieure la taille courante, la chane est tronque. En revanche, si cette taille est suprieure la taille actuelle, la chane est tendue et les nouveaux caractres sont initialiss avec la valeur des caractres dnie par le constructeur par dfaut de leur classe. Pour les types prdnis char et wchar_t, cette valeur est toujours le caractre nul. Les donnes stockes dans la basic_string reprsentent donc toujours la mme chane de caractres C, puisque ces chanes utilisent le caractre nul comme marqueur de n de chane. Ainsi, la longueur renvoye par la mthode size peut tre diffrente de la longueur de la chane C contenue par la basic_string. Exemple 14-1. Redimensionnement dune chane
#include <iostream> #include <string> #include <string.h> using namespace std; int main(void) { string s("123"); s.resize(10); cout << s << endl; // La nouvelle taille vaut bien 10 : cout << "Nouvelle taille : " << s.length() << endl; // mais la longueur de la chane C reste 3 : cout << "Longueur C : " << strlen(s.c_str()) << endl; return 0; }

Note : La mthode c_str utilise dans cet exemple sera dcrite en dtail dans la section suivante. Elle permet dobtenir ladresse de la chane C stocke en interne dans la basic_string. La fonction strlen quant elle est une des fonctions de manipulation des chanes de caractres de la bibliothque C. Elle est dnie dans le chier den-tte string.h (que lon ne confondra pas avec len-tte string de la bibliothque standard C++), et renvoie la longueur de la chane de caractres qui lui est fournie en paramtre.

Si la valeur par dfaut utilise pour les caractres complmentaires dans la mthode resize nest pas celle qui est dsire, il faut en utiliser une autre version. Cette deuxime version prend, en plus

307

Chapitre 14. Les types complmentaires de la nouvelle taille de la chane de caractres, le caractre de remplissage utiliser pour le cas o la nouvelle taille serait suprieure la taille initiale de la chane :
string s("123"); s.resize(10, a);

Dans cet exemple, s contient nalement la chane de caractres "123aaaaaaa". Nous avons vu prcdemment que les basic_string taient susceptibles dallouer plus de mmoire que ncessaire pour stocker leurs donnes an de limiter le nombre de rallocation mmoire. Ce mcanisme est compltement pris en charge par la bibliothque, et le programmeur na en gnral pas sen soucier. Cependant, il peut exister des situations o lon sait lavance la taille minimale quune chane doit avoir pour permettre de travailler dessus sans craindre de rallocations mmoire successives. Dans ce cas, on a tout intrt xer la capacit de la chane directement cette valeur, an doptimiser les traitements. Cela est ralisable laide de la mthode reserve. Cette mthode prend en paramtre la capacit minimale que la basic_string doit avoir. La nouvelle capacit nest pas forcment gale ce paramtre aprs cet appel, car la basic_string peut allouer plus de mmoire que demand. Exemple 14-2. Rservation de mmoire dans une chane
#include <iostream> #include <string> using namespace std; int main(void) { string s; cout << s.capacity() << endl; s.reserve(15); s = "123"; cout << s.capacity() << endl; return 0; }

Note : Les mthodes resize et reserve peuvent effectuer une rallocation de la zone mmoire contenant la chane de caractres. Par consquent, toutes les rfrences sur les caractres de la chane et tous les itrateurs sur cette chane deviennent invalide la suite de leur excution.

14.1.4. Accs aux donnes de la chane de caractres


Les caractres des basic_string peuvent tre accds de nombreuses manires. Premirement, la classe basic_string surcharge loprateur daccs aux lments dun tableau, et lon pourra les utiliser pour obtenir une rfrence un des caractres de la chane partir de son indice. Cet oprateur nest dni que pour les indices valides dans la chane de caractres, savoir les indices variant de 0 la valeur retourne par la mthode size de la chane moins un :
#include <iostream> #include <string>

308

Chapitre 14. Les types complmentaires


using namespace std; int main(void) { string s("azc"); // Remplace le deuxime caractre de la chane par un b : s[1] = b; cout << s << endl; return 0; }

Lorsquil est appliqu une basic_string constante, loprateur tableau peut renvoyer la valeur du caractre dont lindice est exactement la taille de la chane. Il sagit videmment du caractre nul servant de marqueur de n de chane. En revanche, la rfrence renvoye par cet oprateur pour toutes les autres valeurs, ainsi que par loprateur tableau appliqu aux chanes non constante pour le caractre de n de chane ne sont pas valides. Le comportement des programmes qui effectuent de tels accs est imprvisible. Il existe une autre possibilit pour accder aux caractres dune basic_string. Il sagit de la mthode at. Contrairement loprateur tableau, cette mthode permet deffectuer un contrle sur la validit de lindice utilis. Elle renvoie, comme loprateur de tableau de la classe basic_string, la rfrence du caractre dont lindice est spci en paramtre. Cependant, elle effectue au pralable un contrle sur la validit de cet indice, qui doit toujours tre strictement infrieur la taille de la chane. Dans le cas contraire, la mthode at lance une exception out_of_range :
#include <iostream> #include <string> #include <stdexcept> using namespace std; int main(void) { string s("01234"); try { s.at(5); } catch (const out_of_range &) { cout << "Dbordement !" << endl; } return 0; }

La classe basic_string ne contient pas doprateur de transtypage vers les types des chanes de caractres C classique, savoir le type pointeur sur caractre et pointeur sur caractre constant. Cest un choix de conception, qui permet dviter les conversions implicites des basic_string en chane C classique, qui pourraient tre extrmement dangereuses. En effet, ces conversions conduiraient obtenir implicitement des pointeurs qui ne seraient plus valides ds quune opration serait effectue sur la basic_string source. Cependant, la classe basic_string fournit deux mthodes permettant dobtenir de tels pointeurs de manire explicite. Le programmeur prend donc ses responsabilits lorsquil utilise ces mthodes, et est suppos savoir dans quel contexte ces pointeurs sont valides.

309

Chapitre 14. Les types complmentaires Ces deux mthodes sont respectivement la mthode data et la mthode c_str. La premire mthode renvoie un pointeur sur les donnes de la basic_string. Ces donnes ne sont rien dautre quun tableau de caractres, dont la dimension est exactement la valeur retourne par la mthode size ou par la mthode length. Ce tableau peut contenir des caractres de terminaison de chane, si par exemple une telle valeur a t crite explicitement ou a t introduite suite un redimensionnement de la chane. La mthode c_str en revanche retourne un pointeur sur la chane de caractres C contenue dans la basic_string. Contrairement aux donnes renvoyes par la mthode data, cette chane est ncessairement termine par un caractre de n de chane. Cette mthode sera donc utilise partout o lon veut obtenir une chane de caractres C classique, mais elle ne devra pas tre utilise pour accder aux donnes situes aprs ce caractre de n de chane. Exemple 14-3. Accs direct aux donnes dune chane
#include <iostream> #include <string> using namespace std; int main(void) { string s("123456"); // La chane C est coupe au troisime caractre : s[3] = 0; cout << s.c_str() << endl; // Mais on peut quand mme obtenir les caractres suivants : cout << s.data()[4] << s.data()[5] << endl; return 0; }

Notez que ces mthodes renvoient des pointeurs sur des donnes constantes. Cela est normal, car il est absolument interdit de modier les donnes internes la basic_string qui a fourni ces pointeurs. Vous ne devez donc en aucun cas essayer dcrire dans ces tableaux de caractres, mme en faisant un transtypage au pralable. Enn, la classe basic_string dnit des itrateurs accs alatoires permettant daccder ses donnes comme sil sagissait dune chane de caractres standard. Les mthodes begin et end permettent dobtenir respectivement un itrateur sur le premier caractre de la chane et la valeur de n de ce mme itrateur. De mme, les mthodes rbegin et rend permettent de parcourir les donnes de la basic_string en sens inverse. Prenez garde au fait que ces itrateurs ne sont valides que tant quaucune mthode modiant la chane nest appele.

14.1.5. Oprations sur les chanes


La classe basic_string fournit tout un ensemble de mthodes permettant deffectuer les oprations les plus courantes sur les chanes de caractres. Cest cet ensemble de mthodes qui font tout lintrt de cette classe par rapport aux chanes de caractres classiques du langage C, car elles prennent en charge automatiquement les allocations mmoire et les copies de chanes que limplmentation de ces fonctionnalits peut imposer. Ces oprations comprennent laffectation et la concatnation de deux chanes de caractres, lextraction dune sous-chane, ainsi que la suppression et le remplacement de caractres dans une chane.

310

Chapitre 14. Les types complmentaires

14.1.5.1. Affectation et concatnation de chanes de caractres


Plusieurs surcharges de loprateur daffectation sont dnies dans la classe basic_string. Ces surcharges permettent daffecter une nouvelle valeur une chane, en remplaant ventuellement lancienne valeur. Dans le cas dun remplacement, la mmoire consomme par lancienne valeur est automatiquement rutilise ou libre si une rallocation est ncessaire. Ces oprateurs daffectation peuvent prendre en paramtre une autre basic_string, une chane de caractres C classique ou mme un simple caractre. Leur utilisation est donc directe, il suft simplement dcrire une affectation normale. Il est impossible, avec loprateur daffectation, de fournir des paramtres supplmentaires comme ceux dont les constructeurs de la classe basic_string disposent par exemple. Cest pour cette raison quune autre mthode, la mthode assign, a t dnie pour permettre de faire des affectations plus complexes. Les premires versions de ces mthodes permettent bien entendu deffectuer laffectation dune autre basic_string ou dune chane de caractres C classique. Cependant, il est galement possible de spcier la longueur de la portion de chane copier en deuxime paramtre pour les chanes C, et la position ainsi que le nombre de caractres copier dans le cas dune basic_string. Il est galement possible de rinitialiser une basic_string avec un caractre spcique, en donnant et le nombre de caractres dupliquer dans la chane en premier paramtre et la valeur de ce caractre en deuxime paramtre. Enn, il existe une version template de cette mthode permettant daffecter la basic_string la squence de caractres compris entre deux itrateurs dun conteneur. Exemple 14-4. Affectation de chane de caractres
#include <iostream> #include <string> using namespace std; int main(void) { string s1, s2; char *p="1234567890"; // Affecte "345" s1 : s1.assign(p+2, p+6); cout << s1 << endl; // Affecte les deux premiers caractres de s1 s2 : s2.assign(s1, 0, 2); cout << s2 << endl; // Rinitialise s1 avec des A : s1.assign(5, A); cout << s1 << endl; return 0; }

De manire similaire, loprateur daffectation avec addition += a t surcharg an de permettre les concatnations de chanes de caractres de manire intuitive. Cet oprateur permet dajouter aussi bien une autre basic_string quune chane de caractres C classique ou un unique caractre la n dune basic_string. Comme cet oprateur est trop restrictif lorsquil sagit de concatner une partie seulement dune autre chane une basic_string, un jeu de mthodes append a t dni. Ces mthodes permettent dajouter une basic_string une autre basic_string ou une chane de caractres C bien entendu, mais aussi dajouter une partie seulement de ces chanes ou un nombre dtermin de caractres. Toutes ces mthodes prennent les mmes paramtres que les mthodes assign correspondantes et leur emploi ne devrait pas poser de problme particulier.

311

Chapitre 14. Les types complmentaires Exemple 14-5. Concatnation de chanes de carctres
#include <iostream> #include <string> using namespace std; int main(void) { string s1 = "abcef"; string s2 = "ghijkl"; // Utilisation de loprateur de concatnation : s1+=s2; cout << s1 << endl; // Utilisation de la mthode append : s1.append("mnopq123456", 5); cout << s1 << endl; return 0; }

14.1.5.2. Extraction de donnes dune chane de caractres


Nous avons vu que les mthodes data et c_str permettaient dobtenir des pointeurs sur les donnes des basic_string. Cependant, il est interdit de modier les donnes de la basic_string au travers de ces pointeurs. Or, il peut tre utile, dans certaines situations, davoir travailler sur ces donnes, il faut donc pouvoir en faire une copie temporaire. Cest ce que permet la mthode copy. Cette fonction prend en paramtre un pointeur sur une zone de mmoire devant accueillir la copie des donnes de la basic_string, le nombre de caractres copier, ainsi que le numro du caractre de la basic_string partir duquel la copie doit commencer. Ce dernier paramtre est facultatif, car il prend par dfaut la valeur 0 (la copie se fait donc partir du premier caractre de la basic_string. Exemple 14-6. Copie de travail des donnes dune basic_string
#include <iostream> #include <string> using namespace std; int main(void) { string s="1234567890"; // Copie la chane de caractres dans une zone de travail : char buffer[16]; s.copy(buffer, s.size(), 0); buffer[s.size()] = 0; cout << buffer << endl; return 0; }

La basic_string doit contenir sufsamment de caractres pour que la copie puisse se faire. Si ce nest pas le cas, elle lancera une exception out_of_range. En revanche, la mthode copy ne peut faire aucune vrication quant la place disponible dans la zone mmoire qui lui est passe en paramtre. Il est donc de la responsabilit du programmeur de sassurer que cette zone est sufsamment grande pour accueillir tous les caractres quil demande.

312

Chapitre 14. Les types complmentaires La mthode copy permet dobtenir la copie dune sous-chane de la chane contenue dans la basic_string. Toutefois, si lon veut stocker cette sous-chane dans une autre basic_string, il ne faut pas utiliser cette mthode. La mthode substr permet en effet deffectuer ce travail directement. Cette mthode prend en premier paramtre le numro du premier caractre partir de la sous-chane copier, ainsi que sa longueur. Comme pour la mthode copy, il faut que la basic_string source contienne sufsamment de caractres faute de quoi une exception out_of_range sera lance. Exemple 14-7. Extraction de sous-chane
#include <iostream> #include <string> using namespace std; int main(void) { string s1 = "1234567890"; string s2 = s1.substr(2, 5); cout << s2 << endl; return 0; }

14.1.5.3. Insertion et suppression de caractres dans une chane


La classe basic_string dispose de tout un jeu de mthodes dinsertion de caractres ou de chanes de caractres au sein dune basic_string existante. Toutes ces mthodes sont des surcharges de la mthode insert. Ces surcharges prennent toutes un paramtre en premire position qui indique lendroit o linsertion doit tre faite. Ce paramtre peut tre soit un numro de caractre, indiqu par une valeur de type size_type, soit un itrateur de la basic_string dans laquelle linsertion doit tre faite. Les autres paramtres permettent de spcier ce qui doit tre insr dans cette chane. Les versions les plus simples de la mthode insert prennent en deuxime paramtre une autre basic_string ou une chane de caractres C classique. Leur contenu sera insr lemplacement indiqu. Lorsque le deuxime paramtre est une basic_string, il est possible dindiquer le numro du premier caractre ainsi que le nombre de caractres total insrer. De mme, lors de linsertion dune chane C classique, il est possible dindiquer le nombre de caractres de cette chane qui doivent tre insrs. Il existe aussi des mthodes insert permettant dinsrer un ou plusieurs caractres un emplacement donn dans la chane de caractres. Ce caractre doit alors spci en deuxime paramtre, sauf si lon veut insrer plusieurs caractres identiques dans la chane, auquel cas on doit indiquer le nombre de caractres insrer et la valeur de ce caractre. Enn, il existe une version de la mthode insert qui prend en paramtre, en plus de litrateur spciant la position partir de laquelle linsertion doit tre faite dans la basic_string, deux autres itrateurs dun quelconque conteneur contenant des caractres. Utilis avec des pointeurs de caractres, cet itrateur peut tre utilis pour insrer un morceau quelconque de chane de caractres C dans une basic_string. Toutes ces mthodes renvoient gnralement la basic_string sur laquelle ils travaillent, sauf les mthodes qui prennent en paramtre un itrateur. Ces mthodes supposent en effet que la manipulation de la chane de caractres se fait par lintermdiaire de cet itrateur, et non par lintermdiaire dune rfrence sur la basic_string. Cependant, la mthode insert permettant dinsrer un caractre unique un emplacement spci par un itrateur renvoie la valeur de litrateur rfrenant le caractre qui vient dtre insr an de permettre de rcuprer ce nouvel itrateur pour les oprations ultrieures.

313

Chapitre 14. Les types complmentaires Exemple 14-8. Insertion de caractres dans une chane
#include <iostream> #include <string> using namespace std; int main(void) { string s = "abef"; // Insre un c et un d : s.insert(2, "cdze", 2); // Idem pour g et h, mais avec une basic_string : string gh = "gh"; s.insert(6, gh); cout << s << endl; return 0; }

Il existe galement trois surcharges de la mthode erase, dont le but est de supprimer des caractres dans une chane en dcalant les caractres suivant les caractres supprims pour remplir les positions ainsi libres. La premire mthode erase prend en premier paramtre la position du premier caractre et le nombre des caractres supprimer. La deuxime mthode fonctionne de manire similaire, mais prend en paramtre litrateur de dbut et litrateur de n de la sous-chane de caractres qui doit tre supprime. Enn, la troisime version de erase permet de supprimer un unique caractre, dont la position est spcie encore une fois par un itrateur. Ces deux dernires mthodes renvoient litrateur rfrenant le caractre suivant le dernier caractre qui a t supprim de la chane. Sil ny avait pas de caractres aprs le dernier caractre effac, litrateur de n de chane est renvoy. Enn, il existe une mthode ddie pour leffacement complet de la chane de caractres contenue dans une basic_string. Cette mthode est la mthode clear. Exemple 14-9. Suppression de caractres dans une chane
#include <iostream> #include <string> using namespace std; int main(void) { string s = "abcdeerrfgh"; // Supprime la faute de frappe : s.erase(5,3); cout << s << endl; // Efface la chane de caractres complte : s.clear(); if (s.empty()) cout << "Vide !" << endl; return 0; }

314

Chapitre 14. Les types complmentaires

14.1.5.4. Remplacements de caractres dune chane


Comme pour linsertion de chanes de caractres, il existe tout un jeu de fonctions permettant deffectuer un remplacement dune partie de la chane de caractres stocke dans les basic_string par une autre chane de caractres. Ces mthodes sont nommes replace et sont tout fait similaires dans le principe aux mthodes insert. Cependant, contrairement celles-ci, les mthodes replace prennent un paramtre supplmentaire pour spcier la longueur ou le caractre de n de la sous-chane remplacer. Ce paramtre doit tre fourni juste aprs le premier paramtre, qui indique toujours le caractre de dbut de la sous-chane remplacer. Il peut tre de type size_type pour les versions de replace qui travaillent avec des indices, ou tre un itrateur, pour les versions de replace qui travaillent avec des itrateurs. Les autres paramtres des fonctions replace permettent de dcrire la chane de remplacement, et fonctionnent exactement comme les paramtres correspondants des fonctions insert. Exemple 14-10. Remplacement dune sous-chane dans une chane
#include <iostream> #include <string> using namespace std; int main(void) { string s = "abcerfg"; // Remplace le e et le r par un d et un e : s.replace(3, 2, "de"); cout << s << endl; return 0; }

Dans le mme ordre dide que le remplacement, on trouvera la mthode swap de la classe basic_string, qui permet dintervertir le contenu de deux chanes de caractres. Cette mthode prend en paramtre une rfrence sur la deuxime chane de caractres, avec laquelle linterversion doit tre faite. La mthode swap pourra devra tre utilise de prfrence pour raliser les changes de chanes de caractres, car elle est optimise et effectue en fait lchange par rfrence. Elle permet donc dviter de faire une copie temporaire de la chane destination et dcraser la chane source avec cette copie. Exemple 14-11. change du contenu de deux chanes de caractres
#include <iostream> #include <string> using namespace std; int main(void) { string s1 = "abcd"; string s2 = "1234"; cout << "s1 = " << s1 << endl; cout << "s2 = " << s2 << endl; // Intervertit les deux chanes : s1.swap(s2); cout << "s1 = " << s1 << endl; cout << "s2 = " << s2 << endl;

315

Chapitre 14. Les types complmentaires


return 0; }

14.1.6. Comparaison de chanes de caractres


La comparaison des basic_string se base sur la mthode compare, dont plusieurs surcharges existent an de permettre des comparaisons diverses et varies. Les deux versions les plus simples de la mthode compare prennent en paramtre soit une autre basic_string, soit une chane de caractres C classique. Elles effectuent donc la comparaison de la basic_string sur laquelle elles sappliquent avec ces chanes. Elles utilisent pour cela la mthode eq de la classe des traits des caractres utiliss par la chane. Si les deux chanes ne diffrent que par leur taille, la chane la plus courte sera dclare infrieure la chane la plus longue. Les deux autres mthodes compare permettent deffectuer la comparaison de sous-chanes de caractres entre elles. Elles prennent toutes les deux lindice du caractre de dbut et lindice du caractre de n de la sous-chane de la basic_string sur laquelle elles sont appliques, un troisime paramtre indiquant une autre chane de caractres, et des indices spciant la deuxime sous-chane dans cette chane. Si le troisime argument est une basic_string, il faut spcier galement lindice de dbut et lindice de n de la sous-chane. En revanche, sil sagit dune chane C classique, la deuxime souschane commence toujours au premier caractre de cette chane, et il ne faut spcier que la longueur de cette sous-chane. La valeur renvoye par les mthodes compare est de type entier. Cet entier est nul si les deux chanes sont strictement gales (et de mme taille), ngatif si la basic_string sur laquelle la mthode compare est applique est plus petite que la chane passe en argument, soit en taille, soit au sens de lordre lexicographique, et positif dans le cas contraire. Exemple 14-12. Comparaisons de chanes de caractres
#include <iostream> #include <string> using namespace std; int main(void) { const char *c1 = "bcderefb"; const char *c2 = "bcdetab"; // c2 > c1 const char *c3 = "bcderefas"; // c3 < c1 const char *c4 = "bcde"; // c4 < c1 string s1 = c1; if (s1 < c2) cout << "c1 < c2" << endl; else cout << "c1 >= c2" << endl; if (s1.compare(c3)>0) cout << "c1 > c3" << endl; else cout << "c1 <= c3" << endl; if (s1.compare(0, string::npos, c1, 4)>0) cout << "c1 > c4" << endl; else cout << "c1 <= c4" << endl; return 0; }

Bien entendu, les oprateurs de comparaison classiques sont galement dnis an de permettre des comparaisons simples entre chane de caractres. Grce ces oprateurs, il est possible de manipuler

316

Chapitre 14. Les types complmentaires les basic_string exactement comme les autres types ordonns du langage. Plusieurs surcharge de ces oprateurs ont t dnies et travaillent avec les diffrents types de donnes avec lesquels il est possible pour une basic_string de se comparer. Lemploi de ces oprateurs est naturel et ne pose pas de problmes particuliers.
Note : Toutes ces comparaisons se basent sur lordre lexicographique du langage C. Autrement dit, les comparaisons entre chanes de caractres ne tiennent pas compte de la locale et des conventions nationales. Elles sont donc trs efcaces, mais ne pourront pas tre utilises pour comparer des chanes de caractres humainement lisibles. Vous trouverez de plus amples renseignements sur la manire de prendre en compte les locales dans les comparaisons de chanes de caractres dans le Chapitre 16.

14.1.7. Recherche dans les chanes


Les oprations de recherche dans les chanes de caractres constituent une des fonctionnalits des chanes les plus courantes. Elles constituent la plupart des oprations danalyse des chanes, et sont souvent le pendant de la construction et la concatnation de chanes. La classe basic_string fournit donc tout un ensemble de mthodes permettant deffectuer des recherches de caractres ou de souschanes dans une basic_string. Les fonctions de recherche sont toutes surcharges an de permettre de spcier la position partir de laquelle la recherche doit commencer dune part, et le motif de caractre rechercher. Le premier paramtre indique toujours quel est ce motif, que ce soit une autre basic_string, une chane de caractres C classique ou un simple caractre. Le deuxime paramtre est le numro du caractre de la basic_string sur laquelle la mthode de recherche sapplique et partir duquelle elle commence. Ce deuxime paramtre peut tre utilis pour effectuer plusieurs recherches successives, en repartant de la dernire position trouve chaque fois. Lors dune premire recherche ou lors dune recherche unique, il nest pas ncessaire de donner la valeur de ce paramtre, car les mthodes de recherche utilisent la valeur par dfaut qui convient (soit le dbut de la chane, soit la n, selon le sens de recherche utilis par la mthode). Les paramtres suivants permettent de donner des informations complmentaires sur le motif utiliser pour la recherche. Il nest utilis que lorsque le motif est une chane de caractres C classique. Dans ce cas, il est en effet possible de spcier la longueur du motif dans cette chane. Les diffrentes fonctions de recherche disponibles sont prsentes dans le tableau suivant : Tableau 14-1. Fonctions de recherche dans les chanes de caractres Mthode
find

Description Cette mthode permet de rechercher la sous-chane correspondant au motif pass en paramtre dans la basic_string sur laquelle elle est applique. Elle retourne lindice de la premire occurrence de ce motif dans la chane de caractres, ou la valeur npos si le motif ny apparat pas. Cette mthode permet deffectuer une recherche similaire celle de la mthode find, mais en parcourant la chane de caractres en sens inverse. Notez bien que ce nest pas le motif qui est invers ici, mais le sens de parcours de la chane. Ainsi, la mthode rfind retourne lindice de la dernire occurrence du motif dans la chane, ou la valeur npos si le motif na pas t trouv.

rfind

317

Chapitre 14. Les types complmentaires Mthode


find_first_of

Description Cette mthode permet de rechercher la premire occurrence dun des caractres prsents dans le motif fourni en paramtre. Il ne sagit donc plus dune recherche de chane de caractres, mais de la recherche de tous les caractres dun ensemble donn. La valeur retourne est lindice du caractre trouv, ou la valeur npos si aucun caractre du motif nest dtect dans la chane. Cette mthode est la mthode find_first_of ce que rfind est find. Elle effectue la recherche du dernier caractre de la basic_string qui se trouve dans la liste des caractres du motif fourni en paramtre. La valeur retourne est lindice de ce caractre sil existe, et npos sinon.

find_last_of

find_first_not_of

Cette mthode travaille en logique inverse par rapport la mthode find_first_of. En effet, elle recherche le premier caractre de la basic_string qui nest pas dans le motif fourni en paramtre. Elle renvoie lindice de ce caractre, ou npos si celui-ci nexiste pas. Cette mthode effectue le mme travail que la mthode find_first_not_of, mais en parcourant la chane de caractres source en sens inverse. Elle dtermine donc lindice du premier caractre en partant de la n qui ne se trouve pas dans le motif fourni en paramtre. Elle renvoie npos si aucun caractre ne correspond ce critre.

find_last_not_of

Exemple 14-13. Recherches dans les chanes de caractres


#include <iostream> #include <string> using namespace std; int main(void) { string s = "Bonjour tout le monde !"; // Recherche le mot "monde" : string::size_type pos = s.find("monde"); cout << pos << endl; // Recherche le mot "tout" en commenant par la fin : pos = s.rfind("tout"); cout << pos << endl; // Dcompose la chane en mots : string::size_type debut = s.find_first_not_of(" \t\n"); while (debut != string::npos) { // Recherche la fin du mot suivant : pos = s.find_first_of(" \t\n", debut); // Affiche le mot : if (pos != string::npos) cout << s.substr(debut, pos - debut) << endl; else cout << s.substr(debut) << endl; debut = s.find_first_not_of(" \t\n", pos); } return 0; }

318

Chapitre 14. Les types complmentaires


Note : Toutes ces fonctions de recherche utilisent lordre lexicographique du langage C pour effectuer leur travail. Elles peuvent donc ne pas convenir pour effectuer des recherches dans des chanes de caractres saisies par des humains, car elles ne prennent pas en compte la locale et les paramtres nationaux de lutilisateur. La raison de ce choix est essentiellement la recherche de lefcacit dans la bibliothque standard. Nous verrons dans le Chapitre 16 la manire de procder pour prendre en compte les paramtres nationaux au niveau des chanes de caractres.

14.1.8. Fonctions dentre / sortie des chanes de caractres


Pour terminer ce tour dhorizon des chanes de caractres, signalons que la bibliothque standard C++ fournit des oprateurs permettant deffectuer des critures et des lectures sur les ux dentre / sortie. Les oprateurs << et >> sont donc surchargs pour les basic_string, et permettent de manipuler celles-ci comme des types normaux. Loprateur << permet denvoyer le contenu de la basic_string sur le ux de sortie standard. Loprateur >> lit les donnes du ux dentre standard, et les affecte la basic_string quil reoit en paramtre. Il sarrte ds quil rencontre le caractre de n de chier, un espace, ou que la taille maximale des basic_string a t atteinte (cas improbable) :
#include <iostream> #include <string> using namespace std; int main(void) { string s1, s2; cin >> s1; cin >> s2; cout << "Premier mot : " << endl << s1 << endl; cout << "Deuxime mot : " << endl << s2 << endl; return 0; }

Cependant, ces oprateurs peuvent ne pas savrer sufsants. En effet, lune des principales difcults dans les programmes qui manipulent des chanes de caractres est de lire les donnes qui proviennent dun ux dentre ligne par ligne. La notion de ligne nest pas trs claire, et dpend fortement de lenvironnement dexcution. La bibliothque standard C++ suppose, quant elle, que les lignes sont dlimites par un caractre spcial servant de marqueur spcial. Gnralement, ce caractre est le caractre \n, mais il est galement possible dutiliser dautres sparateurs. Pour simplier les oprations de lecture de textes constitus de lignes, la bibliothque fournit la fonction getline. Cette fonction prend en premier paramtre le ux dentre sur lequel elle doit lire la ligne, et la basic_string dans laquelle elle doit stocker cette ligne en deuxime paramtre. Le troisime paramtre permet dindiquer le caractre sparateur de ligne. Ce paramtre est facultatif, car il dispose dune valeur par dfaut qui correspond au caractre de n de ligne classique \n. Exemple 14-14. Lecture de lignes sur le ux dentre
#include <iostream> #include <string>

319

Chapitre 14. Les types complmentaires

using namespace std; int main(void) { string s1, s2; getline(cin, s1); getline(cin, s2); cout << "Premire ligne : " << s1 << endl; cout << "Deuxime ligne : " << s2 << endl; return 0; }

14.2. Les types utilitaires


La bibliothque standard utilise en interne un certain nombre de types de donnes spciques, essentiellement dans un but de simplicit et de facilit dcriture. Ces types seront en gnral rarement utiliss par les programmeurs, mais certaines fonctionnalits de la bibliothque standard peuvent y avoir recours. Il faut donc connatre leur existence et savoir les manipuler correctement.

14.2.1. Les pointeurs auto


La plupart des variables dtruisent leur contenu lorsquelles sont dtruites elles-mmes. Une exception notable ce comportement est bien entendu celle des pointeurs, qui par dnition ne contiennent pas eux-mmes leurs donnes mais plutt une rfrence sur celles-ci. Lorsque ces donnes sont alloues dynamiquement, il faut systmatiquement penser les dtruire manuellement lorsquon nen a plus besoin. Cela peut conduire des fuites de mmoire ( Memory Leak en anglais) trs facilement. Si de telles fuites ne sont pas gnantes pour les processus dont la dure de vie est trs courte, elles peuvent ltre considrablement plus pour les processus destins fonctionner longtemps, si ce nest en permanence, sur une machine. En fait, dans un certain nombre de cas, lallocation dynamique de mmoire nest utilise que pour effectuer localement des oprations sur un nombre arbitraire de donnes qui ne peut tre connu qu lexcution. Cependant, il est relativement rare davoir conserver ces donnes sur de longues priodes, et il est souvent souhaitable que ces donnes soient dtruites lorsque la fonction qui les a alloues se termine. Autrement dit, il faudrait que les pointeurs dtruisent automatiquement les donnes quils rfrencent lorsquils sont eux-mmes dtruits. La bibliothque standard C++ fournit cet effet une classe dencapsulation des pointeurs, qui permet dobtenir ces fonctionnalits. Cette classe se nomme auto_ptr, en raison du fait que ses instances sont utilises comme des pointeurs de donnes dont la porte est la mme que celle des variables automatiques. La dclaration de cette classe est ralise comme suit dans len-tte memory :
template <class T> class auto_ptr { public: typedef T element_type; explicit auto_ptr(T *pointeur = 0) throw(); auto_ptr(const auto_ptr &source) throw(); template <class U> auto_ptr(const auto_ptr<U> &source) throw();

320

Chapitre 14. Les types complmentaires


~auto_ptr() throw(); auto_ptr &operator=(const auto_ptr &source) throw(); template <class U> auto_ptr &operator=(const auto_ptr<U> &source) throw(); T T T T }; &operator*() const throw(); *operator->() const throw(); *get() const throw(); *release() const throw();

Cette classe permet de construire un objet contrlant un pointeur sur un autre objet allou dynamiquement avec loprateur new. Lorsquil est dtruit, lobjet rfrenc est automatiquement dtruit par un appel loprateur delete. Cette classe utilise donc une smantique de proprit stricte de lobjet contenu, puisque le pointeur ainsi contrl ne doit tre dtruit quune seule fois. Cela implique plusieurs remarques. Premirement, il y a ncessairement un transfert de proprit du pointeur encapsul lors des oprations de copie et daffectation. Deuximement, toute opration susceptible de provoquer la perte du pointeur encapsul provoque sa destruction automatiquement. Cest notamment le cas lorsquune affectation dune autre valeur est faite sur un auto_ptr contenant dj un pointeur valide. Enn, il ne faut jamais dtruire soi-mme lobjet point une fois que lon a affect un pointeur sur celui-ci un auto_ptr. Il est trs simple dutiliser les pointeurs automatiques. En effet, il suft de les initialiser leur construction avec la valeur du pointeur sur lobjet allou dynamiquement. Ds lors, il est possible dutiliser lauto_ptr comme le pointeur original, puisquil dnit les oprateurs * et ->. Les auto_ptr sont souvent utiliss en tant que variable automatique dans les sections de code susceptible de lancer des exceptions, puisque la remonte des exceptions dtruit les variables automatiques. Il nest donc plus ncessaire de traiter ces exceptions et de dtruire manuellement les objets allous dynamiquement avant de relancer lexception. Exemple 14-15. Utilisation des pointeurs automatiques
#include <iostream> #include <memory> using namespace std; class A { public: A() { cout << "Constructeur" << endl; } ~A() { cout << "Destructeur" << endl; } }; // Fonction susceptible de lancer une exception :

321

Chapitre 14. Les types complmentaires


void f() // Alloue dynamiquement un objet : auto_ptr<A> p(new A); // Lance une exception, en laissant au pointeur // automatique le soin de dtruire lobjet allou : throw 2; } int main(void) { try { f(); } catch (...) { } return 0; }

Note : On prendra bien garde au fait que la copie dun auto_ptr dans un autre effectue un transfert de proprit. Cela peut provoquer des surprises, notamment si lon utilise des paramtres de fonctions de type auto_ptr (chose expressment dconseille). En effet, il y aura systmatiquement transfert de proprit de lobjet lors de lappel de la fonction, et cest donc la fonction appele qui en aura la responsabilit. Si elle ne fait aucun traitement spcial, lobjet sera dtruit avec le paramtre de la fonction, lorsque lexcution du programme en sortira ! Inutile de dire que la fonction appelante risque davoir des petits problmes... Pour viter ce genre de problmes, il est plutt conseill de passer les auto_ptr par rfrence constante plutt que par valeur dans les appels de fonctions. Un autre pige classique est dinitialiser un auto_ptr avec ladresse dun objet qui na pas t allou dynamiquement. Il est facile de faire cette confusion, car on ne peut a priori pas dire si un pointeur pointe sur un objet allou dynamiquement ou non. Quoi quil en soit, si vous faites cette erreur, un appel delete sera fait avec un paramtre incorrect lors de la destruction du pointeur automatique et le programme plantera. Enn, sachez que les pointeurs automatiques nutilisent que loprateur delete pour dtruire les objets quils encapsulent, jamais loprateur delete[]. Par consquent, les pointeurs automatiques ne devront jamais tre initialiss avec des pointeurs obtenus lors dune allocation dynamique avec loprateur new[] ou avec la fonction malloc de la bibliothque C.

Il est possible de rcuprer la valeur du pointeur pris en charge par un pointeur automatique simplement, grce la mthode get. Cela permet de travailler avec le pointeur original, cependant, il ne faut jamais oublier que cest le pointeur automatique qui en a toujours la proprit. Il ne faut donc jamais appeler delete sur le pointeur obtenu. En revanche, si lon veut sortir le pointeur dun auto_ptr, et forcer celui-ci en abandonner la proprit, on peut utiliser la mthode release. Cette mthode renvoie elle-aussi le pointeur sur lobjet que lauto_ptr contenait, mais libre galement la rfrence sur lobjet point au sein de lauto_ptr. Ainsi, la destruction du pointeur automatique ne provoquera plus la destruction de lobjet point et il faudra nouveau prendre en charge cette destruction soi-mme.

322

Chapitre 14. Les types complmentaires Exemple 14-16. Sortie dun pointeur dun auto_ptr
#include <iostream> #include <memory> using namespace std; class A { public: A() { cout << "Constructeur" << endl; } ~A() { cout << "Destructeur" << endl; } }; A *f(void) { cout << "Construcion de lobjet" << endl; auto_ptr<A> p(new A); cout << "Extraction du pointeur" << endl; return p.release(); } int main(void) { A *pA = f(); cout << "Destruction de lobjet" << endl; delete pA; return 0; }

14.2.2. Les paires


Outre les pointeurs automatiques, la bibliothque standard C++ dnit une autre classe utilitaire qui permet quant elle de stocker un couple de valeurs dans un mme objet. Cette classe, la classe template pair, est en particulier trs utilise dans limplmentation de certains conteneurs de la bibliothque. La dclaration de la classe template pair est la suivante dans len-tte utility :
template <class T1, class T2> struct pair { typedef T1 first_type; typedef T2 second_type; T1 first; T2 second; pair();

323

Chapitre 14. Les types complmentaires


pair(const T1 &, const T2 &); template <class U1, class U2> pair(const pair<U1, U2> &); }; template <class T1, class T2> bool operator==(const pair<T1, T2> &, const pair<T1, T2> &); template <class T1, class T2> bool operator<(const pair<T1, T2> &, const pair<T1, T2> &); template <class T1, class T2> pair<T1, T2> make_pair(const T1 &, const T2 &);

Comme cette dclaration le montre, lutilisation de la classe pair est extrmement simple. La construction dune paire se fait soit en fournissant le couple de valeurs devant tre stock dans la paire, soit en appelant la fonction make_pair. La rcupration des deux composantes dune paire se fait simplement en accdant aux donnes membres publiques first et second . Exemple 14-17. Utilisation des paires
#include <iostream> #include <utility> using namespace std; int main(void) { // Construit une paire associant un entier // un flottant : pair<int, double> p1(5, 7.38), p2; // Initialise p2 avec make_pair : p2 = make_pair(9, 3.14); // Affiche les deux paires : cout << "p1 = (" << p1.first << ", " << p1.second << ")" << endl; cout << "p2 = (" << p2.first << ", " << p2.second << ")" << endl; return 0; }

La classe template pair dispose galement doprateurs de comparaison qui utilisent lordre lexicographique induit par les valeurs de ses deux lments. Deux paires sont donc gales si et seulement si leurs couples de valeurs sont gaux membre membre, et une paire est infrieure lautre si la premire valeur de la premire paire est infrieure la valeur correspondante de la deuxime paire, ou, si elles sont gales, la deuxime valeur de la premire paire est infrieure la deuxime valeur de la deuxime paire.

324

Chapitre 14. Les types complmentaires

14.3. Les types numriques


En plus des types dintrt gnral vus dans les sections prcdentes, la bibliothque standard fournit des types de donnes plus spcialiss dans les calculs numriques ou mathmatiques. Ces types de donnes permettent deffectuer des calculs sur les nombres complexes, ainsi que des calculs parallles sur des tableaux de valeurs.

14.3.1. Les complexes


Les types de base du langage C++ fournissent une approximation relativement able des diffrents domaines de nombres mathmatiques. Par exemple, le type int permet de reprsenter une plage de valeurs limite des entiers relatifs, mais sufsamment large toutefois pour permettre deffectuer la plupart des calculs intervenant dans la vie relle. De mme, les types des nombres virgule ottante fournissent une approximation relativement satisfaisante des nombres rels des mathmatiques. Lapproximation cette fois porte non seulement sur la plage de valeur accessible, mais galement sur la prcision des nombres.
Note : On prendra bien conscience du fait que les types du langage ne reprsentent effectivement que des approximations, car les ordinateurs sont des machines limites en mmoire et en capacit de reprsentation du monde rel. Il faut donc toujours penser aux ventuels cas de dbordements et erreurs de reprsentation des nombres, surtout en ce qui concerne les nombres rels. Les bogues les plus graves (en terme de pertes matrielles ou humaines) sont souvent ds de tels dbordements, qui sont inhrents aux techniques utilises par linformatique (mme avec des langages plus srs que le C++).

Il existe en mathmatiques un autre type de nombres, qui nont pas de reprsentation physique immdiate pour le commun des mortels, mais qui permettent souvent de simplier beaucoup certains calculs : les nombres complexes. Ces nombres tendent en effet le domaine des nombres accessibles et permettent de poursuivre les calculs qui ntaient pas ralisables avec les nombres rels seulement, en saffranchissant des contraintes imposes sur les solutions des quations algbriques. Les nombres complexes sont donc dune trs grande utilit dans toute lalgbre, et en particulier dans les calculs matriciels o ils prennent une place prdominante. Les nombres complexes permettent galement de simplier srieusement les calculs trigonomtriques, les calculs de signaux en lectricit et les calculs en mcanique quantique. Le plus intressant avec ces nombres est sans doute le fait que mme si les rsultats intermdiaires que lon trouve avec eux nont pas de signication relle, les rsultats naux, eux, peuvent en avoir une et nauraient pas t trouvs aussi facilement en conservant toutes les contraintes imposes par les nombres rels. An de simplier la vie des programmeurs qui ont besoin de manipuler des nombres complexes, la bibliothque standard C++ dnit la classe template complex, qui permet de les reprsenter et deffectuer les principales oprations mathmatiques dessus. Si lutilisation de la classe complex en soi ne pose aucun problme particulier, il peut tre utile de donner une description sommaire de ce quest un nombre complexe pour les nophytes en mathmatiques. Toutefois, cette description nest pas destine aux personnes nayant aucune connaissance en mathmatiques (si tant est quun programmeur puisse tre dans ce cas...). Si vous ne la comprenez pas, cest sans doute que vous navez aucunement besoin des nombres complexes et vous pouvez donc passer cette section sans crainte.

325

Chapitre 14. Les types complmentaires

14.3.1.1. Dnition et principales proprits des nombres complexes


Il nest pas compliqu de se reprsenter ce que signie un nombre rel puisquon les utilise couramment dans la vie courante. La mthode la plus simple est dimaginer une rgle gradue o chaque position est donne par un nombre rel par rapport lorigine. Ce nombre indique le nombre de fois que lunit de distance doit tre rpte depuis lorigine pour arriver cette position. Pour se reprsenter la valeur dun nombre complexe, il faut utiliser une dimension supplmentaire. En fait, tout nombre complexe peut tre exprim avec deux valeurs relles : la partie relle du complexe, et sa partie imaginaire. Plusieurs notations existent pour reprsenter les nombres complexes partir de ces deux parties. La plus courante est de donner la partie relle et la partie imaginaire entre parenthses, spares par une virgule :
(relle, imaginaire)

o relle est la valeur de la partie relle, et imaginaire la valeur de la partie imaginaire. Il est galement trs courant en France de noter les deux parties directement en les sparant dun signe daddition et en accolant le caractre i (pour imaginaire ) la partie imaginaire :
relle + imaginaire i

Lexemple suivant vous prsente quelques nombres complexes :


7.56 3i 5+7i

Vous constaterez que les nombres rels peuvent parfaitement tre reprsents par les nombres complexes, puisquil suft simplement dutiliser une partie imaginaire nulle. Les oprations algbriques classiques ont t dnies sur les nombres complexes. Les additions et soustractions se font membre membre, partie relle avec partie relle et partie imaginaire avec partie imaginaire. En revanche, la multiplication est un peu plus complexe, car elle se base sur la proprit fondamentale que le carr de lunit de la partie imaginaire vaut -1. Autrement dit, le symbole i de la notation prcdente dispose de la proprit fondamentale suivante : i2 =-1. Il sagit en quelque sorte dune racine carre de -1 (la racine carre des nombres ngatifs nayant pas de sens, puisquun carr est normalement toujours positif, on comprend la qualication d imaginaire des nombres complexes). partir de cette rgle de base, et en conservant les rgles dassociativit des oprateurs, on peut dnir le produit de deux nombres complexes comme suit :
(a,b) * (c,d) = (ac - bd, ad + bc)

Enn, la division se dnit toujours comme lopration inverse de la multiplication, cest--dire lopration qui trouve le nombre qui, multipli par le diviseur, redonne le dividende. Chaque nombre complexe non nul dispose dun inverse, qui est le rsultat de la division de 1 par ce nombre. On peut montrer facilement que linverse dun nombre complexe est dni comme suit :
1/(a,b) = (a / (a2 + b2 ), -b / (a2 + b2 ))

partir de linverse, il est simple de calculer une division quelconque. Comme il la t dit plus haut, les nombres complexes peuvent tre reprsents en utilisant une dimension supplmentaire. Ainsi, si on dnit un repre dans le plan, dont laxe des abscisses est associ la partie relle des nombres complexes et laxe des ordonnes la partie imaginaire, tout nombre complexe est associ un point du plan. On appelle alors ce plan le plan complexe. La dnition des complexes donne ici correspond donc un systme de coordonnes cartsiennes du plan complexe, et chaque nombre complexe dispose de ses propres coordonnes.

326

Chapitre 14. Les types complmentaires En mathmatiques, il est galement courant dutiliser un autre systme de coordonnes : le systme de coordonnes polaires. Dans ce systme, chaque point du plan est identi non plus par les coordonnes de ses projections orthogonales sur les axes du repre, mais par sa distance lorigine et par langle que la droite qui rejoint lorigine au point fait avec laxe des abscisses. Ces deux nombres sont couramment nots respectivement avec les lettres grecques rho et theta. La dnomination de coordonnes polaires provient du fait que lorigine du repre joue le rle dun ple par rapport auquel on situe le point dans le plan. Il est donc vident que les nombres complexes peuvent galement tre reprsents par leurs coordonnes polaires. On appelle gnralement la distance lorigine la norme du nombre complexe, et langle quil fait avec laxe des abscisses son argument. Faites bien attention ce terme, il ne reprsente pas un argument dune fonction ou quoi que ce soit qui se rapporte la programmation. La plupart des fonctions mathmatiques classiques ont t dnies sur les nombres complexes, parfois en restreignant leur domaine de validit. Ainsi, il est possible de calculer un sinus, un cosinus, une exponentielle, etc. pour les nombres complexes. Il est bien entendu hors de question de dnir rigoureusement, ni mme de prsenter succinctement ces fonctions dans ce document. Cependant, il est bon de savoir quon ne peut pas dnir une relation dordre sur les nombres complexes. Autrement dit, on ne peut pas faire dautre comparaison que lgalit entre deux nombres complexes (essayez de comparer les nombres complexes situs sur un cercle centr lorigine dans le plan complexe pour vous en rendre compte).

14.3.1.2. La classe complex


La classe template complex est dnie dans len-tte complex de la bibliothque standard. Cette classe peut tre instancie pour lun quelconque des trois types de nombre virgule ottante du langage : oat, double ou long double. Elle permet deffectuer les principales oprations dnies sur les nombres complexes, comme les additions, soustractions, multiplications, division, mais galement des oprations spciques aux nombres complexes, comme la dtermination de leur argument ou de leur norme. Enn, len-tte complex contient des surcharges des fonctions mathmatiques standards, telles que les fonctions trigonomtriques, la racine carre, les puissances et exponentielles, ainsi que les logarithmes (dnis sur le plan complexe auquel laxe des abscisses ngatives a t t). La construction dun complexe ne pose aucun problme en soi. La classe complex dispose dun constructeur par dfaut, dun constructeur de copie et dun constructeur prenant en paramtre la partie relle et la partie imaginaire du nombre :
#include <iostream> #include <complex> using namespace std; int main(void) { complex<double> c(2,3); cout << c << endl; return 0; }

Lexemple prcdent prsente galement loprateur de sortie sur les ux standards, qui formate un nombre complexe en utilisant la notation (rel,imaginaire). Il existe galement une surcharge de loprateur dentre pour le ux dentre :

327

Chapitre 14. Les types complmentaires


#include <iostream> #include <complex> using namespace std; int main(void) { complex<double> c; cin >> c; cout << "Vous avez saisi : " << c << endl; return 0; }

Note : Malheureusement, cette notation pose des problmes avec la locale franaise, puisque nous utilisons des virgules pour sparer la partie entire de la partie dcimale des nombres virgules. Lorsque lun des deux nombres ottants est un entier, il est impossible de dterminer o se trouve la virgule sparant la partie entire de la partie imaginaire du nombre complexe. Une premire solution est de modier le formatage des nombres rels pour que les chiffres aprs la virgule soient toujours afchs, mme sils sont nuls. Cependant, il faut galement imposer que les saisies des nombres soient galement toujours effectus avec des nombres virgules, ce qui est sujet erreur et invriable. Il est donc recommand de nutiliser que la locale de la bibliothque C lorsquon fait un programme utilisant les nombres complexes.

Il nexiste pas de constructeur permettant de crer un nombre complexe partir de ses coordonnes polaires. En revanche, la fonction polar permet den construire un. Cette fonction prend en paramtre la norme du complexe construire ainsi que son argument. Elle renvoie le nombre complexe nouvellement construit. La partie imaginaire et la partie relle dun nombre complexe peuvent tre rcupres tout instant laide des mthodes real et imag de la classe template complex. Il est galement possible dutiliser les fonctions template real et imag, qui prennent toutes deux le nombre complexe dont il faut calculer la partie relle et la partie imaginaire. De mme, la norme dun nombre complexe est retourne par la fonction abs, et son argument peut tre obtenu avec la fonction arg. Bien entendu, les oprations classiques sur les complexes se font directement, comme sil sagissait dun type prdni du langage :
#include <iostream> #include <complex> using namespace std; int main(void) { complex<double> c1(2.23, 3.56); complex<double> c2(5, 5); complex<double> c = c1+c2; c = c/(c1-c2); cout << c << endl; return 0; }

328

Chapitre 14. Les types complmentaires Les fonctions spciques permettant de manipuler les complexes et de leur appliquer les oprations qui leurs sont propres sont rcapitules dans le tableau suivant : Tableau 14-2. Fonctions spciques aux complexes Fonction
real imag abs arg norm

Description Retourne la partie relle du nombre complexe. Retourne la partie imaginaire du nombre complexe. Retourne la norme du nombre nombre complexe, cest--dire sa distance lorigine. Retourne largument du nombre complexe. Retourne le carr de la norme du nombre complexe. Attention, cette fonction porte mal son nom, puisque la vraie norme est retourne par la surcharge de la fonction abs pour les nombres complexes. Cette incohrence provient de linterprtation diffrente de celle des Franais que font les Anglo-saxons de la notion de norme. Retourne le nombre complexe conjugu du nombre complexe fourni en argument. Le nombre conjugu dun nombre complexe est son symtrique par rapport laxe des abscisses dans le plan complexe, cest--dire quil dispose de la mme partie relle, mais que sa partie imaginaire est oppose celle du nombre complexe original (cela revient galement dire que largument du conjugu est loppos de largument du complexe original). Le produit dun nombre complexe et de son conjugu donne le carr de sa norme. Permet de construire un nombre complexe partir de ses coordonnes polaires.

conj

polar

Exemple 14-18. Manipulation des nombres complexes


#include <iostream> #include <complex> using namespace std; int main(void) { // Cre un nombre complexe : complex<double> c(2,3); // Dtermine son argument et sa norme : double Arg = arg(c); double Norm = abs(c); // Construit le nombre complexe conjugu : complex<double> co = polar(Norm, -Arg); // Affiche le carr de la norme du conjugu : cout << norm(co) << endl; // Calcule le carr ce cette norme par le produit // du complexe et de son conjugu : cout << real(c * conj(c)) << endl; return 0; }

329

Chapitre 14. Les types complmentaires

14.3.2. Les tableaux de valeurs


Comme il la t expliqu dans le Chapitre 1, les programmes classiques fonctionnent toujours sur le mme principe : ils travaillent sur des donnes quils reoivent en entre et produisent des rsultats en sortie. Ce mode de fonctionnement convient dans la grande majorit des cas, et en fait les programmes que lon appelle couramment les ltres en sont une des applications principales. Un ltre nest rien dautre quun programme permettant, comme son nom lindique, de ltrer les donnes reues en entre selon un critre particulier et de ne fournir en sortie que les donnes qui satisfont ce critre. Certains ltres plus volus peuvent mme modier les donnes la vole ou les traduire dans un autre format. Les ltres sont trs souvent utiliss avec les mcanismes de redirection des systmes qui les supportent an dexcuter des traitements complexes sur les ux de donnes partir de ltres simples, en injectant les rsultats des uns dans le ux dentre des autres. Cependant, ce modle a une limite pratique en terme de performances, car il ncessite un traitement squentiel des donnes. La vitesse dexcution dun programme conu selon ce modle est donc directement li la vitesse dexcution des instructions, donc la vitesse du processeur de la machine utilise. Lorsquun haut niveau de performance doit tre atteint, plusieurs solutions sont disponibles. Dans la pratique, on distingue trois solutions classiques. La premire solution consiste simplement, pour augmenter la puissance dune machine, augmenter celle du processeur. Cela se traduit souvent par une augmentation de la frquence de ce processeur, technique que tout le monde connat. Les avantages de cette solution sont vidents : tous les programmes bncient directement de laugmentation de la puissance du processeur et nont pas tre modis. En revanche, cette technique atteindra un jour ou un autre ses limites en termes de cots de fabrication et de moyens techniques mettre en uvre pour produire les processeurs. La deuxime solution est daugmenter le nombre de processeurs de la machine. Cette solution est trs simple, mais suppose que les programmes soient capables deffectuer plusieurs calculs indpendants simultanment. En particulier, les traitements effectuer doivent tre sufsamment indpendants et ne pas avoir attendre les donnes produites par les autres an de pouvoir rellement tre excuts en parallle. On quitte donc le modle squentiel, pour entrer dans un modle de traitement o chaque processeur travaille en parallle (modle MIMD , abrviation de langlais Multiple Instruction Multiple Data ). Cette technique est galement souvent appele le paralllisme de traitement. Malheureusement, pour un unique processus purement squentiel, cette technique ne convient pas, puisque de toutes faons, les oprations excuter ne le seront que par un seul processeur. Enn, il existe une technique mixte, qui consiste parallliser les donnes. Les mmes oprations dun programme squentiel sont alors excutes sur un grand nombre de donnes similaires. Les donnes sont donc traites par blocs, par un unique algorithme : il sagit du paralllisme de donnes ( SIMD en anglais, abrviation de Single Instruction Multiple Data ). Cette solution est celle mise en uvre dans les processeurs modernes qui disposent de jeux dinstructions spcialises permettant deffectuer des calculs sur plusieurs donnes simultanment (MMX, 3DNow et SSE pour les processeurs de type x86 par exemple). Bien entendu, cette technique suppose que le programme ait effectivement traiter des donnes semblables de manire similaire. Cette contrainte peut paratre trs forte, mais, en pratique, les situations les plus consommatrices de ressources sont justement celles qui ncessite la rptition dun mme calcul sur plusieurs donnes. On citera par exemple tous les algorithmes de traitement de donnes multimdia, dont les algorithmes de compression, de transformation et de combinaison. Si laugmentation des performances des processeurs apporte un gain directement observable sur tous les programmes, ce nest pas le cas pour les techniques de paralllisation. Le paralllisme de traitement est gnralement accessible au niveau systme, par lintermdiaire du multitche et de la programmation multithreade. Il faut donc crire les programmes de telle sorte bncier de ce paralllisme de traitement, laide des fonctions spcique au systme dexploitation. De mme, le paralllisme de donnes ncessite la dnition de types de donnes complexes, capables de reprsen-

330

Chapitre 14. Les types complmentaires ter les blocs de donnes sur lesquels le programme doit travailler. Ces blocs de donnes sont couramment grs comme des vecteurs ou des matrices, cest--dire, en gnral, comme des tableaux de nombres. Le programme doit donc utiliser ces types spciques pour accder toutes les ressources de la machine. Cela ncessite un support de la part du langage de programmation. Chaque environnement de dveloppement est susceptible de fournir les types de donnes permettant deffectuer des traitements SIMD. Cependant, ces types dpendent de lenvironnement utilis et encore plus de la plateforme utilise. La bibliothque standard C++ permet dviter ces cueils, car elle dnit un type de donne permettant de traiter des tableaux unidimensionnels dobjets, en assurant que les mcanismes doptimisation propre aux plates-formes matrielles et aux compilateurs seront effectivement utiliss : les valarray.

14.3.2.1. Fonctionnalits de base des valarray


La classe valarray est une classe template capable de stocker un tableau de valeurs de son type template. Il est possible de linstancier pour tous les types de donnes pour lesquels les oprations dnies sur la classe valarray sont elles-mmes dnies. La bibliothque standard C++ garantit que la classe valarray est crite de telle sorte que tous les mcanismes doptimisation des compilateurs pourront tre appliqus sur elle, an dobtenir des performances optimales. De plus, chaque implmentation est libre dutiliser les possibilits de calcul parallle disponible sur chaque plateforme, du moins pour les types pour lesquels ces fonctionnalits sont prsentes. Par exemple, la classe valarray instancie pour le type oat peut utiliser les instructions spciques de calcul sur les nombres ottants du processeur si elles sont disponibles. Toutefois, la norme nimpose aucune contrainte ce niveau, et la manire dont la classe valarray est implmente reste la discrtion de chaque fournisseur. La classe valarray fournit toutes les fonctionnalits ncessaires la construction des tableaux de valeurs, leur initialisation, ainsi qu leur manipulation. Elle est dclare comme suit dans len-tte valarray :
// Dclaration des classes de slection de sous-tableau : class slice; class gslice; // Dclaration de la classe valarray : template <class T> class valarray { public: // Types des donnes : typedef T value_type; // Constructeurs et destructeurs : valarray(); explicit valarray(size_t taille); valarray(const T &valeur, size_t taille); valarray(const T *tableau, size_t taille); valarray(const valarray &source); valarray(const mask_array<T> &source); valarray(const indirect_array<T> &source); valarray(const slice_array<T> &source); valarray(const gslice_array<T> &source); ~valarray(); // Oprateurs daffectation : valarray<T> &operator=(const T &valeur); valarray<T> &operator=(const valarray<T> &source);

331

Chapitre 14. Les types complmentaires


valarray<T> valarray<T> valarray<T> valarray<T> &operator=(const &operator=(const &operator=(const &operator=(const mask_array<T> &source); indirect_array<T> &source); slice_array<T> &source); gslice_array<T> &source);

// Oprateurs daccs aux lments : T operator[](size_t indice) const; T &operator[](size_t indice); // Oprateurs de slection de sous-ensemble du tableau : valarray<T> operator[](const valarray<bool> &masque) const; mask_array<T> operator[](const valarray<bool> &masque); valarray<T> operator[](const valarray<size_t> &indices) const; indirect_array<T> operator[](const valarray<size_t> &indices); valarray<T> operator[](slice selecteur) const; slice_array<T> operator[](slice selecteur); valarray<T> operator[](const gslice &selecteur) const; gslice_array<T> operator[](const gslice &selecteur); // Oprateurs unaires : valarray<T> operator+() valarray<T> operator-() valarray<T> operator~() valarray<T> operator!()

const; const; const; const;

// Oprateurs daffectation compose : valarray<T> &operator*=(const T &valeur); valarray<T> &operator*=(const valarray<T> &tableau); valarray<T> &operator/=(const T &valeur); valarray<T> &operator/=(const valarray<T> &tableau); valarray<T> &operator%=(const T &valeur); valarray<T> &operator%=(const valarray<T> &tableau); valarray<T> &operator+=(const T &valeur); valarray<T> &operator+=(const valarray<T> &tableau); valarray<T> &operator-=(const T &valeur); valarray<T> &operator-=(const valarray<T> &tableau); valarray<T> &operator^=(const T &valeur); valarray<T> &operator^=(const valarray<T> &tableau); valarray<T> &operator&=(const T &valeur); valarray<T> &operator&=(const valarray<T> &tableau); valarray<T> &operator|=(const T &valeur); valarray<T> &operator|=(const valarray<T> &tableau); valarray<T> &operator<<=(const T &valeur); valarray<T> &operator<<=(const valarray<T> &tableau); valarray<T> &operator>>=(const T &valeur); valarray<T> &operator>>=(const valarray<T> &tableau); // Oprations spcifiques : size_t size() const; T sum() const; T min() const; T max() const; valarray<T> shift(int) const; valarray<T> cshift(int) const; valarray<T> apply(T fonction(T)) const; valarray<T> apply(T fonction(const T &)) const; void resize(size_t taille, T initial=T());

332

Chapitre 14. Les types complmentaires


};

Nous verrons dans la section suivante la signication des types slice, gslice, slice_array, gslice_array, mask_array et indirect_array. Il existe plusieurs constructeurs permettant de crer et dinitialiser un tableau de valeurs. Le constructeur par dfaut initialise un tableau de valeur vide. Les autres constructeurs permettent dinitialiser le tableau de valeur partir dune valeur dinitialisation pour tous les lments du valarray, ou dun autre tableau contenant les donnes affecter aux lments du valarray :
// Construit un valarray de doubles : valarray<double> v1; // Initialise un valarray de doubles explicitement : double valeurs[] = {1.2, 3.14, 2.78, 1.414, 1.732}; valarray<double> v2(valeurs, sizeof(valeurs) / sizeof(double)); // Construit un valarray de 10 entiers initialiss 3 : valarray<int> v3(3, 10);

Vous pouvez constater que le deuxime argument des constructeurs qui permettent dinitialiser les valarray prennent un argument de type size_t, qui indique la taille du valarray. Une fois un valarray construit, il est possible de le redimensionner laide de la mthode resize. Cette mthode prend en premier paramtre la nouvelle taille du valarray et la valeur utiliser pour rinitialiser tous les lments du valarray aprs redimensionnement. La valeur par dfaut est celle fournie par le constructeur par dfaut du type des donnes contenues dans le valarray. La taille courante dun valarray peut tre rcupre tout moment grce la mthode size. Exemple 14-19. Modication de la taille dun valarray
#include <iostream> #include <valarray> using namespace std; int main(void) { // Cration dun valarray : valarray<double> v; cout << v.size() << endl; // Redimensionnement du valarray : v.resize(5, 3.14); cout << v.size() << endl; return 0; }

Toutes les oprations classiques des mathmatiques peuvent tre appliques sur un valarray pourvu quelles puissent ltre galement sur le type des donnes contenues par ce tableau. La dnition de ces oprations est trs simple : lopration du type de base est applique simplement chaque lment contenu dans le tableau de valeurs.

333

Chapitre 14. Les types complmentaires La bibliothque standard dnit galement les oprateurs binaires ncessaires pour effectuer les oprations binaires sur chaque lment des valarray. En fait, ces oprateurs sont classs en deux catgories, selon la nature de leurs arguments. Les oprateurs de la premire catgorie permettent deffectuer une opration entre deux valarray de mme dimension, en appliquant cette opration membre membre. Il sagit donc rellement dune opration vectorielle dans ce cas. En revanche, les oprateurs de la deuxime catgorie appliquent lopration avec une mme et unique valeur pour chaque donne stocke dans le valarray. Exemple 14-20. Oprations sur les valarray
#include <iostream> #include <valarray> using namespace std; void affiche(const valarray<double> &v) { size_t i; for (i=0; i<v.size(); ++i) cout << v[i] << " "; cout << endl; } int main(void) { // Construit deux valarray de doubles : double v1[] = {1.1, 2.2, 3.3}; double v2[] = {5.3, 4.4, 3.5}; valarray<double> vect1(v1, 3); valarray<double> vect2(v2, 3); valarray<double> res(3); // Effectue une somme membre membre : res = vect1 + vect2; affiche(res); // Calcule le sinus des membres du premier valarray : res = sin(vect1); affiche(res); return 0; }

Parmi les oprateurs binaires que lon peut appliquer un valarray, on trouve bien entendu les oprateurs de comparaison. Ces oprateurs, contrairement aux oprateurs de comparaison habituels, ne renvoient pas un boolen, mais plutt un autre tableau de boolens. En effet, la comparaison de deux valarray a pour rsultat le valarray des rsultats des comparaisons membres membres des deux valarray. La classe valarray dispose de mthodes permettant deffectuer diverses oprations spciques aux tableaux de valeurs. La mthode sum permet dobtenir la somme de toutes les valeurs stockes dans le tableau de valeur. Les mthodes shift et cshift permettent, quant elles, de construire un nouveau valarray dont les lments sont les lments du valarray auquel la mthode est applique, dcals ou permuts circulairement dun certain nombre de positions. Le nombre de dplacements effectus est pass en paramtre ces deux fonctions, les valeurs positives entranant des dplacements vers la gauche et les valeurs ngatives des dplacements vers la droite. Dans le cas des dcalages les nouveaux lments introduits pour remplacer ceux qui nont pas eux-mmes de remplaant prennent la valeur spcie par le constructeur par dfaut du type utilis.

334

Chapitre 14. Les types complmentaires Exemple 14-21. Dcalages et rotations de valeurs
#include <iostream> #include <valarray> using namespace std; void affiche(const valarray<double> &v) { size_t i; for (i=0; i<v.size(); ++i) cout << v[i] << " "; cout << endl; } int main(void) { // Construit un valarray de doubles : double v1[] = {1.1, 2.2, 3.3, 4.4, 5.5}; valarray<double> vect1(v1, 5); valarray<double> res(5); // Effectue un dcalage gauche de deux positions : res = vect1.shift(2); affiche(res); // Effectue une rotation de 2 positions vers la droite : res = vect1.cshift(-2); affiche(res); return 0; }

Enn, il existe deux mthodes apply permettant dappliquer une fonction chaque lment dun valarray et de construire un nouveau valarray de mme taille et contenant les rsultats. Ces deux surcharges peuvent travailler respectivement avec des fonctions prenant en paramtre soit par valeur, soit par rfrence, lobjet sur lequel elles doivent tre appliques.

14.3.2.2. Slection multiple des lments dun valarray


Les lments dun valarray peuvent tre accds laide de loprateur daccs aux lments de tableau []. La fonction affiche des exemples du paragraphe prcdent utilise cette fonctionnalit pour en rcuprer la valeur. Cependant, les valarray dispose de mcanismes plus sophistiqus pour manipuler les lments des tableaux de valeur en groupe, an de bncier de tous les mcanismes doptimisation qui peuvent exister sur une plateforme donne. Grce ces mcanismes, il est possible deffectuer des oprations sur des parties seulement dun valarray ou dcrire de nouvelles valeurs dans certains de ses lments seulement. Pour effectuer ces slections multiples, plusieurs techniques sont disponibles. Cependant, toutes ces techniques se basent sur le mme principe, puisquelles permettent de ltrer les lments du valarray pour nen slectionner quune partie seulement. Le rsultat de ce ltrage peut tre un nouveau valarray ou une autre classe pouvant tre manipule exactement de la mme manire quun valarray. En pratique, il existe quatre manires de slectionner des lments dans un tableau. Nous allons les dtailler dans les sections suivantes.

335

Chapitre 14. Les types complmentaires 14.3.2.2.1. Slection par un masque La manire la plus simple est dutiliser un masque de boolens indiquant quels lments doivent tre slectionns ou non. Le masque de boolens doit obligatoirement tre un valarray de mme dimension que le valarray contenant les lments slectionner. Chaque lment est donc slectionn en fonction de la valeur du boolen correspondant dans le masque. Une fois le masque construit, la slection des lments peut tre ralise simplement en fournissant ce masque loprateur [] du valarray contenant les lments slectionner. La valeur retourne par cet oprateur est alors une instance de la classe template mask_array, par lintermdiaire de laquelle les lments slectionns peuvent tre manipuls. Pour les valarray constants cependant, la valeur retourne est un autre valarray, contenant une copie des lments slectionns. La classe mask_array fournit un nombre limit doprations. En fait, ses instances ne doivent tre utilises que pour effectuer des oprations simples sur les lments du tableau slectionn par le masque fourni loprateur []. Les oprations ralisables seront dcrites dans la Section 14.3.2.2.4. La slection des lments dun tableau par lintermdiaire dun masque est utilise couramment avec les oprateurs de comparaison des valarray, puisque ceux-ci renvoient justement un tel masque. Il est donc trs facile deffectuer des oprations sur les lments dun valarray qui vrient une certaine condition. Exemple 14-22. Slection des lments dun valarray par un masque
#include <iostream> #include <valarray> using namespace std; void affiche(const valarray<int> &v) { size_t i; for (i=0; i<v.size(); ++i) cout << v[i] << " "; cout << endl; } int main(void) { // Construit un valarray dentier : int valeurs[] = { 1, 5, 9, 4, 3, 7, 21, 32 }; valarray<int> vi(valeurs, sizeof(valeurs) / sizeof(int)); affiche(vi); // Multiplie par 2 tous les multiples de 3 : vi[(vi % 3)==0] *= valarray<int>(2, vi.size()); affiche(vi); return 0; }

14.3.2.2.2. Slection par indexation explicite La slection des lments dun valarray par un masque de boolens est explicite et facile utiliser, mais elle souffre de plusieurs dfauts. Premirement, il faut fournir un tableau de boolen de mme dimension que le valarray source. Autrement dit, il faut fournir une valeur boolenne pour tous les

336

Chapitre 14. Les types complmentaires lments du tableau, mme pour ceux qui ne nous intressent pas. Ensuite, les lments slectionns apparaissent systmatiquement dans le mme ordre que celui quils ont dans le valarray source. La bibliothque standard C++ fournit donc un autre mcanisme de slection, toujours explicite, mais qui permet de faire une rindexation des lments ainsi slectionns. Cette fois, il ne faut plus fournir un masque loprateur [], mais un valarray contenant directement les indices des lments slectionns. Ces indices peuvent ne pas tre dans lordre croissant, ce qui permet donc de rarranger lordre des lments ainsi slectionns. Exemple 14-23. Slection des lments dun valarray par indexation
#include <iostream> #include <valarray> using namespace std; void affiche(const valarray<int> &v) { size_t i; for (i=0; i<v.size(); ++i) cout << v[i] << " "; cout << endl; } int main(void) { // Construit un valarray dentier : int valeurs[] = { 1, 5, 9, 4, 3, 7, 21, 32 }; valarray<int> vi(valeurs, sizeof(valeurs) / sizeof(int)); affiche(vi); // Multiplie par 2 les lments dindices 2, 5 et 7 : size_t indices[] = {2, 5, 7}; valarray<size_t> ind(indices, sizeof(indices) / sizeof(size_t)); vi[ind] *= valarray<int>(2, ind.size()); affiche(vi); return 0; }

La valeur retourne par loprateur de slection sur les valarray non constants est cette fois du type indirect_array. Comme pour la classe mask_array, les oprations ralisables par lintermdiaire de cette classe sont limites et doivent servir uniquement modier les lments slectionns dans le valarray source.

14.3.2.2.3. Slection par indexation implicite Dans beaucoup de situations, les indices des lments slectionns suivent un motif rgulier et il nest pas toujours pratique de spcier ce motif explicitement. La mthode de slection prcdente nest dans ce cas pas trs pratique et il est alors prfrable de slectionner les lments par un jeu dindices dcrits de manire implicite. La bibliothque fournit cet effet deux classes utilitaires permettant de dcrire des jeux dindices plus ou moins complexes : la classe slice et la classe gslice. Ces deux classes dnissent les indices des lments slectionner laide de plusieurs variables pouvant prendre un certain nombre de valeurs espaces par un pas dincrmentation xe. La dnition des indices consiste donc simplement donner la valeur de dpart de lindice de slection, le nombre

337

Chapitre 14. Les types complmentaires de valeurs gnrer pour chaque variable et le pas qui spare ces valeurs. Les variables de contrle commencent toutes leur itration partir de la valeur nulle et prennent comme valeurs successives les multiples du pas quelles utilisent.
Note : En ralit, la classe slice est un cas particulier de la classe gslice qui nutilise quune seule variable de contrle pour dnir les indices. Les slice ne sont donc rien dautre que des gslice unidimensionnels. Le terme de gslice provient de langlais Generalized Slice , qui signie bien que les gslice sont des slice tendues plusieurs dimensions.

La classe slice est relativement facile utiliser, puisquil suft de spcier la valeur de dpart de lindice, le nombre de valeurs gnrer et le pas qui doit les sparer. Elle est dclare comme suit dans len-tte valarray :
class slice { public: slice(); slice(size_t debut, size_t nombre, size_t pas); // Accesseurs : size_t start() const; size_t size() const; size_t stride() const; };

Exemple 14-24. Slection par indexation implicite


#include <iostream> #include <valarray> using namespace std; void affiche(const valarray<int> &v) { size_t i; for (i=0; i<v.size(); ++i) cout << v[i] << " "; cout << endl; } int main(void) { // Construit un valarray dentier : int valeurs[] = { 1, 5, 9, 4, 3, 7, 21, 32 }; valarray<int> vi(valeurs, 8); affiche(vi); // Multiplie par 2 un lment sur 3 partir du deuxime : slice sel(1, 3, 3); vi[sel] *= valarray<int>(2, vi.size()); affiche(vi); // Multiplie par 2 un lment sur 3 partir du deuxime : slice sel(1, 3, 3);

338

Chapitre 14. Les types complmentaires


vi[sel] *= valarray<int>(2, vi.size()); affiche(vi); return 0; }

La classe gslice est en revanche un peu plus difcile demploi puisquil faut donner le nombre de valeurs et le pas pour chaque variable de contrle. Le constructeur utilis prend donc en deuxime et troisime paramtres non plus deux valeurs de type size_t, mais deux valarray de size_t. La dclaration de la classe gslice est donc la suivante :
class gslice { public: gslice(); gslice(size_t debut, const valarray<size_t> nombres, const valarray<size_t> pas); // Accesseurs : size_t start() const; valarray<size_t> size() const; valarray<size_t> stride() const; };

Les deux valarray dterminant le nombre de valeurs des variables de contrle et leurs pas doivent bien entendu avoir la mme taille. Lordre dans lequel les indices des lments slectionns sont gnrs par la classe gslice est celui obtenu en faisant varier en premier les dernires variables caractrises par les valarray fournis lors de sa construction. Par exemple, une classe gslice utilisant trois variables prenant respectivement 2, 3 et 5 valeurs et variant respectivement par pas de 3, 1 et 2 units, en partant de lindice 2, gnrera les indices suivants :
2, 4, 6, 8, 10, 3, 5, 7, 9, 11, 4, 6, 8, 10, 12, 5, 7, 9, 11, 13, 6, 8, 10, 12, 14, 7, 9, 11, 13, 15

La variable prenant cinq valeurs et variant de deux en deux est donc celle qui volue le plus vite. Comme vous pouvez le constater avec lexemple prcdent, un mme indice peut apparatre plusieurs fois dans la srie dnie par une classe gslice. La bibliothque standard C++ neffectue aucun contrle ce niveau : il est donc du ressort du programmeur de bien faire attention ce quil fait lorsquil manipule des jeux dindices dgnrs. Comme pour les autres techniques de slection, la slection dlments dun valarray non constant par lintermdiaire des classes slice et gslice retourne une instance dune classe particulire permettant de prendre en charge les oprations de modication des lments ainsi slectionns. Pour les slections simples ralises avec la classe slice, lobjet retourn est de type slice_array. Pour les slections ralises avec la classe gslice, le type utilis est le type gslice_array.

339

Chapitre 14. Les types complmentaires 14.3.2.2.4. Oprations ralisables sur les slections multiples Comme on la vu dans les sections prcdentes, les slections multiples ralises sur des objets non constants retournent des instances des classes utilitaires mask_array, indexed_array, slice_array et gslice_array. Ces classes rfrencent les lments ainsi slectionns dans le valarray source, permettant ainsi de les manipuler en groupe. Cependant, ce ne sont pas des valarray complets et, en fait, ils ne doivent tre utiliss, de manire gnrale, que pour effectuer une opration daffectation sur les lments slectionns. Ces classes utilisent donc une interface restreinte de celle de la classe valarray, qui naccepte que les oprateurs daffectation sur les lments quelles reprsentent. Par exemple, la classe mask_array est dclare comme suit dans len-tte valarray :
template <class T> class mask_array { public: typedef T value_type; ~mask_array(); // Oprateurs daffectation et daffectation composes : void operator=(const valarray<T> &) const; void operator*=(const valarray<T> &) const; void operator/=(const valarray<T> &) const; void operator%=(const valarray<T> &) const; void operator+=(const valarray<T> &) const; void operator-=(const valarray<T> &) const; void operator^=(const valarray<T> &) const; void operator&=(const valarray<T> &) const; void operator|=(const valarray<T> &) const; void operator<<=(const valarray<T> &) const; void operator>>=(const valarray<T> &) const; void operator=(const T &valeur); };

Tous ces oprateurs permettent daffecter aux lments de la slection reprsents par cette classe les valeurs spcies par leur paramtre. En gnral, ces valeurs doivent tre fournies sous la forme dun valarray, mais il existe galement une surcharge de loprateur daffectation permettant de leur affecter tous une mme valeur.
Note : Les slections ralises sur les valarray constants ne permettent bien entendu pas de modier leurs lments. Les objets retourns par loprateur [] lors des slections multiples sur ces objets sont donc des valarray classiques contenant une copie des valeurs des lments slectionns.

14.3.3. Les champs de bits


De tous les types de donnes quun programme peut avoir besoin de stocker, les boolens sont certainement lun des plus importants. En effet, les programmes doivent souvent reprsenter des proprits qui sont soit vraies, soit fausses. Aprs tout, la base du traitement de linformation telle quil est ralis par les ordinateurs est le bit, ou chiffre binaire...

340

Chapitre 14. Les types complmentaires Il existe plusieurs manires de stocker des boolens dans un programme. La technique la plus simple est bien entendu dutiliser le type C++ natif bool, qui ne peut prendre que les valeurs true et false. Les programmes plus vieux utilisaient gnralement des entiers et des constantes prdnies ou encore une numration. Malheureusement, toutes ces techniques souffrent du gros inconvnient que chaque information est stocke dans le type sous-jacent au type utilis pour reprsenter les boolens et, dans la plupart des cas, ce type est un entier. Cela signie que pour stocker un bit, il faut rserver un mot mmoire complet. Mme en tenant compte du fait que la plupart des compilateurs C++ stockent les variables de type bool dans de simples octets, la dperdition reste dans un facteur 8. Bien entendu, cela nest pas grave si lon na que quelques bits stocker, mais si le programme doit manipuler un grand nombre dinformations boolennes, cette technique est proscrire. Nous avons vu dans la Section 3.2.5 quil est possible de dnir des champs de bits en attribuant un nombre de bits xe plusieurs identicateurs de type entier. Cette solution peut permettre dconomiser de la mmoire, mais reste malgr tout relativement limite si un grand nombre de bits doit tre manipul. An de rsoudre ce problme, la bibliothque standard C++ fournit la classe template bitset qui, comme son nom lindique, encapsule des champs de bits de tailles arbitraires. Le paramtre template est de type size_t et indique le nombre de bits que le champ de bits encapsul contient.
Note : Vous noterez que cela impose de connatre la compilation la taille du champ de bits. Cela est regrettable et limite srieusement lintrt de cette classe. Si vous devez manipuler des champs de bits de taille dynamique, vous devrez crire vous-mme une classe dencapsulation dynamique des champs de bits.

La classe bitset est dclare comme suit dans len-tte bitset :


template <size_t N> class bitset { public: class reference;

// Classe permettant de manipuler les bits.

// Les constructeurs : bitset(); bitset(unsigned long val); template<class charT, class traits, class Allocator> explicit bitset( const basic_string<charT, traits, Allocator> &chaine, typename basic_string<charT, traits, Allocator>::size_type debut = 0, typename basic_string<charT, traits, Allocator>::size_type taille = basic_string<charT, traits, Allocator>::npos); // Les fonctions de conversion : unsigned long to_ulong() const; template <class charT, class traits, class Allocator> basic_string<charT, traits, Allocator> to_string() const; // Les oprateurs de manipulation : bitset<N> &operator&=(const bitset<N> &); bitset<N> &operator|=(const bitset<N> &); bitset<N> &operator^=(const bitset<N> &); bitset<N> &operator<<=(size_t pos); bitset<N> &operator>>=(size_t pos); bitset<N> operator<<(size_t pos) const;

341

Chapitre 14. Les types complmentaires


bitset<N> operator>>(size_t pos) const; bitset<N> operator~() const; bitset<N> &set(); bitset<N> &set(size_t pos, bool val = true); bitset<N> &reset(); bitset<N> &reset(size_t pos); bitset<N> &flip(); bitset<N> &flip(size_t pos); bool test(size_t pos) const; reference operator[](size_t pos); // for b[i]; // Les oprateurs de comparaison : bool operator==(const bitset<N> &rhs) const; bool operator!=(const bitset<N> &rhs) const; // Les fonctions de test : size_t count() const; size_t size() const; bool any() const; bool none() const; };

La construction dun champ de bits ncessite de connatre le nombre de bits que ce champ doit contenir an dinstancier la classe template bitset. Les diffrents constructeurs permettent dinitialiser le champ de bits en affectant la valeur nulle tous ses bits ou en les initialisant en fonction des paramtres du constructeur. Le deuxime constructeur affectera aux premiers bits du champ de bits les bits correspondant de lentier de type unsigned long fourni en paramtre, et initialisera les autres bits du champ de bits la valeur 0 si celui-ci contient plus de bits quun unsigned long. Le troisime constructeur initialise le champ de bits partir de sa reprsentation sous forme de chane de caractres ne contenant que des 0 ou des 1. Cette reprsentation doit tre stocke dans la basic_string fournie en premier paramtre, partir de la position debut et sur une longueur de taille caractres. Cette taille peut tre infrieure la taille du champ de bits. Dans ce cas, le constructeur considrera que les bits de poids fort sont tous nuls et initialisera les premiers bits du champ avec les valeurs lues dans la chane. Notez bien que les premiers caractres de la chane de caractres reprsentent les bits de poids fort, cette chane est donc parcourue en sens inverse lors de linitialisation. Ce constructeur est susceptible de lancer une exception out_of_range si le paramtre debut est suprieur la taille de la chane ou une exception invalid_argument si lun des caractres utiliss est diffrent des caractres 0 ou 1. Comme vous pouvez le constater daprs la dclaration, la classe bitset fournit galement des mthodes permettant deffectuer les conversions inverses de celles effectues par les constructeurs. La mthode to_ulong renvoie donc un entier de type unsigned long correspondant la valeur des premiers bits du champ de bits, et la mthode template to_string renvoie une chane de caractres contenant la reprsentation du champ de bits sous la forme dune suite de caractres 0 et 1. La classe bitset fournit galement des surcharges des oprateurs operator<< et operator>> pour les ux dentre / sortie de la bibliothque standard. Exemple 14-25. Utilisation dun bitset
#include <iostream> #include <bitset> #include <string> using namespace std;

342

Chapitre 14. Les types complmentaires

int main(void) { // Construit un champ de bits : string s("100110101"); bitset<32> bs(s); // Affiche la valeur en hexadcimal de lentier associ : cout << hex << showbase << bs.to_ulong() << endl; // Affiche la valeur sous forme de chane de caractres : string t; t = bs.to_string<string::value_type, string::traits_type, string::allocator_type>(); cout << t << endl; // Utilise directement << sur le flux de sortie : cout << bs << endl; return 0; }

Note : La mthode to_string est une fonction template ne prenant pas de paramtres. Le compilateur ne peut donc pas raliser une instanciation implicite lors de son appel. Par consquent, vous devrez fournir la liste des paramtres template explicitement si vous dsirez utiliser cette mthode. Il est gnralement plus simple dcrire la valeur du bitset dans un ux standard. Les modicateurs de format de ux hex et showbase ont pour but deffectuer lafchage des entiers sous forme hexadcimale. La personnalisation des ux dentre / sortie sera dcrite en dtail dans le Chapitre 15.

Les oprateurs de manipulation des champs de bits ne posent pas de problme particulier puisquils ont la mme smantique que les oprateurs standards du langage, ceci prs quils travaillent sur lensemble des bits du champ en mme temps. Le seul oprateur qui demande quelques explications est loprateur daccs unitaire aux bits du champ, savoir loprateur operator[]. En effet, cet oprateur ne peut pas retourner une rfrence sur le bit dsign par son argument puisquil ny a pas de type pour reprsenter les bits en C++. Par consquent, la valeur retourne est en ralit une instance de la sous-classe reference de la classe bitset. Cette sous-classe encapsule laccs individuel aux bits dun champ de bits et permet de les utiliser exactement comme un boolen. En particulier, il est possible de faire des tests directement sur cette valeur ainsi que de lui affectuer une valeur boolenne. Enn, la sous-classe reference dispose dune mthode flip dont le rle est dinverser la valeur du bit auquel lobjet reference donne accs. La classe template bitset dispose galement de mthodes spciques permettant de manipuler les bits sans avoir recours loprateur operator[]. Il sagit des mthodes test, set, reset et flip. La premire mthode permet de rcuprer la valeur courante dun des bits du champ de bits. Elle prend en paramtre le numro de ce bit et renvoie un boolen valant true si le bit est 1 et false sinon. La mthode set permet de rinitialiser le champ de bits complet en positionnant tous ses bits 1 ou de xer manuellement la valeur dun bit particulier. La troisime mthode permet de rinitialiser le champ de bits en annulant tous ses bits ou dannuler un bit spcique. Enn, la mthode flip permet dinverser la valeur de tous les bits du champ ou dinverser la valeur dun bit spcique. Les surcharges des mthodes qui travaillent sur un seul bit prennent toutes en premier paramtre la position du bit dans le champ de bits. Exemple 14-26. Manipulation des bits dun champ de bits
#include <iostream> #include <string>

343

Chapitre 14. Les types complmentaires


#include <bitset> using namespace std; int main(void) { // Construit un champ de bits : string s("10011010"); bitset<8> bs(s); cout << bs << endl; // Inverse le champ de bits : bs.flip(); cout << bs << endl; // Fixe le bit de poids fort : bs.set(7, true); cout << bs << endl; // Annule le 7me bit laide dune rfrence de bit : bs[6] = false; cout << bs << endl; // Anule le bit de poids faibe : bs.reset(0); cout << bs << endl; return 0; }

Enn, la classe bitset fournit quelques mthodes permettant deffectuer des tests sur les champs de bits. Outre les oprateurs de comparaison classiques, elle fournit les mthodes count, size, any et none. La mthode count renvoie le nombre de bits positionns 1 dans le champ de bits. La mthode size renvoie quant elle la taille du champ de bits, cest--dire la valeur du paramtre template utilise pour instancier la classe bitset. Enn, les mthodes any et none renvoient true si un bit au moins du champ de bits est positionn ou sils sont tous nuls.

344

Chapitre 15. Les ux dentre / sortie


Nous avons vu dans la Section 7.12 un exemple dapplication des classes de ux dentre / sortie de la bibliothque pour les entres / sorties standards des programmes. En ralit, ces classes de gestion des ux sintgrent dans une hirarchie complexe de classes permettant de manipuler les ux dentre / sortie et pas seulement pour les entres / sorties standards. En effet, an de faciliter la manipulation des ux dentre / sortie, la bibliothque standard C++ fournit tout un ensemble de classes template. Ces classes sont paramtres par le type de base des caractres quelles manipulent. Bien entendu, les types de caractres les plus utiliss sont les type char et wchar_t, mais il est possible dutiliser a priori nimporte quel autre type de donne pour lequel une classe de traits char_traits est dnie. Ce chapitre a pour but de dtailler cette hirarchie de classes. Les principes de base et larchitecture gnrale des ux C++ seront donc abords dans un premier temps, puis les classes de gestion des tampons seront traites. Les classes gnriques de gestion des ux dentre / sortie seront ensuite dcrites, et ce sera enn le tour des classes de gestion des ux orients chanes de caractres et des classes de gestion des ux orients chiers.

15.1. Notions de base et prsentation gnrale


Les classes de la bibliothque dentre / sortie de la bibliothque standard se subdivisent en deux catgories distinctes. La premire catgorie regroupe les classes de gestion des tampons dentre / sortie. Ces classes sont au nombre de trois : la classe template basic_stringbuf, qui permet de raliser des tampons pour les ux orients chanes de caractres, la classe template basic_lebuf, qui prend en charge les tampons pour les ux orients chiers, et leur classe de base commune, la classe template basic_streambuf. Le rle de ces classes est principalement doptimiser les entres / sorties en intercalant des tampons dentre / sortie au sein mme du programme. Ce sont principalement des classes utilitaires, qui sont utilises en interne par les autres classes de la bibliothque dentre / sortie. La deuxime catgorie de classes est de loin la plus complexe, puisquil sagit des classes de gestion des ux eux-mmes. Toutes ces classes drivent de la classe template basic_ios (elle-mme drive de la classe de base ios_base, qui dnit tous les types et les constantes utiliss par les classes de ux). La classe basic_ios fournit les fonctionnalits de base des classes de ux et, en particulier, elle gre le lien avec les tampons dentre / sortie utiliss par le ux. De cette classe de base drivent des classes spcialises respectivement pour les entres ou pour les sorties. Ainsi, la classe template basic_istream prend en charge toutes les oprations des ux dentre et la classe basic_ostream toutes les oprations des ux de sortie. Enn, la bibliothque standard dnit la classe template basic_iostream, qui regroupe toutes les fonctionnalits des classes basic_istream et basic_ostream et dont drivent toutes les classes de gestion des ux mixtes. Les classes basic_istream, basic_ostream et basic_iostream fournissent les fonctionnalits de base des ux dentre / sortie. Ce sont donc les classes utilises pour implmenter les ux dentre / sortie standards du C++ cin, cout, cerr et clog, que lon a brivement prsents dans la Section 7.12. Cependant, ces classes ne prennent pas en charge toutes les spcicits des mdias avec lesquels des ux plus complexes peuvent communiquer. Par consquent, des classes drives, plus spcialises, sont fournies par la bibliothque standard. Ces classes prennent en charge les entres / sorties sur chier et les ux orients chanes de caractres. La bibliothque standard fournit donc deux jeux de classes spcialises pour les entres / sorties dans des chiers et dans des chanes de caractres. Pour chacune des classes de base basic_istream,

345

Chapitre 15. Les ux dentre / sortie basic_ostream et basic_iostream il existe deux classes drives, une pour les chiers, et une pour les chanes de caractres. Par exemple, les classes template basic_ifstream et basic_istringstream drivent de la classe basic_istream et prennent en charge respectivement les ux dentre partir de chiers et les ux dentre partir de chanes de caractres. De mme, la bibliothque standard dnit les classes template basic_ofstream et basic_ostringstream, drives de la classe basic_ostream, pour les ux de sortie dans des chiers ou dans des chanes de caractres, et les classes template basic_fstream et basic_stringstream, drives de la classe basic_iostream, pour les ux dentre / sortie sur les chiers et les chanes de caractres.
Note : Cette hirarchie de classes est assez complexe et peut paratre trange au niveau des classes des ux mixtes. En effet, la classe basic_fstream ne drive pas des classes basic_ifstream et basic_ofstream mais de la classe basic_iostream, et cest cette classe qui drive des classes basic_istream et basic_ostream. De mme, la classe basic_stringstream ne drive pas des classes basic_istringstream et basic_ostringstream, mais de la classe basic_iostream.

Comme il la dj t dit, toutes ces classes template peuvent tre instancies pour nimporte quel type de caractre, pourvu quune classe de traits char_traits soit dnie. Cependant, en pratique, il nest courant dinstancier ces classes que pour les types de caractres de base du langage, savoir les types char et wchar_t. Historiquement, les classes dentre / sortie des bibliothques fournies avec la plupart des implmentations ntaient pas template et ne permettaient de manipuler que des ux bass sur le type de caractre char. Les implmentations disposant de classes de ux dentre / sortie capables de manipuler les caractres de type wchar_t taient donc relativement rares. prsent, toutes ces classes sont dnies comme des instances des classes template cites ci-dessus. Par souci de compatibilit, la bibliothque standard C++ dnit tout un jeu de types pour ces instances qui permettent aux programmes utilisant les anciennes classes de fonctionner. Ces types sont dclars de la manire suivante dans len-tte iosfwd (mais sont dnis dans leurs en-ttes respectifs, que lon dcrira plus tard) :
// Types de base des tampons : typedef basic_streambuf<char> typedef basic_streambuf<wchar_t> typedef basic_stringbuf<char> typedef basic_stringbuf<wchar_t> typedef basic_filebuf<char> typedef basic_filebuf<wchar_t>

streambuf; wstreambuf; stringbuf; wstringbuf; filebuf; wfilebuf;

// Types de base des flux dentre / sortie : typedef basic_ios<char> ios; typedef basic_ios<wchar_t> wios; // Types des flux dentre / sortie standards : typedef basic_istream<char> istream; typedef basic_istream<wchar_t> wistream; typedef basic_ostream<char> ostream; typedef basic_ostream<wchar_t> wostream; typedef basic_iostream<char> iostream; typedef basic_iostream<wchar_t> wiostream; // Types des flux orients fichiers : typedef basic_ifstream<char> ifstream; typedef basic_ifstream<wchar_t> wifstream; typedef basic_ofstream<char> ofstream;

346

Chapitre 15. Les ux dentre / sortie


typedef basic_ofstream<wchar_t> wofstream; typedef basic_fstream<char> fstream; typedef basic_fstream<wchar_t> wfstream; // Types des flux orients chanes de caractres : typedef basic_istringstream<char> istringstream; typedef basic_istringstream<wchar_t> wistringstream; typedef basic_ostringstream<char> ostringstream; typedef basic_ostringstream<wchar_t> wostringstream; typedef basic_stringstream<char> stringstream; typedef basic_stringstream<wchar_t> wstringstream;

Les objets cin, cout, cerr et clog sont donc des instances des classes istream et ostream, qui sont associes aux ux dentre / sortie standards du programme. En fait, la bibliothque standard dnit galement des versions capables de manipuler des ux bass sur le type wchar_t pour les programmes qui dsirent travailler avec des caractres larges. Ces objets sont respectivement wcin (instance de wistream), wcout, wcerr et wclog (instances de wostream). Tous ces objets sont initialiss par la bibliothque standard automatiquement lorsquils sont utiliss pour la premire fois, et sont donc toujours utilisables.
Note : En ralit, sur la plupart des systmes, les ux dentre / sortie standards sont les premiers descripteurs de chiers que le systme attribue automatiquement aux programmes lorsquils sont lancs. En toute logique, les objets cin, cout, cerr et clog devraient donc tre des instances de classes de gestion de ux orients chiers. Cependant, ces chiers ne sont pas nomms dune part et, dautre part, tous les systmes ne grent pas les ux dentre / sortie standards de la mme manire. Ces objets ne sont donc pas toujours des ux sur des chiers et la bibliothque standard C++ ne les dnit par consquent pas comme tels.

15.2. Les tampons


Les classes de gestion des tampons de la bibliothque standard C++ se situent au cur des oprations dcriture et de lecture sur les ux de donnes physiques quun programme est susceptible de manipuler. Bien quelles ne soient quasiment jamais utilises directement par les programmeurs, cest sur ces classes que les classes de ux sappuient pour effectuer les oprations dentre sortie. Il est donc ncessaire de connatre un peu leur mode de fonctionnement.

15.2.1. Gnralits sur les tampons


Un tampon, galement appel cache, est une zone mmoire dans laquelle les oprations dcriture et de lecture se font et dont le contenu est mis en correspondance avec les donnes dun mdia physique sous-jacent. Les mcanismes de cache ont essentiellement pour but doptimiser les performances des oprations dentre / sortie. En effet, laccs la mmoire cache est gnralement beaucoup plus rapide que laccs direct au support physique ou au mdia de communication. Les oprations effectues par le programme se font donc, la plupart du temps, uniquement au niveau du tampon, et ce nest que dans certaines conditions que les donnes du tampon sont effectivement transmises au mdia physique. Le gain en performance peut intervenir plusieurs niveau. Les cas les plus simples tant simplement lorsquune donne crite est crase peu de temps aprs par une autre valeur (la premire opration dcriture nest alors jamais transmise au mdia) ou lorsquune donne est lue

347

Chapitre 15. Les ux dentre / sortie plusieurs fois (la mme donne est renvoye chaque lecture). Bien entendu, cela suppose que les donnes stockes dans le tampon soient cohrentes avec les donnes du mdia, surtout si les donnes sont accdes au travers de plusieurs tampons. Tout mcanisme de gestion de cache permet donc de vider les caches (cest--dire de forcer les oprations dcriture) et de les invalider (cest--dire de leur signaler que leurs donnes sont obsoltes et quune lecture physique doit tre faite si on cherche y accder). Les mcanismes de mmoire cache et de tampon sont trs souvent utiliss en informatique, tous les niveaux. On trouve des mmoires cache dans les processeurs, les contrleurs de disque, les graveurs de CD, les pilotes de priphriques des systmes dexploitation et bien entendu dans les programmes. Chacun de ces caches contribue lamlioration des performances globales en retardant au maximum la ralisation des oprations lentes et en optimisant les oprations de lecture et dcriture (souvent en les effectuant en groupe, ce qui permet de rduire les frais de communication ou dinitialisation des priphriques). Il nest donc absolument pas surprenant que la bibliothque standard C++ utilise elle aussi la notion de tampon dans toutes ses classes dentre / sortie...

15.2.2. La classe basic_streambuf


Les mcanismes de base des tampons de la bibliothque standard sont implments dans la classe template basic_streambuf. Cette classe nest pas destine tre utilise telle quelle car elle ne sait pas communiquer avec les supports physiques des donnes. En fait, elle ne peut tre utilise quen tant que classe de base de classes plus spcialises, qui elles fournissent les fonctionnalits daccs aux mdias par lintermdiaire de fonctions virtuelles. La classe basic_streambuf appelle donc ces mthodes en diverses circonstances au sein des traitements effectus par son propre code de gestion du tampon, aussi bien pour signaler les changements dtat de celui-ci que pour demander lcriture ou la lecture des donnes dans la squence sous contrle. La classe basic_streambuf fournit donc une interface publique permettant daccder aux donnes du tampon dun ct et dnit linterface de communication avec ses classes lles par lintermdiaire de ses mthodes virtuelles de lautre cot. Bien entendu, ces mthodes virtuelles sont toutes dclares en zone protge an dviter que lon puisse les appeler directement, tout en permettant aux classes drives de les rednir et dy accder. En interne, la classe basic_streambuf encapsule deux tampons, un pour les critures et un pour les lectures. Cependant, ces tampons accdent la mme mmoire et la mme squence de donnes physiques. Ces deux tampons peuvent tre utiliss simultanment ou non, suivant la nature de la squence sous contrle et suivant le ux qui utilise le tampon. Par exemple, les ux de sortie nutilisent que le tampon en criture, et les ux dentre que le tampon en lecture. La classe basic_streambuf gre ses tampons dentre et de sortie laide dune zone de mmoire interne qui contient un sous-ensemble des donnes de la squence sous contrle. Les deux tampons travaillent de manire indpendante sur cette zone de mmoire et sont chacun reprsents laide de trois pointeurs. Ces pointeurs contiennent respectivement ladresse du dbut de la zone mmoire du tampon, son adresse de n et ladresse de la position courante en lecture ou en criture. Ces pointeurs sont compltement grs en interne par la classe basic_streambuf, mais les classes drives peuvent y accder et les modier en fonction de leurs besoins par lintermdiaire daccesseurs. Les pointeurs dun tampon peuvent parfaitement tre nuls si celui-ci nest pas utilis. Toutefois, si le pointeur rfrenant la position courante nest pas nul, ses pointeurs associs ne doivent pas ltre et la position courante rfrence doit obligatoirement se situer dans une zone mmoire dnie par les pointeurs de dbut et de n du tampon. La classe basic_streambuf est dclare comme suit dans len-tte streambuf :
template <class charT, class traits =

348

Chapitre 15. Les ux dentre / sortie


char_traits<charT> > class basic_streambuf { public: // Les types de base : typedef charT typedef typename traits::int_type typedef typename traits::pos_type typedef typename traits::off_type typedef traits

char_type; int_type; pos_type; off_type; traits_type;

// Les mthodes publiques utilisables par les classes de flux : // Les mthodes de gestion des locales : locale pubimbue(const locale &loc); locale getloc() const; // Les mthodes de gestion du tampon : basic_streambuf<char_type,traits> * pubsetbuf(char_type* s, streamsize n); pos_type pubseekoff(off_type off, ios_base::seekdir sens, ios_base::openmode mode = ios_base::in | ios_base::out); pos_type pubseekpos(pos_type sp, ios_base::openmode mode = ios_base::in | ios_base::out); int pubsync(); // Mthodes daccs au tampon en lecture : streamsize in_avail(); int_type sgetc(); int_type sbumpc(); int_type snextc(); streamsize sgetn(char_type *s, streamsize n); // Mthode dannulation de lecture dun caractre : int_type sputbackc(char_type c); int_type sungetc(); // Mthode daccs en criture : int_type sputc(char_type c); streamsize sputn(const char_type *s, streamsize n); // Le destructeur : virtual ~basic_streambuf(); protected: // Les mthodes protected utilisables par // les classes drives : // Le constructeur : basic_streambuf(); // Mthodes daccs aux pointeurs du tampon de lecture : char_type *eback() const; char_type *gptr() const; char_type *egptr() const; void gbump(int n); void setg(char_type *debut, char_type *suivant,

349

Chapitre 15. Les ux dentre / sortie


char_type *fin); // Mthodes daccs aux pointeurs du tampon dcriture : char_type *pbase() const; char_type *pptr() const; char_type *epptr() const; void pbump(int n); void setp(char_type *debut, char_type *fin); // Les mthodes protected virtuelles, que les classes // drives doivent implmenter : virtual void imbue(const locale &loc); virtual basic_streambuf<char_type, traits>* setbuf(char_type *s, streamsize n); virtual pos_type seekoff(off_type off, ios_base::seekdir sens, ios_base::openmode mode = ios_base::in | ios_base::out); virtual pos_type seekpos(pos_type sp, ios_base::openmode mode = ios_base::in | ios_base::out); virtual int sync(); virtual int showmanyc(); virtual streamsize xsgetn(char_type *s, streamsize n); virtual int_type underflow(); virtual int_type uflow(); virtual int_type pbackfail(int_type c = traits::eof()); virtual streamsize xsputn(const char_type* s, streamsize n); virtual int_type overflow (int_type c = traits::eof()); };

Comme vous pouvez le constater, le constructeur de la classe basic_streambuf est dclar en zone protected, ce qui empche quiconque de linstancier. Cest normal, puisque cette classe nest destine tre utilise quen tant que classe de base dune classe spcialise pour un mdia spcique. En revanche, les mthodes virtuelles ne sont pas pures, car elles fournissent un comportement par dfaut qui conviendra dans la plupart des cas. Linterface publique comprend des mthodes dordre gnrale et des mthodes permettant deffectuer les oprations dcriture et de lecture sur les tampons encapsuls par la classe basic_streambuf. Pour les distinguer des mthodes virtuelles qui doivent tre implmentes dans les classes drives, leur nom est prx par pub (pour publique ). Les mthodes pubimbue et locale permettent respectivement de xer la locale utilise par le programme pour ce tampon et de rcuprer la locale courante. Par dfaut, la locale globale active au moment de la construction du tampon est utilise. Les notions de locales seront dcrites dans le Chapitre 16. Les mthodes pubsetbuf, pubseekoff et pubseekpos permettent quant elles de paramtrer le tampon dentre / sortie. Ces mthodes se contentent dappeler les mthodes virtuelles setbuf, seekoff et seekpos, dont le comportement, spcique chaque classe drive, sera dcrit cidessous. Viennent ensuite les mthodes daccs aux donnes en lecture et en criture. Les mthodes de lecture sont respectivement les mthodes sgetc, sbumpc et snextc. La mthode sgetc permet de lire la valeur du caractre rfrenc par le pointeur courant du tampon dentre. Cette fonction renvoie la mme valeur chaque appel, car elle ne modie pas la valeur de ce pointeur. En revanche, la mthode sbumpc fait avancer ce pointeur aprs la lecture du caractre courant, ce qui fait quelle peut tre utilise pour lire tous les caractres du ux de donnes physiques. Enn, la mthode snextc appelle

350

Chapitre 15. Les ux dentre / sortie la mthode sbumpc dans un premier temps, puis renvoie la valeur retourne par sgetc. Cette mthode permet donc de lire la valeur du caractre suivant dans le tampon et de positionner le pointeur sur ce caractre. Notez que, contrairement la mthode sbumpc, snextc modie la valeur du pointeur avant de lire le caractre courant, ce qui fait quen ralit elle lit le caractre suivant. Toutes ces mthodes renvoient la valeur de n de chier dnie dans la classe des traits du type de caractre utilis en cas derreur. Elles sont galement susceptibles de demander la lecture de donnes complmentaires dans le cadre de la gestion du tampon. La mthode in_avail renvoie le nombre de caractres encore stocks dans le tampon gr par la classe basic_streambuf. Si ce tampon est vide, elle renvoie une estimation du nombre de caractres qui peuvent tre lus dans la squence contrle par le tampon. Cette estimation est un minimum, la valeur renvoye garantit quautant dappel sbumpc russiront. Les tampons de la bibliothque standard donnent la possibilit aux programmes qui les utilisent dannuler la lecture dun caractre. Normalement, ce genre dannulation ne peut tre effectu quune seule fois et la valeur qui doit tre replace dans le tampon doit tre exactement celle qui avait t lue. Les mthodes qui permettent deffectuer ce type dopration sont les mthodes sputbackc et sungetc. La premire mthode prend en paramtre la valeur du caractre qui doit tre replac dans le tampon et la deuxime ne fait que dcrmenter le pointeur rfrenant llment courant dans le tampon de lecture. Ces deux oprations peuvent chouer si la valeur replacer nest pas gale la valeur du caractre qui se trouve dans le tampon ou si, tout simplement, il est impossible de revenir en arrire (par exemple parce quon se trouve en dbut de squence). Dans ce cas, ces mthodes renvoient la valeur de n de chier dnie dans la classe des traits du type de caractre utilis, savoir traits::eof(). Enn, les mthodes dcriture de la classe basic_streambuf sont les mthodes sputc et sputn. La premire permet dcrire un caractre unique dans le tampon de la squence de sortie et la deuxime dcrire toute une srie de caractres. Dans ce dernier cas, les caractres crire sont spcis laide dun tableau dont la longueur est passe en deuxime paramtre sputn. Ces deux mthodes peuvent renvoyer le caractre de n de chier de la classe des traits du type de caractre utilis pour signaler une erreur dcriture. Linterface protge de la classe basic_streambuf est constitue des accesseurs aux pointeurs sur les tampons dentre et de sortie dune part et, dautre part, des mthodes virtuelles que les classes drives doivent rednir pour implmenter la gestion des entres / sorties physiques. Les pointeurs du tableau contenant les donnes du tampon de lecture peuvent tre rcuprs par les classes drives laide des mthodes eback, gptr et egptr. La mthode eback renvoie le pointeur sur le dbut du tableau du tampon dentre. Les mthodes gptr et egptr renvoient quant elles le pointeur sur llment courant, dont la valeur peut tre obtenue avec la mthode sgetc, et le pointeur sur la n du tableau du tampon. Le nom de la mthode gptr provient de labrviation de langlais get pointer et celui de la mthode egptr de labrviation end of gptr . Enn, les mthodes gbump et setg permettent respectivement de faire avancer le pointeur sur llment courant dun certain nombre de positions et de xer les trois pointeurs du tampon de lecture en une seule opration. Les pointeurs du tampon dcriture sont accessibles par des mthodes similaires celles dnies pour le tampon de lecture. Ainsi, les mthodes pbase, pptr et epptr permettent respectivement daccder au dbut du tableau contenant les donnes du tampon dcriture, la position courante pour les critures et au pointeur de n de ce tableau. pptr est ici labrviation de langlais put pointer . Les mthodes pbump et setp jouent le mme rle pour les pointeurs du tampon dcriture que les mthodes gbump et setg pour les pointeurs du tampon de lecture. Enn, les mthodes protges de la classe basic_streambuf permettent, comme on la dj indiqu ci-dessus, de communiquer avec les classes drives implmentant les entres / sorties physiques. Le rle de ces mthodes est dcrit dans le tableau ci-dessous :

351

Chapitre 15. Les ux dentre / sortie Mthode


imbue

Description Cette mthode est appele chaque fois quil y a un changement de locale au niveau du tampon. Les classes drives de la classe basic_streambuf sont assures quil ny aura pas de changement de locale entre chaque appel cette fonction et peuvent donc maintenir une rfrence sur la locale courante en permanence. Les notions concernant les locales seront dcrites dans le Chapitre 16. Cette mthode nest appele que par la mthode pubsetbuf. Elle a principalement pour but de xer la zone mmoire utilise par le tampon. Ceci peut ne pas avoir de sens pour certains mdias, aussi cette mthode peut-elle ne rien faire du tout. En pratique, si cette fonction est appelle avec deux paramtres nuls, les mcanismes de gestion du cache doivent tre dsactivs. Pour cela, la classe drive doit xer les pointeurs des tampons de lecture et dcriture la valeur nulle. Cette mthode nest appele que par la mthode pubseekoff. Tout comme la mthode setbuf, sa smantique est spcique chaque classe drive de gestion des mdias physiques. En gnral, cette fonction permet de dplacer la position courante dans la squence de donnes dun certain dcalage. Ce dcalage peut tre spci relativement la position courante, au dbut ou la n de la squence de donnes sous contrle. Le mode de dplacement est spci laide du paramtre sens, qui doit prendre lune des constantes de type seekdir dnie dans la classe ios_base. De mme, le tampon concern par ce dplacement est spci par le paramtre mode, dont la valeur doit tre lune des constante de type ios_base::openmode. Ces types et ces constantes seront dcrits avec la classe de base ios_base dans la Section 15.3. Cette mthode nest appele que par la mthode pubseekpos. Elle fonctionne de manire similaire la mthode seekoff puisquelle permet de positionner le pointeur courant des tampons de la classe basic_streambuf un emplacement arbitraire dans la squence de donnes sous contrle. Cette mthode nest appele que par la mthode pubsync et permet de demander la synchronisation du tampon avec la squence de donnes physiques. Autrement dit, les oprations dcritures doivent tre effectues sur le champ an de sassurer que les modiations effectues dans le cache soient bien enregistres. Cette mthode est appele par la mthode in_avail lorsque la n du tampon de lecture a t atteinte. Elle doit renvoyer une estimation basse du nombre de caractres qui peuvent encore tre lus dans la squence sous contrle. Cette estimation doit tre sre, dans le sens o le nombre de caractres renvoys doit effectivement pouvoir tre lu sans erreur. Cette mthode nest appele que par la mthode sgetn. Elle permet deffectuer la lecture de plusieurs caractres et de les stocker dans le tableau reu en paramtre. La lecture de chaque caractre doit se faire exactement comme si la mthode sbumpc tait appele successivement pour chacun deux an de maintenir le tampon de lecture dans un tat cohrent. La valeur retourne est le nombre de caractres effectivement lus ou traits::eof() en cas derreur.

setbuf

seekoff

seekpos

sync

showmanyc

xsgetn

352

Chapitre 15. Les ux dentre / sortie Mthode


underflow

Description Cette mthode est appele lorsque la n du tampon est atteinte lors de la lecture dun caractre. Cela peut se produire lorsquil ny a plus de caractre disponible dans le tampon ou tout simplement chaque lecture, lorsque le mcanisme de cache est dsactiv. Cette fonction doit renvoyer le caractre suivant de la squence sous contrle et remplir le tampon si ncessaire. Le pointeur rfrenant le caractre courant est alors initialis sur le caractre dont la valeur a t rcupre. Ainsi, la mthode underflow doit remplir le tampon, mais ne doit pas faire avancer la position courante de lecture. Cette mthode peut renvoyer traits::eof() en cas dchec, ce qui se produit gnralement lorsque la n de la squence sous contrle a t atteinte. Cette mthode est appele dans les mmes conditions que la mthode underflow. Elle doit galement remplir le tampon, mais, contrairement underflow, elle fait galement avancer le pointeur du caractre courant dune position. Ceci implique que cette mthode ne peut pas tre utilise avec les ux non bufferiss. En gnral, cette mthode na pas tre rednie parce que le code de la mthode uflow de la classe basic_streambuf effectue ces oprations en sappuyant sur la mthode underflow. Cette mthode peut renvoyer traits::eof() en cas dchec. Cette mthode est appele lorsque la mthode sputbackc choue, soit parce que le pointeur de lecture se trouve au dbut du tampon de lecture, soit parce que le caractre qui doit tre replac dans la squence nest pas le caractre qui vient den tre extrait. Cette mthode doit prendre en charge le dplacement des caractres du tampon pour permettre le replacement du caractre fourni en paramtre et mettre jour les pointeurs de gestion du tampon de lecture en consquence. Elle peut renvoyer la valeur traits::eof() pour signaler un chec. Cette mthode nest appele que par la mthode sputn. Elle permet de raliser lcriture de plusieurs caractres dans la squence de sortie. Ces caractres sont spcis dans le tableau fourni en paramtre. Ces critures sont ralises exactement comme si la mthode sputc tait appele successivement pour chaque caractre an de maintenir le tampon dcriture dans un tat cohrent. La valeur retourne est le nombre de caractres crits ou traits::eof() en cas derreur. Cette mthode est appele par les mthodes dcriture de la classe basic_streambuf lorsque le tampon dcriture est plein. Elle a pour but de dgager de la place dans ce tampon en consommant une partie des caractres situs entre le pointeur de dbut du tampon et le pointeur de position dcriture courante. Elle est donc susceptible deffectuer les critures physiques sur le mdia de sortie au cours de cette opration. Si lcriture russit, les pointeurs de gestion du tampon dcriture doivent tre mis jour en consquence. Dans le cas contraire, la fonction peut renvoyer la valeur traits::eof() pour signaler lerreur ou lancer une exception.

uflow

pbackfail

xsputn

overflow

15.2.3. Les classes de tampons basic_stringbuf et

353

Chapitre 15. Les ux dentre / sortie

basic_lebuf
Vous laurez compris, lcriture dune classe drive de la classe basic_streambuf prenant en charge un mdia peut tre relativement technique et difcile. Heureusement, cette situation ne se prsente quasiment jamais, parce que la bibliothque standard C++ fournit des classes drives prenant en charge les deux situations les plus importantes : les tampons daccs une chane de caractres et les tampons daccs aux chiers. Ces classes sont respectivement les classes template basic_stringbuf et basic_lebuf.

15.2.3.1. La classe basic_stringbuf


La classe basic_stringbuf permet deffectuer des entres / sorties en mmoire de la mme manire que si elles taient effectues sur un priphrique dentre / sortie normal. Le but de cette classe nest videmment pas doptimiser les performances laide dun cache puisque les oprations se font destination de la mmoire, mais duniformiser et de permettre les mmes oprations de formatage dans des chanes de caractres que celles que lon peut raliser avec les ux dentre / sortie normaux. La classe basic_streambuf drive bien entendu de la classe basic_streambuf puisquelle dnit les oprations fondamentales dcriture et de lecture dans une chane de caractres. Elle est dclare comme suit dans len-tte sstream :
template <class charT, class traits = char_traits<charT>, class Allocator = allocator<chatT> > class basic_stringbuf : public basic_streambuf<charT, traits> { public: // Les types : typedef charT char_type; typedef typename traits::int_type int_type; typedef typename traits::pos_type pos_type; typedef typename traits::off_type off_type; typedef traits traits_type; // Les constructeurs / destructeurs : explicit basic_stringbuf( ios_base::openmode mode = ios_base::in | ios_base::out); explicit basic_stringbuf( const basic_string<charT,traits,Allocator> &str, ios_base::openmode mode = ios_base::in | ios_base::out); virtual ~basic_stringbuf(); // Les mthodes de gestion de la chane de caractres sous contrle : basic_string<charT,traits,Allocator> str() const; void str(const basic_string<charT,traits,Allocator> &s); };

Comme cette dclaration le montre, la classe basic_streambuf dnit elle aussi un jeu de types permettant dobtenir facilement les types de objets manipuls. De plus, elle dnit galement quelques mthodes complmentaires permettant deffectuer les oprations spciques aux ux orients chane de caractres. En particulier, les constructeurs permettent de fournir une chane de caractres partir de laquelle le tampon sera initialis. Cette chane de caractres est copie lors de la construction du tampon, ce qui fait quelle peut tre rutilise ou modie aprs la cration du tampon. Ces constructeurs prennent galement en paramtre le mode de fonctionnement du tampon. Ce mode peut tre la lecture

354

Chapitre 15. Les ux dentre / sortie (auquel cas le paramtre mode vaut ios_base::in), lcriture (mode vaut alors ios_base::out) ou les deux (mode vaut alors la combinaison de ces deux constantes par un ou logique). Les constantes de mode douverture sont dnis dans la classe ios_base, que lon dcrira dans la Section 15.3.
Note : Vous remarquerez que, contrairement au constructeur de la classe basic_streambuf, les constructeurs de la classe basic_stringbuf sont dclars dans la zone de dclaration publique, ce qui autorise la cration de tampons de type basic_stringbuf. Le constructeur de la classe de base est appel par ces constructeurs, qui ont le droit de le faire puisquil sagit dune mthode protected.

Il est galement possible daccder aux donnes stockes dans le tampon laide des accesseurs str. Le premier renvoie une basic_string contenant la chane du tampon en criture si possible (cest--dire si le tampon a t cr dans le mode criture ou lecture / criture), et la chane du tampon en lecture sinon. Le deuxime accesseur permet de dnir les donnes du tampon a posteriori, une fois celui-ci cr. Exemple 15-1. Lecture et criture dans un tampon de chane de caractres
#include <iostream> #include <string> #include <sstream> using namespace std; int main(void) { // Construit une chane de caractre : string s("123456789"); // Construit un tampon bas sur cette chane : stringbuf sb(s); // Lit quelques caractres unitairement : cout << (char) sb.sbumpc() << endl; cout << (char) sb.sbumpc() << endl; cout << (char) sb.sbumpc() << endl; // Replace le dernier caractre lu dans le tampon : sb.sungetc(); // Lit trois caractres conscutivement : char tab[4]; sb.sgetn(tab, 3); tab[3] = 0; cout << tab << endl; // crase le premier caractre de la chane : sb.sputc(a); // Rcupre une copie de la chane utilise par le tampon : cout << sb.str() << endl; return 0; }

Note : La classe basic_stringbuf rednit bien entendu certaines des mthodes protges de la classe basic_streambuf. Ces mthodes nont pas t prsentes dans la dclaration ci-dessus parce quelles font partie de limplmentation de la classe basic_stringbuf et leur description na que peu dintrt pour les utilisateurs.

355

Chapitre 15. Les ux dentre / sortie

15.2.3.2. La classe basic_lebuf


La classe basic_lebuf est la classe qui prend en charge les oprations dentre / sortie sur chier dans la bibliothque standard C++. Pour la bibliothque standard C++, un chier est une squence de caractres simples (donc de type char). Il est important de bien comprendre quil nest pas possible, avec la classe basic_lebuf, de manipuler des chiers contenant des donnes de type wchar_t. En effet, mme dans le cas o les donnes enregistres sont de type wchar_t, les chiers contenant ces donnes sont enregistrs sous la forme de squences de caractres dont lunit de base reste le caractre simple. La manire de coder les caractres larges dans les chiers nest pas spcie et chaque implmentation est libre de faire ce quelle veut ce niveau. Gnralement, lencodage utilis est un encodage taille variable, cest dire que chaque caractre large est reprsent sous la forme dun ou de plusieurs caractres simples, selon sa valeur et selon sa position dans le ux de donnes du chier. Cela signie quil ne faut pas faire dhypothse sur la manire dont les instances de la classe template basic_lebuf enregistrent les donnes des chiers pour des valeurs du paramtre template charT autres que le type char. En gnral, lencodage utilis ne concerne pas le programmeur, puisquil suft denregistrer et de lire les chiers avec les mme types de classes basic_lebuf pour retrouver les donnes initiales. Toutefois, si les chiers doivent tre relus par des programmes crits dans un autre langage ou compils avec un autre compilateur, il peut tre ncessaire de connatre lencodage utilis. Vous trouverez cette information dans la documentation de votre environnement de dveloppement. La classe basic_lebuf est dclare comme suit dans len-tte fstream :
template <class charT, class traits = char_traits<charT> > class basic_filebuf : public basic_streambuf<charT,traits> { public: // Les types : typedef charT char_type; typedef typename traits::int_type int_type; typedef typename traits::pos_type pos_type; typedef typename traits::off_type off_type; typedef traits traits_type; // Les constructeurs / destructeurs : basic_filebuf(); virtual ~basic_filebuf(); // Les mthodes de gestion du fichier sous contrle : basic_filebuf<charT,traits> *open(const char *s, ios_base::openmode mode); basic_filebuf<charT,traits> *close(); bool is_open() const; };

Comme vous pouvez le constater, la classe basic_lebuf est semblable la classe basic_stringbuf. Outre les dclarations de types et celles du constructeur et du destructeur, elle dnit trois mthodes permettant de raliser les oprations spciques aux chiers. La mthode open permet, comme son nom lindique, douvrir un chier. Cette mthode prend en paramtre le nom du chier ouvrir ainsi que le mode douverture. Ce mode peut tre une combinaison logique de plusieurs constantes dnies dans la classe ios_base. Ces constantes sont dcrites dans la

356

Chapitre 15. Les ux dentre / sortie Section 15.3. Les plus importantes sont in, qui permet douvrir un chier en lecture, out, qui permet de louvrir en lecture, binary, qui permet de louvrir en mode binaire, app, qui permet de louvrir en mode ajout, et trunc, qui permet de le vider lorsquil est ouvert en criture. La mthode open renvoie le pointeur this si le chier a pu tre ouvert ou le pointeur nul dans le cas contraire. La classe basic_lebuf ne gre quune seule position pour la lecture et lcriture dans les chiers. Autrement dit, si un chier est ouvert la fois en lecture et en criture, les pointeurs de lecture et dcriture du tampon auront toujours la mme valeur. Lcriture une position provoquera donc non seulement la modication de la position courante en criture, mais galement celle de la position courante en lecture. La mthode close est la mthode utiliser pour fermer un chier ouvert. Cette mthode ne peut fonctionner que si un chier est effectivement ouvert dans ce tampon. Elle renvoie le pointeur this si le chier courant a effectivement pu tre ferm ou le pointeur nul en cas derreur. Enn, la mthode is_open permet de dterminer si un chier est ouvert ou non dans ce tampon. Exemple 15-2. Lecture et criture dans un tampon de chier
#include <iostream> #include <string> #include <fstream> using namespace std; int main(void) { // Ouvre un fichier texte et cre un tampon pour y accder : filebuf fb; fb.open("test.txt", ios_base::in | ios_base::out | ios_base::trunc); // Teste si le fichier est ouvert : if (fb.is_open()) { // crit deux lignes : string l1 = "Bonjour\n"; string l2 = "tout le monde !\n"; fb.sputn(l1.data(), l1.size()); fb.sputn(l2.data(), l2.size()); // Repositionne le pointeur de fichier au dbut. // Note : le dplacement se fait pour les deux // tampons parce quil ny a quun pointeur // sur les donnes du fichier : fb.pubseekpos(0, ios_base::in | ios_base::out); // Lit les premires lettres du fichier : cout << (char) fb.sbumpc() << endl; cout << (char) fb.sbumpc() << endl; cout << (char) fb.sbumpc() << endl; // Ferme le fichier : fb.close(); } return 0; }

357

Chapitre 15. Les ux dentre / sortie

15.3. Les classes de base des ux : ios_base et basic_ios


Les classes de gestion des ux constituent la deuxime hirarchie de classes de la bibliothque standard dentre / sortie. Bien que destines accder des mdias varis, ces classes disposent dune interface commune qui permet den simplier lutilisation. Cette interface est essentiellement dnie par deux classes de bases fondamentales : la classe ios_base, qui dnit toutes les fonctionnalits indpendantes du type de caractre utilis par les ux, et la classe template basic_ios, qui regroupe lessentiel des fonctionnalits des ux dentre / sortie.

15.3.1. La classe ios_base


La classe ios_base est une classe C++ classique dont toutes les classes template de gestion des ux dentre / sortie drivent. Cette classe ne fournit, comme cest le cas de la plupart des classes de base, quun nombre de fonctionnalits trs rduit. En pratique, sa principale utilit est de dnir plusieurs jeux de constantes qui sont utilises par ses classes drives pour identier les options des diffrents modes de fonctionnement disponibles. Ces constantes portent un nom standardis mais leur type nest pas prcis par la norme C++. Cependant, leur nature (entire, numration, champ de bits) est impose, et les implmentations doivent dnir un typedef permettant de crer des variables du mme type. La classe de base ios_base est dclare comme suit dans len-tte ios :
class ios_base { // Constructeur et destructeur : protected: ios_base(); public: ~ios_base(); // Classe de base des exceptions des flux dentre / sortie : class failure; // Classe dinitialisation des objets dentre / sortie standards : class Init; // Constantes de dfinition des options de formatage : typedef T1 fmtflags; static const fmtflags boolalpha; static const fmtflags hex; static const fmtflags oct; static const fmtflags dec; static const fmtflags fixed; static const fmtflags scientific; static const fmtflags left; static const fmtflags right; static const fmtflags internal; static const fmtflags showbase; static const fmtflags showpoint; static const fmtflags showpos; static const fmtflags uppercase; static const fmtflags unitbuf; static const fmtflags skipws;

358

Chapitre 15. Les ux dentre / sortie


static const fmtflags adjustfield; static const fmtflags basefield; static const fmtflags floatfield; // Constantes des modes douverture des flux et des fichiers : typedef T3 openmode; static const openmode in; static const openmode out; static const openmode binary; static const openmode trunc; static const openmode app; static const openmode ate; // Constantes de dfinition des modes de positionnement : typedef T4 seekdir; static const seekdir beg; static const seekdir cur; static const seekdir end; // Constantes dtat des typedef T2 iostate; static const iostate static const iostate static const iostate static const iostate flux dentre / sortie : goodbit; eofbit; failbit; badbit;

// Accesseurs sur les options de formatage : fmtflags flags() const; fmtflags flags(fmtflags fmtfl); fmtflags setf(fmtflags fmtfl); fmtflags setf(fmtflags fmtfl, fmtflags mask); void unsetf(fmtflags mask); streamsize precision() const; streamsize precision(streamsize prec); streamsize width() const; streamsize width(streamsize wide); // Mthode de synchronisation : static bool sync_with_stdio(bool sync = true); // Mthode denregistrement des callback pour les vnements : enum event { erase_event, imbue_event, copyfmt_event }; typedef void (*event_callback)(event, ios_base &, int index); void register_callback(event_call_back fn, int index); // Mthode de gestion des donnes prives : static int xalloc(); long &iword(int index); void* &pword(int index); // Mthodes de gestion des locales : locale imbue(const locale &loc); locale getloc() const; };

359

Chapitre 15. Les ux dentre / sortie Comme vous pouvez le constater, le constructeur de la classe ios_base est dclar en zone protge. Il nest donc pas possible dinstancier un objet de cette classe, ce qui est normal puisquelle nest destine qu tre la classe de base de classes plus spcialises. Le premier jeu de constantes dni par la classe ios_base contient toutes les valeurs de type fmtags, qui permettent de spcier les diffrentes options utiliser pour le formatage des donnes crites dans les ux. Ce type doit obligatoirement tre un champ de bits. Les constantes quant elles permettent de dnir la base de numrotation utilise, si celle-ci doit tre indique avec chaque nombre ou non, ainsi que les diffrentes options de formatage utiliser. La signication prcise de chacune de ces constantes est donne dans le tableau suivant : Tableau 15-1. Options de formatage des ux Constante
boolalpha

Signication Permet de raliser les entres / sorties des boolens sous forme textuelle et non sous forme numrique. Ainsi, les valeurs true et false ne sont pas crites ou lues sous la forme de 0 ou de 1, mais sous la forme xe par la classe de localisation utilise par le ux. Par dfaut, les boolens sont reprsents par les chanes de caractres true et false lorsque ce ag est actif. Cependant, il est possible de modier ces chanes de caractres en dnissant une locale spcique. Les notions de locales seront dcrites dans le Chapitre 16. Permet de raliser les entres / sorties des entiers en base hexadcimale. Permet de raliser les entres / sorties des entiers en base octale. Permet de raliser les entres / sorties des entiers en dcimal. Active la reprsentation en virgule xe des nombres virgule ottante. Active la reprsentation en virgule ottante des nombres virgule ottante. Utilise lalignement gauche pour les donnes crites sur les ux de sortie. Dans le cas o la largeur des champs est xe, des caractres de remplissage sont ajouts la droite de ces donnes pour atteindre cette largeur. Utilise lalignement droite pour les donnes crites sur les ux de sortie. Dans le cas o la largeur des champs est xe, des caractres de remplissage sont ajouts la gauche de ces donnes pour atteindre cette largeur. Effectue un remplissage avec les caractres de remplissage une position xe dtermine par la locale en cours dutilisation si la largeur des donnes est infrieure la largeur des champs utiliser. Si la position de remplissage nest pas spcie par la locale pour lopration en cours, le comportement adopt est lalignement droite. Prcise la base utilise pour le formatage des nombres entiers. crit systmatiquement le sparateur de la virgule dans le formatage des nombres virgule ottante, que la partie fractionnaire de ces nombres soit nulle ou non. Le caractre utilis pour reprsenter ce sparateur est dni dans la locale utilise par le ux dentre / sortie. La notion de locale sera vue dans le Chapitre 16. Par dfaut, le caractre utilis est le point dcimal (.). Utilise systmatiquement le signe des nombres crits sur le ux de sortie, quils soient positifs ou ngatifs. Le formatage du signe des nombre se fait selon les critres dnis par la locale active pour ce ux. Par dfaut, les nombres ngatifs sont prxs du symbole - et les nombres positifs du symbole + si cette option est active. Dans le cas contraire, le signe des nombres positifs nest pas crit.

hex oct dec fixed scientific left

right

internal

showbase showpoint

showpos

360

Chapitre 15. Les ux dentre / sortie Constante


uppercase

Signication Permet dcrire en majuscule certains caractres, comme le e de lexposant des nombres virgule ottante par exemple, ou les chiffres hexadcimaux A F. Permet deffectuer automatiquement une opration de synchronisation du cache utilis par le ux de sortie aprs chaque criture. Permet dignorer les blancs prcdant les donnes lire dans les oprations dentre pour lesquelles de tels blancs sont signicatifs.

unitbuf skipws

La classe ios_base dnit galement les constantes adjustfield, basefield et floatfield, qui sont en ralit des combinaisons des autres constantes. Ainsi, la constante adjustfield reprsente lensemble des options dalignement ( savoir left, right et internal), la constante basefield reprsente les options de spcication de base pour les sorties numriques (cest--dire les options hex, oct et dec), et la constante floatfield les options dnissant les types de formatage des nombres virgules (scientific et fixed). Le deuxime jeu de constantes permet de caractriser les modes douverture des ux et des chiers. Le type de ces constantes est le type openmode. Il sagit galement dun champ de bits, ce qui permet de raliser des combinaisons entre leurs valeurs pour cumuler diffrents modes douverture lors de lutilisation des chiers. Les constantes dnies par la classe ios_base sont dcrites dans le tableau ci-dessous : Tableau 15-2. Modes douverture des chiers Constante
in out binary

Signication Permet douvrir le ux en criture. Permet douvrir le ux en lecture. Permet douvrir le ux en mode binaire, pour les systmes qui font une distinction entre les chiers textes et les chiers binaires. Ce ag nest pas ncessaire pour les systmes dexploitation conformes la norme POSIX. Cependant, il est prfrable de lutiliser lors de louverture de chiers binaires si lon veut que le programme soit portable sur les autres systmes dexploitation. Permet de vider automatiquement le chier lorsquune ouverture en criture est demande. Permet douvrir le chier en mode ajout lorsquune ouverture en criture est demande. Dans ce mode, le pointeur de chier est systmatiquement positionn en n de chier avant chaque criture. Ainsi, les critures se font les unes la suite des autres, toujours la n du chier, et quelles que soient les oprations qui peuvent avoir lieu sur le chier entre-temps. Permet douvrir le chier en criture et de positionner le pointeur de chier la n de celui-ci. Notez que ce mode de fonctionnement se distingue du mode app par le fait que si un repositionnement a lieu entre deux critures la deuxime criture ne se fera pas forcment la n du chier.

trunc app

ate

Le troisime jeu de constantes dnit les diverses directions quil est possible dutiliser lors dun repositionnement dun pointeur de chier. Le type de ces constantes, savoir le type seekdir, est une numration dont les valeurs sont dcrites dans le tableau ci-dessous :

361

Chapitre 15. Les ux dentre / sortie Tableau 15-3. Directions de dplacement dans un chier Constante
beg

Signication Le dplacement de fait par rapport au dbut du chier. Le dcalage spci dans les fonctions de repositionnement doit tre positif ou nul, la valeur 0 correspondant au dbut du chier. Le dplacement se fait relativement la position courante. Le dcalage spci dans les fonctions de repositionnement peut donc tre ngatif, positif ou nul (auquel cas aucun dplacement nest effectu). Le dplacement se fait relativement la n du chier. Le dcalage fourni dans les fonctions de repositionnement doit tre positif ou nul, la valeur 0 correspondant la n de chier.

cur

end

Enn, les constantes de type iostate permettent de dcrire les diffrents tats dans lequel un ux dentre / sortie peut se trouver. Il sagit, encore une fois, dun champ de bits, et plusieurs combinaisons sont possibles. Tableau 15-4. tats des ux dentre / sortie Constante
goodbit eofbit

Signication Cette constante correspond ltat normal du ux, lorsquil ne sest produit aucune erreur. Ce bit est positionn dans la variable dtat du ux lorsque la n du ux a t atteinte, soit parce quil ny a plus de donnes lire, soit parce quon ne peut plus en crire. Ce bit est positionn dans la variable dtat du ux lorsquune erreur logique sest produite lors dune opration de lecture ou dcriture. Ceci peut avoir lieu lorsque les donnes crites ou lues sont incorrectes. Ce bit est positionn lorsquune erreur fatale sest produite. Ce genre de situation peut se produire lorsquune erreur a eu lieu au niveau matriel (secteur dfectueux dun disque dur ou coupure rseau par exemple).

failbit

badbit

Les diffrentes variables dtat des ux dentre / sortie peuvent tre manipules laide de ces constantes et des accesseurs de la classe ios_base. Les mthodes les plus importantes sont sans doute celles qui permettent de modier les options de formatage pour le ux dentre / sortie. La mthode flags permet de rcuprer la valeur de la variable dtat contenant les options de formatage du ux. Cette mthode dispose galement dune surcharge qui permet de spcier une nouvelle valeur pour cette variable dtat, et qui retourne la valeur prcdente. Il est aussi possible de xer et de dsactiver les options de formatage indpendamment les unes des autres laide des mthodes setf et unsetf. La mthode setf prend en paramtre les nouvelles options qui doivent tre ajoutes au jeu doptions dj actives, avec, ventuellement, un masque permettant de rinitialiser certaines autres options. On emploiera gnralement un masque lorsque lon voudra xer un paramtre parmi plusieurs paramtres mutuellement exclusifs, comme la base de numrotation utilise par exemple. La mthode unsetf prend quant elle le masque des options qui doivent tre supprimes du jeu doptions utilis par le ux en paramtre. Outre les mthodes de gestion des options de formatage, la classe ios_base dnit deux surcharges pour chacune des mthodes precision et width. Ces mthodes permettent respectivement de lire et de xer la prcision avec laquelle les nombres virgule doivent tre crits et la largeur minimale

362

Chapitre 15. Les ux dentre / sortie des conversions des nombres lors des critures. La plupart des options que lon peut xer sont permanentes, cest--dire quelles restent actives jusqu ce quon spcie de nouvelles options. Cependant, ce nest pas le cas du paramtre de largeur que lon renseigne grce la mthode width. En effet, chaque opration dcriture rinitialise ce paramtre la valeur 0. Il faut donc spcier la largeur minimale pour chaque donne crite sur le ux. Exemple 15-3. Modication des options de formatage des ux
#include <iostream> using namespace std; // Affiche un boolen, un nombre entier et un nombre virgule : void print(bool b, int i, float f) { cout << b << " " << i << " " << f << endl; } int main(void) { // Affiche avec les options par dfaut : print(true, 35, 3105367.9751447); // Passe en hexadcimal : cout.unsetf(ios_base::dec); cout.setf(ios_base::hex); print(true, 35, 3105367.9751447); // Affiche la base des nombres et // affiche les boolens textuellement : cout.setf(ios_base::boolalpha); cout.setf(ios_base::showbase); print(true, 35, 3105367.9751447); // Affiche un flottant en notation virgule fixe // avec une largeur minimale de 16 caractres : cout << "***"; cout.width(16); cout.setf(ios_base::fixed, ios_base::floatfield); cout << 315367.9751447; cout << "***" << endl; // Recommence en fixant la prcision // 3 chiffres et la largeur 10 caractres : cout << "***"; cout.precision(3); cout.width(10); cout << 315367.9751447; cout << "***" << endl; return 0; }

Note : On prendra bien garde au fait que la largeur des champs dans lesquels les donnes sont crites est une largeur minimale, pas une largeur maximale. En particulier, cela signigie que les critures ne sont pas tronques si elles sont plus grande que cette largeur. On devra donc faire extrmement attention ne pas provoquer de dbordements lors des critures. On noubliera pas de sassurer de la cohrence des paramtres du ux lorsquon modie la valeur dune option. Par exemple, dans lexemple prcdent, il faut dsactiver lemploi de la numrotation dcimale lorsque lon demande utiliser la base hexadcimale. Cette opration a t faite

363

Chapitre 15. Les ux dentre / sortie


explicitement ici pour bien montrer son importance, mais elle aurait galement pu tre ralise par lemploi dun masque avec la constante ios_base::basefield. Lexemple prcdent montre comment utiliser un masque avec lappel setf pour xer la reprsentation des nombres virgule.

La classe ios_base fournit galement un certain nombre de services gnraux au programmeur et ses classes drives. La mthode sync_with_stdio permet de dterminer, pour un ux dentre / sortie standard, sil est synchronis avec le ux sous-jacent ou si des donnes se trouvent encore dans son tampon. Lorsquelle est appele avec le paramtre false ds le dbut du programme, elle permet de dcorrler le fonctionnement du ux C++ et du ux standard sous-jacent. Pour tous les autres appels, cette mthode ignore le paramtre qui lui est fourni. La mthode register_callback permet denregistrer une fonction de rappel qui sera appele par la classe ios_base lorsque des vnements susceptibles de modier notablement le comportement du ux se produisent. Ces fonctions de rappel peuvent recevoir une valeur entire en paramtre qui peut tre utilise pour rfrencer des donnes prives contenant des paramtres plus complexes. Les mthodes xalloc, iword et pword sont fournies an de permettre de stocker ces donnes prives et de les retrouver facilement laide dun indice, qui peut tre la valeur passe en paramtre la fonction de rappel. Ces mthodes permettent de rcuprer des rfrences sur des valeurs de type long et sur des pointeurs de type void. Enn, la classe ios_base fournit une classe de base pour les exceptions que les classes de ux pourront utiliser an de signaler une erreur et une classe permettant dinitialiser les objets cin, cout, cerr, clog et leurs semblables pour les caractres larges. Toutes ces fonctionnalits ne sont gnralement pas dune trs grande utilit pour les programmeurs et sont en ralit fournie pour faciliter limplmentation des classes de ux de la bibliothque standard. Enn, la classe ios_base fournit les mthodes getloc et imbue qui permettent respectivement de rcuprer la locale utilise par le ux et den xer une autre. Cette locale est utilise par le ux pour dterminer la manire de reprsenter et de lire les nombres et pour effectuer les entres / sorties formates en fonction des paramtres de langue et des conventions locales du pays o le programme est excut. Les notions de locale et de paramtres internationaux seront dcrits en dtail dans le Chapitre 16.

15.3.2. La classe basic_ios


La classe template basic_ios fournit toutes les fonctionnalits communes toutes les classes de ux de la bibliothque dentre / sortie. Cette classe drive de la classe ios_base et apporte tous les mcanismes de gestion des tampons pour les classes de ux. La classe basic_ios est dclare comme suit dans len-tte ios :
template <class charT, class traits = char_traits<charT> > class basic_ios : public ios_base { // Constructeur et destructeur : protected: basic_ios(); void init(basic_streambuf<charT,traits> *flux); public: // Types de typedef typedef typedef

donnes : charT char_type; typename traits::int_type int_type; typename traits::pos_type pos_type;

364

Chapitre 15. Les ux dentre / sortie


typedef typename traits::off_type off_type; typedef traits traits_type; // Constructeur publique, destructeur et opration de copie : explicit basic_ios(basic_streambuf<charT,traits> *flux); virtual ~basic_ios(); basic_ios &copyfmt(const basic_ios &); // Mthodes de gestion des tampons : basic_streambuf<charT,traits> *rdbuf() const; basic_streambuf<charT,traits> *rdbuf( basic_streambuf<charT,traits> *tampon); // Mthodes de gestion des exceptions : iostate exceptions() const; void exceptions(iostate except); // Accesseurs : operator void*() const bool operator!() const iostate rdstate() const; void clear(iostate statut = goodbit); void setstate(iostate statut); bool good() const; bool eof() const; bool fail() const; bool bad() const; char_type fill() const; char_type fill(char_type c); basic_ostream<charT,traits> *tie() const; basic_ostream<charT,traits> *tie( basic_ostream<charT,traits> *flux); // Mthodes de gestion des locales : locale imbue(const locale &loc); char narrow(char_type c, char defaut) const; char_type widen(char c) const; };

Le constructeur de base ainsi que la mthode init, destine associer un tampon un ux aprs sa construction, sont dclars en zone protge. Ainsi, il nest pas possible dinstancier et dinitialiser manuellement un ux avec un tampon. Cependant, la classe basic_ios fournit un constructeur en zone publique qui permet de crer un nouveau ux et de linitialiser la vole. Ce constructeur prend en paramtre ladresse dun tampon qui sera associ au ux aprs la construction. Remarquez que, bien quil soit ainsi parfaitement possible dinstancier un objet de type lune des instances de la classe template basic_ios, cela na pas grand intrt. En effet, cette classe ne fournit pas assez de fonctionnalits pour raliser des entres / sorties facilement sur le ux. La classe basic_ios est donc rellement destine tre utilise en tant que classe de base pour des classes plus spcialises dans les oprations dentre / sortie. Une fois initialiss, les ux sont associs aux tampons grce auxquels ils accdent aux mdias physiques pour leurs oprations dentre / sortie. Cependant, cette association nest pas ge et il est possible de changer de tampon a posteriori. Les manipulations de tampon sont effectues avec les deux surcharges de la mthode rdbuf. La premire permet de rcuprer ladresse de lobjet tampon

365

Chapitre 15. Les ux dentre / sortie courant et la deuxime den spcier un nouveau. Cette dernire mthode renvoie ladresse du tampon prcdent. Bien entendu, le fait de changer le tampon dun ux provoque sa rinitialisation. Les ux de la bibliothque standard peuvent signaler les cas derreurs aux fonctions qui les utilisent de diffrentes manires. La premire est simplement de renvoyer un code derreur (false ou le caractre de n de chier), et la deuxime est de lancer une exception drive de la classe dexception failure (dnie dans la classe de base ios_base). Ce comportement est paramtrable en fonction des types derreurs qui peuvent se produire. Par dfaut, les classes de ux nutilisent pas les exceptions, quelles que soient les erreurs rencontres. Toutefois, il est possible dactiver le mcanisme des exceptions individuellement pour chaque type derreur possible. La classe basic_ios gre pour cela un masque dexceptions qui peut tre rcupr et modi laide de deux mthodes surcharges. Ces mthodes sont les mthodes exceptions. La premire version renvoie le masque courant et la deuxime permet de xer un nouveau masque. Les masques dexceptions sont constitus de combinaisons logiques des bits dtat des ux dnis dans la classe ios_base ( savoir goodbit, eofbit, failbit et badbit). Le fait de changer le masque dexceptions rinitialise ltat du ux. La classe basic_ios fournit galement tout un ensemble daccesseurs grce auxquels il est possible de rcuprer ltat courant du ux. Ces accesseurs sont principalement destins faciliter la manipulation du ux et simplier les diffrentes expressions dans lesquelles il est utilis. Par exemple, loprateur de transtypage vers le type pointeur sur void permet de tester la validit du ux comme sil sagissait dun pointeur. Cet oprateur retourne en effet une valeur non nulle si le ux est utilisable (cest--dire si la mthode fail renvoie false. De mme, loprateur de ngation operator! renvoie la mme valeur que la mthode fail. Comme vous laurez sans doute compris, la mthode fail indique si le ux (et donc le tampon contrl par ce ux) est dans un tat correct. En pratique, cette mthode renvoie true ds que lun des bits ios_base::failbit ou ios_base::badbit est positionn dans la variable dtat du ux. Vous pourrez faire la distinction entre ces deux bits grce la mthode bad, qui elle ne renvoie true que si le bit ios_base::badbit est positionn. Les autres mthodes de lecture de ltat du ux portent des noms explicites et leur signication ne doit pas poser de problme. On prendra toutefois garde bien distinguer la mthode clear, qui permet de rinitialiser ltat du ux avec le masque de bits pass en paramtre, de la mthode setstate, qui permet de positionner un bit complmentaire. Ces deux mthodes sont susceptibles de lancer des exceptions si le nouvel tat du ux le requiert et si son masque dexceptions lexige. Le dernier accesseur utile pour le programmeur est laccesseur fill. Cet accesseur permet de lire la valeur du caractre de remplissage utilis lorsque la largeur des champs est suprieure la largeur des donnes qui doivent tre crites sur le ux de sortie. Par dfaut, ce caractre est le caractre despacement.
Note : Les deux surcharges de la mthode tie permettent de stocker dans le ux un pointeur sur un ux de sortie standard avec lequel les oprations dentre / sortie doivent tre synchronises. Ces mthodes sont utilises en interne par les mthodes dentre / sortie des classes drives de la classe basic_ios et ne sont pas rellement utiles pour les programmeurs. En gnral donc, seule les classes de la bibliothque standard les appelleront.

Enn, la classe basic_ios prend galement en compte la locale du ux dans tous ses traitements. Elle rednit donc la mthode imbue an de pouvoir dtecter les changement de locale que lutilisateur peut faire. Bien entendu, la mthode getloc est hrite de la classe de base ios_base et permet toujours de rcuprer la locale courante. De plus, la classe basic_ios dnit deux mthodes permettant de raliser les conversions entre le type de caractre char et le type de caractre fourni en paramtre template. La mthode widen permet, comme son nom lindique, de convertir un caractre de type char en un caractre du type template du ux. Inversement, la mthode narrow permet de conver-

366

Chapitre 15. Les ux dentre / sortie tir un caractre du type de caractre du ux en un caractre de type char. Cette mthode prend en paramtre le caractre convertir et la valeur par dfaut que doit prendre le rsultat en cas dchec de la conversion.

15.4. Les ux dentre / sortie


La plupart des fonctionnalits des ux dentre / sortie sont implmentes au niveau des classes template basic_ostream et basic_istream. Ces classes drivent toutes deux directement de la classe basic_ios, dont elles hritent de toutes les fonctionnalits de gestion des tampons et de gestion dtat. Les classes basic_ostream et basic_istream seront sans doute les classes de ux que vous utiliserez le plus souvent, car cest leur niveau que sont dnies toutes les fonctionnalits de lecture et dcriture sur les ux, aussi bien pour les donnes formates telles que les entiers, les ottants ou les chanes de caractres, que pour les critures de donnes brutes. De plus, les ux dentre / sortie standards cin, cout, cerr et clog sont tous des instances de ces classes. La bibliothque standard dnit galement une classe capable de raliser la fois les oprations de lecture et dcriture sur les ux : la classe basic_iostream. En fait, cette classe drive simplement des deux classes basic_istream et basic_ostream, et regroupe donc toutes les fonctionnalits de ces deux classes.

15.4.1. La classe de base basic_ostream


La classe basic_ostream fournit toutes les fonctions permettant deffectuer des critures sur un ux de sortie, que ces critures soient formates ou non. Elle est dclare comme suit dans len-tte ostream :
template <class charT, class traits = char_traits<charT> > class basic_ostream : virtual public basic_ios<charT, traits> { public: // Les types de donnes : typedef charT char_type; typedef typename traits::int_type int_type; typedef typename traits::pos_type pos_type; typedef typename traits::off_type off_type; typedef traits traits_type; // Le constructeur et le destructeur : explicit basic_ostream(basic_streambuf<char_type, traits> *tampon); virtual ~basic_ostream(); // Les oprations dcritures formates : basic_ostream<charT, traits> &operator<<(bool); basic_ostream<charT, traits> &operator<<(short); basic_ostream<charT, traits> &operator<<(unsigned short); basic_ostream<charT, traits> &operator<<(int); basic_ostream<charT, traits> &operator<<(unsigned int); basic_ostream<charT, traits> &operator<<(long); basic_ostream<charT, traits> &operator<<(unsigned long); basic_ostream<charT, traits> &operator<<(float); basic_ostream<charT, traits> &operator<<(double);

367

Chapitre 15. Les ux dentre / sortie


basic_ostream<charT, traits> &operator<<(long double); basic_ostream<charT, traits> &operator<<(void *); basic_ostream<charT, traits> &operator<< (basic_streambuf<char_type, traits> *tampon); // Classe de gestion des exceptions pour les oprateurs dcritures formates : class sentry { public: explicit sentry(basic_ostream<charT, traits> &); ~sentry(); operator bool(); }; // Les oprations dcritures non formates : basic_ostream<charT, traits> &put(char_type); basic_ostream<charT, traits> &write(const char_type *p, streamsize taille); // Les oprations de gestion du tampon : basic_ostream<charT, traits> &flush(); pos_type tellp(); basic_ostream<charT, traits> &seekp(pos_type); basic_ostream<charT, traits> &seekp(off_type, ios_base::seekdir); // Les oprations de gestion des manipulateurs : basic_ostream<charT, traits> &operator<< (basic_ostream<charT, traits> & (*pf)( basic_ostream<charT, traits> &)); basic_ostream<charT, traits> &operator<< (basic_ios<charT, traits> & (*pf)(basic_ios<charT, traits> &)); basic_ostream<charT, traits> &operator<< (ios_base & (*pf)(ios_base &)); };

Comme vous pouvez le constater, le constructeur de cette classe prend en paramtre un pointeur sur lobjet tampon dans lequel les critures devront tre ralises. Vous pouvez donc construire un ux de sortie partir de nimporte quel tampon, simplement en fournissant ce tampon en paramtre au constructeur. Cependant, il ne faut pas procder ainsi en gnral, mais utiliser plutt les classes drives de la classe basic_ostream et spcialises dans les critures sur chiers et dans des chanes de caractres. Les critures formates sont ralises par lintermdiaire de diffrentes surcharges de loprateur dinsertion operator<<, dont il existe une version pour chaque type de donne de base du langage. Ainsi, lcriture dune valeur dans le ux de sortie se fait extrmement simplement :
// criture dune chane de caractres sur le flux de sortie standard : cout << "Voici la valeur dun entier :\n"; // criture dun entier sur le flux de sortie standard : cout << 45;

Vous constaterez que, grce aux mcanismes de surcharge, ces critures se font exactement de la mme manire pour tous les types de donnes. On ne peut faire plus simple...

368

Chapitre 15. Les ux dentre / sortie


Note : Les oprations de formatage prennent en compte toutes les options de formatage qui sont stockes dans la classe de base ios_base. En gnral, les oprations dcriture ne modient pas ces options. Toutefois, la largeur minimale des champs dans lesquels les rsultats sont formats est systmatiquement rinitialise 0 aprs chaque criture. Il est donc ncessaire, lorsque lon ralise plusieurs critures formates dans un ux de sortie, de spcier pour chaque valeur sa largeur minimale si elle ne doit pas tre gale 0. Autrement dit, il faut redire la largeur de chaque champ, mme sils utilisent tous la mme largeur minimale. Les oprations de formatage utilisent galement les conventions locales du pays o le programme est excut, conventions qui sont dnies dans la locale incluse dans le ux de sortie. Les notions de locale seront dtailles dans le Chapitre 16.

Bien entendu, il est possible de dnir de nouvelles surcharges de loprateur dinsertion pour les types dnis par lutilisateur, ce qui permet dtendre linni les possibilits de cette classe. Ces surcharges devront obligatoirement tre dnies lextrieur de la classe template basic_ostream et, si lon veut les crire de manire gnrique, elles devront galement tre des fonctions template paramtres par le type de caractre du ux sur lequel elles travaillent. Vous noterez la prsence dune classe sentry dans la classe basic_ostream. Cette classe est une classe utilitaire permettant de raliser les initialisations qui doivent prcder toutes les oprations dcriture au sein des surcharges de loprateur dinsertion. Entre autres oprations, le constructeur de cette classe peut synchroniser le ux de sortie standard encapsul dans le ux grce la mthode tie de la classe de base basic_ios, et prendre toutes les mesures devant prcder les critures sur le ux. Vous devrez donc toujours utiliser un objet local de ce type lorsque vous crirez une surcharge de loprateur operator<< pour vos propres types. Les critures ne devront tre ralises que si linitialisation a russi, ce qui peut tre vri simplement en comparant lobjet local de type sentry avec la valeur true. Exemple 15-4. Dnition dun nouvel oprateur dinsertion pour un ux de sortie
#include <iostream> #include <string> using namespace std; // Dfinition dun type de donne priv : struct Personne { string Nom; string Prenom; int Age; // En centimtres. int Taille; }; // Dfinition de loprateur dcriture pour ce type : template <class charT, class Traits> basic_ostream<charT, Traits> &operator<<( basic_ostream<charT, Traits> &flux, const Personne &p) { // Inialisation du flux de sortie : typename basic_ostream<charT, Traits>::sentry init(flux); if (init) { // criture des donnes :

369

Chapitre 15. Les ux dentre / sortie


int Metres = p.Taille / 100; int Reste = p.Taille % 100; flux << p.Prenom << " " << p.Nom << " mesure " << Metres << "m" << Reste << " (" << p.Age << " an"; if (p.Age > 1) flux << "s"; flux << ")"; } return flux; } int main(void) { // Construit une nouvelle personne : Personne p; p.Nom = "Dupont"; p.Prenom = "Jean"; p.Age = 28; p.Taille = 185; // Affiche les caractristiques de cette personne : cout << p << endl; return 0; }

Note : Lutilisation de lobjet local de type sentry comme un boolen est autorise parce que la classe sentry dnit un oprateur de transtypage vers le type bool. Le constructeur de la classe sentry est susceptible de lancer des exceptions, selon la conguration du masque dexceptions du ux de sortie avec lequel on linitialise.

Les critures de donnes brutes ne disposent bien entendu pas de surcharges pour chaque type de donne, puisquil sagit dans ce cas dcrire les donnes directement sur le ux de sortie, sans les formater sous forme textuelle. Ces critures sont donc ralises par lintermdiaire de mthodes ddies qui effectuent soit lcriture dun caractre unique, soit lcriture dun tableau de caractres complet. Pour crire un unique caractre sur le ux de sortie, vous pouvez utiliser la mthode put. Pour lcriture dun bloc de donnes en revanche, il faut utiliser la mthode write, qui prend en paramtre un pointeur sur la zone de donnes crire et la taille de cette zone. Exemple 15-5. criture de donnes brutes sur un ux de sortie
#include <iostream> using namespace std; // Dfinition de quelques codes de couleurs // pour les terminaux ANSI : const char Rouge[] = {033, [, 3, 1, m}; const char Vert[] = {033, [, 3, 2, m}; const char Jaune[] = {033, [, 3, 3, m}; const char Reset[] = {033, [, m, 017}; int main(void) { // criture dun message color : cout.write(Rouge, sizeof(Rouge));

370

Chapitre 15. Les ux dentre / sortie


cout << "Bonjour "; cout.write(Vert, sizeof(Vert)); cout << "tout "; cout.write(Jaune, sizeof(Jaune)); cout << "le monde !" << endl; cout.write(Reset, sizeof(Reset)); return 0; }

Bien entendu, la classe basic_ostream fournit les mthodes ncessaires la gestion du tampon sousjacent. La mthode flush permet de synchroniser le tampon utilis par le ux (en appelant la mthode pubsync de ce dernier). La mthode tellp permet de lire la position courante dans le ux de sortie, et les deux surcharges de la mthode seekp permettent de modier cette position soit de manire absolue, soit de manire relative la position courante. Nous verrons un exemple dutilisation de ces mthodes dans la description des classes de ux pour les chiers. La classe basic_ostream dnit galement des surcharges de loprateur dinsertion capables de prendre en paramtre des pointeurs de fonctions. Ces mthodes ne constituent pas des oprations dcriture proprement parler, mais permettent de raliser des oprations sur les ux de sortie plus facilement laide de fonctions capables de les manipuler. En raison de cette proprit, ces fonctions sont couramment appeles des manipulateurs. En ralit, ces manipulateurs ne sont rien dautre que des fonctions prenant un ux en paramtre et ralisant des oprations sur ce ux. Les oprateurs operator<< prenant en paramtre ces manipulateurs les excutent sur lobjet courant *this. Ainsi, il est possible dappliquer ces manipulateurs un ux simplement en ralisant une criture du manipulateur sur ce ux, exactement comme pour les critures normales. La bibliothque standard dnit tout un jeu de manipulateurs extrmement utiles pour dnir les options de formatage et pour effectuer des oprations de base sur les ux. Grce ces manipulateurs, il nest plus ncessaire dutiliser la mthode setf de la classe ios_base par exemple. Par exemple, le symbole endl utilis pour effectuer un retour la ligne dans les oprations dcriture sur les ux de sortie nest rien dautre quun manipulateur, dont la dclaration est la suivante :
template <class charT, class Traits> basic_ostream<charT, Traits> &endl( basic_ostream<charT, Traits> &flux);

et dont le rle est simplement dcrire le caractre de retour la ligne et dappeler la mthode flush du ux de sortie. Il existe des manipulateurs permettant de travailler sur la classe de base ios_base ou sur ses classes drives comme la classe basic_ostream par exemple, do la prsence de plusieurs surcharges de loprateur dinsertion pour ces diffrents manipulateurs. Il existe galement des manipulateurs prenant des paramtres et renvoyant un type de donnes spcial pour lequel un oprateur dcriture a t dni, et qui permettent de raliser des oprations plus complexes ncessitant des paramtres complmentaires. Les manipulateurs sont dnis, selon leur nature, soit dans len-tte de dclaration du ux, soit dans len-tte ios, soit dans len-tte iomanip. Le tableau suivant prsente les manipulateurs les plus simples qui ne prennent pas de paramtre : Tableau 15-5. Manipulateurs des ux de sortie Manipulateur
endl ends

Fonction Envoie un caractre de retour la ligne sur le ux et synchronise le tampon par un appel la mthode flush. Envoie un caractre nul terminal de n de ligne sur le ux.

371

Chapitre 15. Les ux dentre / sortie Manipulateur


flush boolalpha noboolalpha hex oct dec fixed scientific left right internal

Fonction Synchronise le tampon utilis par le ux par un appelle la mthode flush. Active le formatage des boolens sous forme textuelle. Dsactive le formatage textuel des boolens. Formate les nombres en base 16. Formate les nombres en base 8. Formate les nombres en base 10. Utilise la notation en virgule xe pour les nombres virgule. Utilise la notation en virgule ottante pour les nombres virgule. Aligne les rsultats gauche. Aligne les rsultats droite. Utilise le remplissage des champs avec des espaces complmentaires une position xe dtermine par la locale courante. quivalent right si la locale ne spcie aucune position de remplissage particulire. Indique la base de numrotation utilise. Nindique pas la base de numrotation utilise. Utilise le sparateur de virgule dans les nombres virgule, mme si la partie fractionnaire est nulle. Nutilise le sparateur de virgule que si la partie fractionnaire des nombres virgule ottante est signicative. crit systmatiquement le signe des nombres, mme sils sont positifs. Ncrit le signe des nombres que sils sont ngatifs. crit les exposants et les chiffres hexadcimaux en majuscule. crit les exposants et les chiffres hexadcimaux en minuscule. Effectue une opration de synchronisation du cache gr par le tampon du ux aprs chaque criture. Neffectue les oprations de synchronisation du cache gr par le tampon du ux que lorsque cela est explicitement demand.

showbase noshowbase showpoint noshowpoint showpos noshowpos uppercase nouppercase unitbuf nounitbuf

Les paramtres suivants sont un peu plus complexes, puisquils prennent des paramtres complmentaires. Ils renvoient un type de donne spcique chaque implmentation de la bibliothque standard et qui nest destin qu tre insr dans un ux de sortie laide de loprateur dinsertion : Tableau 15-6. Manipulateurs utilisant des paramtres Manipulateur
resetiosflags(ios_base::fmtflags)

Fonction Permet deffacer certains bits des options du ux. Ces bits sont spcis par une combinaison logique de constantes de type ios_base::fmtags. Permet de positionner certains bits des options du ux. Ces bits sont spcis par une combinaison logique de constantes de type ios_base::fmtags.

setiosflags(ios_base::fmtflags)

372

Chapitre 15. Les ux dentre / sortie Manipulateur


setbase(int base)

Fonction Permet de slectionner la base de numrotation utilise. Les valeurs admissibles sont 8, 10 et 16 respectivement pour la base octale, la base dcimale et la base hexadcimale. Permet de spcier la prcision (nombre de caractres signicatifs) des nombres formats. Permet de spcier la largeur minimale du champ dans lequel la donne suivante sera crite la prochaine opration dcriture sur le ux. Permet de spcier le caractre de remplissage utiliser lorsque la largeur des champs est infrieure la largeur minimale spcie dans les options de formatage.

setprecision(int) setw(int)

setfill(char_type)

Exemple 15-6. Utilisation des manipulateurs sur un ux de sortie


#include <iostream> #include <iomanip> using namespace std; int main(void) { // Affiche les boolens sous forme textuelle : cout << boolalpha << true << endl; // crit les nombres en hexadcimal : cout << hex << 57 << endl; // Repasse en base 10 : cout << dec << 57 << endl; // Affiche un flottant avec une largeur // minimale de 15 caractres : cout << setfill(*) << setw(15) << 3.151592 << endl; // Recommence mais avec un alignement gauche : cout << left << setw(15) << 3.151592 << endl; }

15.4.2. La classe de base basic_istream


La deuxime classe la plus utilise de la bibliothque dentre / sortie est sans doute la classe template basic_istream. linstar de la classe ostream, cette classe fournit toutes les fonctionnalits de lecture de donnes formates ou non partir dun tampon. Ce sont donc certainement les mthodes cette classe que vous utiliserez le plus souvent lorsque vous dsirerez lire les donnes dun ux. La classe basic_istream est dclare comme suit dans len-tte istream :
template <class charT, class traits = char_traits<charT> > class basic_istream : virtual public basic_ios<charT, traits> { public:

373

Chapitre 15. Les ux dentre / sortie


// Les types de donnes : typedef charT typedef typename traits::int_type typedef typename traits::pos_type typedef typename traits::off_type typedef traits

char_type; int_type; pos_type; off_type; traits_type;

// Le constructeur et destructeur : explicit basic_istream(basic_streambuf<charT, traits> *sb); virtual ~basic_istream(); // Les opration de gestion des entres formates : basic_istream<charT, traits> &operator>>(bool &n); basic_istream<charT, traits> &operator>>(short &n); basic_istream<charT, traits> &operator>>(unsigned short &n); basic_istream<charT, traits> &operator>>(int &n); basic_istream<charT, traits> &operator>>(unsigned int &n); basic_istream<charT, traits> &operator>>(long &n); basic_istream<charT, traits> &operator>>(unsigned long &n); basic_istream<charT, traits> &operator>>(float &f); basic_istream<charT, traits> &operator>>(double &f); basic_istream<charT, traits> &operator>>(long double &f); basic_istream<charT, traits> &operator>>(void * &p); basic_istream<charT, traits> &operator>> (basic_streambuf<char_type, traits> *sb); // Classe de gestion des exceptions pour les oprateurs dcritures formates : class sentry { public: explicit sentry(basic_istream<charT, traits> &flux, bool conserve = false); ~sentry(); operator bool(); }; // Les oprations de lecture des int_type get(); basic_istream<charT, traits> int_type peek(); basic_istream<charT, traits> basic_istream<charT, traits> basic_istream<charT, traits> streamsize donnes brutes : &get(char_type &c); &putback(char_type c); &unget(); &read(char_type *s, streamsize n); readsome(char_type *s, streamsize n);

basic_istream<charT, traits> &get(char_type *s, streamsize n); basic_istream<charT, traits> &get(char_type *s, streamsize n, char_type delim); basic_istream<charT, traits> &get( basic_streambuf<char_type, traits> &sb); basic_istream<charT, traits> &get( basic_streambuf<char_type, traits> &sb, char_type delim); basic_istream<charT, traits> &getline(char_type *s, streamsize n); basic_istream<charT, traits> &getline(char_type *s, streamsize n, char_type delim); basic_istream<charT, traits> &ignore (streamsize n = 1, int_type delim = traits::eof());

374

Chapitre 15. Les ux dentre / sortie

streamsize gcount() const; // Les oprations de gestion du tampon : int sync(); pos_type tellg(); basic_istream<charT, traits> &seekg(pos_type); basic_istream<charT, traits> &seekg(off_type, ios_base::seekdir); // Les oprations de gestion des manipulateurs : basic_istream<charT, traits> &operator>> (basic_istream<charT, traits> & (*pf)( basic_istream<charT, traits> &)); basic_istream<charT, traits> &operator>> (basic_ios<charT, traits> & (*pf)(basic_ios<charT, traits> &)); basic_istream<charT, traits> &operator>> (ios_base & (*pf)(ios_base &)); };

Tout comme la classe basic_ostream, le constructeur de la classe basic_istream prend en paramtre un pointeur sur lobjet grant le tampon dans lequel les critures devront tre effectues. Cependant, mme sil est possible de crer une instance de ux dentre simplement laide de ce constructeur, cela nest pas recommand puisque la bibliothque standard fournit des classes spcialises permettant de crer des ux de sortie orients chiers ou chanes de caractres. Lutilisation des diffrentes surcharges de loprateur dextraction des donnes formates operator>> ne devrait pas poser de problme. Le compilateur dtermine la surcharge utiliser en fonction du type des donnes lire, dtermin par la rfrence de variable fournie en paramtre. Cette surcharge rcupre alors les informations dans le tampon associ au ux, les interprte et crit la nouvelle valeur dans la variable. Bien entendu, tout comme pour la classe basic_ostream, il est possible dcrire de nouvelles surcharges de loprateur dextraction an de prendre en charge de nouveaux types de donnes. Idalement, ces surcharges devront tre galement des fonctions template paramtres par le type de caractre du ux sur lequel elles travaillent, et elles devront galement utiliser une classe dinitialisation sentry. Cette classe a principalement pour but dinitialiser le ux dentre, ventuellement en le synchronisant avec un ux de sortie standard dont la classe basic_ostream peut tre stocke dans le ux dentre laide de la mthode tie de la classe de base basic_ios, et en supprimant les ventuels caractres blancs avant la lecture des donnes. Notez que, contrairement la classe sentry des ux de sortie, le constructeur de la classe sentry des ux dentre prend un deuxime paramtre. Ce paramtre est un boolen qui indique si les caractres blancs prsents dans le ux de donnes doivent tre limins avant lopration de lecture ou non. En gnral, pour les oprations de lecture formates, ce sont des caractres non signicatifs et il faut effecivement supprimer ces caractres, aussi la valeur spcier pour ce second paramtre est-elle false. Comme cest aussi la valeur par dfaut, la manire dutiliser de la classe sentry dans les oprateurs dextraction est strictement identique celle de la classe sentry des oprateurs dinsertion de la classe basic_ostream. Exemple 15-7. criture dun nouvel oprateur dextraction pour un ux dentre
#include <iostream> #include <string>

375

Chapitre 15. Les ux dentre / sortie


using namespace std; // Dfinition dun type de donne priv : struct Personne { string Nom; string Prenom; int Age; // En centimtres. int Taille; }; // Dfinition de loprateur de lecture pour ce type : template <class charT, class Traits> basic_istream<charT, Traits> &operator>>( basic_istream<charT, Traits> &flux, Personne &p) { // Inialisation du flux de sortie : typename basic_istream<charT, Traits>::sentry init(flux); if (init) { // Lecture du prnom et du nom : flux >> p.Prenom; flux >> p.Nom; // Lecture de lge : flux >> p.Age; // Lecture de la taille en mtres : double Taille; flux >> Taille; // Conversion en centimtres ; p.Taille = (int) (Taille * 100 + 0.5); } return flux; } int main(void) { // Construit une nouvelle personne : Personne p; // Demande la saisie dune personne : cout << "Prnom Nom ge(ans) Taille(m) : "; cin >> p; // Affiche les valeurs lues : cout << endl; cout << "Valeurs saisies :" << endl; cout << p.Prenom << " " << p.Nom << " a " << p.Age << " ans et mesure " << p.Taille << " cm." << endl; return 0; }

Note : La classe sentry est galement utilise par les mthodes de lecture de donnes non formates. Pour ces mthodes, les caractres blancs sont importants et dans ce cas le second paramtre fourni au constructeur de la classe sentry est true. Comme pour la classe sentry de la classe basic_ostream, lutilisation de lobjet dinitialisation dans les tests est rendue possible par la prsence de loprateur de transtypage vers le type bool. La valeur retourne est true si linitialisation sest bien faite et false dans le cas contraire.

376

Chapitre 15. Les ux dentre / sortie


Remarquez galement que le constructeur de la classe sentry est susceptible de lancer des exceptions selon la conguration du masque dexceptions dans la classe de ux.

Les oprations de lecture de donnes non formates sont un peu plus nombreuses pour les ux dentre que les oprations dcriture non formates pour les ux de sortie. En effet, la classe basic_istream donne non seulement la possibilit de lire un caractre simple ou une srie de caractres, mais aussi de lire les donnes provenant du tampon de lecture et de les interprter en tant que lignes . Une ligne est en ralit une srie de caractres termine par un caractre spcial que lon nomme le marqueur de n de ligne. En gnral, ce marqueur est le caractre \n, mais il est possible de spcier un autre caractre. La lecture dun caractre unique dans le ux dentre se fait laide de la mthode get. Il existe deux surcharges de cette mthode, la premire ne prenant aucun paramtre et renvoyant le caractre lu, et la deuxime prenant en paramtre une rfrence sur la variable devant recevoir le caractre lu et ne renvoyant rien. Ces deux mthodes extraient les caractres quelles lisent du tampon dentre que le ux utilise. Si lon veut simplement lire la valeur du caractre suivant sans len extraire, il faut appeler la mthode peek. De plus, tout caractre extrait peut tre rinsr dans le ux dentre (pourvu que le tampon sous-jacent accepte cette opration) laide de lune des deux mthodes unget ou putback. Cette dernire mthode prend en paramtre le caractre qui doit tre rinsr dans le ux dentre. Notez que la rinsertion ne peut tre ralise que si le caractre fourni en paramtre est prcisment le dernier caractre extrait. Si lon dsire raliser la lecture dune srie de caractres au lieu de les extraire un un, il faut utiliser la mthode read. Cette mthode est la mthode de base pour les lectures non formates puisquelle lit les donnes brutes de fonderie, sans les interprter. Elle prend en paramtre un pointeur sur un tableau de caractres dans lequel les donnes seront crites et le nombre de caractres lire. Cette mthode ne vrie pas la taille du tableau spci, aussi celui-ci doit-il tre capable daccueillir le nombre de caractres demand. Il existe une variante de la mthode read, la mthode readsome, qui permet de lire les donnes prsentes dans le tampon gr par le ux sans accder au mdia que ce dernier prend en charge. Cette mthode prend galement en paramtre un pointeur sur la zone mmoire devant recevoir les donnes et le nombre de caractres dsir, mais, contrairement la mthode read, elle peut ne pas lire exactement ce nombre. En effet, la mthode readsome sarrte ds que le tampon utilis par le ux est vide, ce qui permet dviter les accs sur le priphrique auquel ce tampon donne accs. La mthode readsome renvoie le nombre de caractres effectivement lus. Les mthodes de lecture des lignes sont diviser en deux catgories. La premire catgorie, constitue de plusieurs surcharges de la mthode get, permet deffectuer une lecture des donnes du tampon jusqu ce que le tableau fourni en paramtre soit rempli ou quune n de ligne soit atteinte. La deuxime catgorie de mthodes est constitue des surcharges de la mthode getline. Ces mthodes se distinguent des mthodes get par le fait quelles nchouent pas lorsque la ligne lue (dlimiteur de ligne compris) remplit compltement le tableau fourni en paramtre dune part, et par le fait que le dlimiteur de ligne est extrait du tampon dentre utilis par le ux dautre part. Autrement dit, si une ligne complte (cest--dire avec son dlimiteur) a une taille exactement gale la taille du tableau fourni en paramtre, les mthodes get choueront alors que les mthodes getline russiront, car elles ne considrent pas le dlimiteur comme une information importante. Ceci revient dire que les mthodes getline interprtent compltement le caractre dlimiteur, alors que les mthodes get le traitent simplement comme le caractre auquel la lecture doit sarrter. Dans tous les cas, un caractre nul terminal est insr en lieu et place du dlimiteur dans le tableau fourni en paramtre et devant recevoir les donnes. Comme le deuxime paramtre de ces mthodes indique la dimension de ce tableau, le nombre de caractres lu est au plus cette dimension moins un. Le nombre de caractres extraits du tampon dentre est quant lui rcuprable grce la mthode gcount. Remarquez que le caractre de n de ligne est compt dans le nombre de caractres extraits

377

Chapitre 15. Les ux dentre / sortie pour les mthodes getline, alors quil ne lest pas pour les mthodes get puisque ces dernires ne lextraient pas du tampon. Enn, il est possible de demander la lecture dun certain nombre de caractres et de les passer sans en rcuprer la valeur. Cette opration est ralisable laide de la mthode ignore, qui ne prend donc pas de pointeurs sur la zone mmoire o les caractres lus doivent tre stocks puisquils sont ignors. Cette mthode lit autant de caractres que spci, sauf si le caractre dlimiteur indiqu en deuxime paramtre est rencontr. Dans ce cas, ce caractre est extrait du tampon dentre, ce qui fait que la mthode ignore se comporte exactement comme les mthodes getline. Exemple 15-8. Lectures de lignes sur le ux dentre standard
#include <iostream> #include <sstream> using namespace std; int main(void) { // Tableau devant recevoir une ligne : char petit_tableau[10]; // Lit une ligne de 9 caractres : cout << "Saisissez une ligne :" << endl; cin.getline(petit_tableau, 10); if (cin.fail()) cout << "Ligne trop longue !" << endl; cout << "Lu : ***" << petit_tableau << "***" << endl; // Lit une ligne de taille arbitraire via un tampon : cout << "Saisissez une autre ligne :" << endl; stringbuf s; cin.get(s); // Affiche la ligne lue : cout << "Lu : ***" << s.str() << "***"; // Extrait le caractre de saut de ligne // et ajoute-le au flux de sortie standard : cout << (char) cin.get(); return 0; }

Note : Remarquez que le caractre de saut de ligne tant lu, il est ncessaire de saisir deux retours de chariot successifs pour que la mthode getline renvoie son rsultat. Comme pour toutes les mthodes de lectures formates, ce caractre interrompt la lecture dans le ux dentre standard du programme et se trouve donc encore dans le tampon dentre lors de la lecture suivante. Cela explique que dans le cas de lectures successives, il faut extraire ce caractre du ux dentre manuellement, par exemple laide de la mthode get. Cest ce que cet exemple ralise sur sa dernire ligne pour lenvoyer sur le ux de sortie standard. De plus, on ne peut pas prvoir, a priori, quelle sera la taille des lignes saisies par lutilisateur. On ne procdera donc pas comme indiqu dans cet exemple pour effectuer la lecture de lignes en pratique. Il est en effet plus facile dutiliser la fonction getline, que lon a dcrit dans la Section 14.1.8 dans le cadre du type basic_string. En effet, cette fonction permet de lire une ligne complte sans avoir se soucier de sa longueur maximale et de stocker le rsultat dans une basic_string.

378

Chapitre 15. Les ux dentre / sortie La classe basic_istream dispose galement de mthodes permettant de manipuler le tampon quelle utilise pour lire de nouvelles donnes. La mthode sync permet de synchroniser le tampon dentre avec le mdia auquel il donne accs, puisquelle appelle la mthode pubsync de ce tampon. Pour les ux dentre, cela na pas rellement dimportance parce que lon ne peut pas crire dedans. La mthode tellg permet de dterminer la position du pointeur de lecture courant, et les deux surcharges de la mthode seekg permettent de repositionner ce pointeur. Nous verrons un exemple dutilisation de ces mthodes dans la description des classes de ux pour les chiers. Enn, les ux dentre disposent galement de quelques manipulateurs permettant de les congurer simplement laide de loprateur operator>>. Ces manipulateurs sont prsents dans le tableau ci-dessous : Tableau 15-7. Manipulateurs des ux dentre Manipulateur
boolalpha noboolalpha hex oct dec skipws noskipws ws

Fonction Active linterprtation des boolens sous forme de textuelle. Dsactive linterprtation des boolens sous forme textuelle. Utilise la base 16 pour linterprtation des nombres entiers. Utilise la base 8 pour linterprtation des nombres entiers. Utilise la base 10 pour linterprtation des nombres entiers. Ignore les espaces lors des entres formates. Conserve les espaces lors des entres formates. Supprime tous les espaces prsents dans le ux dentre jusquau premier caractre non blanc.

Ces manipulateurs sutilisent directement laide de loprateur operator>>, exactement comme les manipulateurs de la classe basic_ostream sutilisent avec loprateur dinsertion normal.

15.4.3. La classe basic_iostream


La bibliothque standard dnit dans len-tte iostream la classe template basic_iostream an de permettre la fois les oprations dcriture et les oprations de lecture sur les ux. En fait, cette classe nest rien dautre quune classe drive des deux classes basic_ostream et basic_istream qui fournissent respectivement, comme on la vu, toutes les fonctionnalits de lecture et dcriture sur un tampon. La classe basic_iostream ne comporte pas dautres mthodes quun constructeur et un destructeur, qui servent uniquement initialiser et dtruire les classes de base basic_ostream et basic_istream. Lutilisation de cette classe ne doit donc pas poser de problme particulier et je vous invite vous rfrer aux descriptions des classes de base si besoin est.
Note : Tout comme ses classes de base, la classe basic_iostream sera rarement utilise directement. En effet, elle dispose de classes drives spcialises dans les oprations dcriture et de lecture sur chiers ou dans des chanes de caractres, classes que lon prsentera dans les sections suivantes. Ce sont ces classes que lon utilisera en pratique lorsque lon dsirera crer un nouveau ux pour lire et crire dans un chier ou dans une basic_string. Vous aurez peut-tre remarqu que les classes basic_ostream et basic_istream utilisent un hritage virtuel pour rcuprer les fonctionnalits de la classe de base basic_ios. La raison en est que la classe basic_iostream ralise un hritage multiple sur ses deux classes de base et que les donnes de la classe basic_ios ne doivent tre prsente quen un seul exemplaire dans les ux dentre / sortie. Cela implique que les constructeurs des classes drives de la classe

379

Chapitre 15. Les ux dentre / sortie


basic_iostream prenant des paramtres doivent appeler explicitement les constructeurs de toutes leur classes de base. Voyez la Section 7.6 pour plus de dtails sur les notions dhritage multiple et de classes virtuelles.

15.5. Les ux dentre / sortie sur chanes de caractres


An de donner la possibilit aux programmeurs deffectuer les oprations de formatage des donnes en mmoire aussi simplement quavec les classes de gestion des ux dentre / sortie standards, la bibliothque dentre / sortie dnit trois classes de ux capables de travailler dans des chanes de caractres de type basic_string. Ces classes sont les classes basic_ostringstream, pour les critures dans les chanes de caractres, basic_istringstream, pour les lectures de donnes stockes dans les chanes de caractres, et basic_stringstream, pour les oprations la fois dcriture et de lecture. Ces classes drivent respectivement des classes de ux basic_istream, basic_ostream et basic_iostream et reprennent donc leur compte toutes les fonctions de formatage et dcriture de ces classes. Les critures et les lectures de donnes en mmoire se font donc, grce ces classes, aussi facilement quavec les ux dentre / sortie standards, et ce de manire compltement transparente. En fait, les classes de ux orientes chanes de caractres fonctionnent exactement comme leurs classes de base, car toutes les fonctionnalits de gestion des chanes de caractres sont encapsules au niveau des classes de gestion des tampons qui ont t prsentes au dbut de ce chapitre. Cependant, elles disposent de mthodes spciques qui permettent de manipuler les chanes de caractres sur lesquelles elles travaillent. Par exemple, la classe basic_ostringstream est dclare comme suit dans len-tte sstream :
template <class charT, class traits = char_traits<charT>, class Allocator = allocator<charT> > class basic_ostringstream : public basic_ostream<charT, traits> { public: // Les types de donnes : typedef charT char_type; typedef typename traits::int_type int_type; typedef typename traits::pos_type pos_type; typedef typename traits::off_type off_type; // Les constructeurs et destructeurs : explicit basic_ostringstream(ios_base::openmode mode = ios_base::out); explicit basic_ostringstream( const basic_string<charT, traits, Allocator> &chaine, ios_base::openmode mode = ios_base::out); virtual ~basic_ostringstream(); // Les mthodes de gestion de la chane de caractres : basic_stringbuf<charT, traits, Allocator> *rdbuf() const; basic_string<charT, traits, Allocator> str() const; void str(const basic_string<charT, traits, Allocator> &chaine); };

380

Chapitre 15. Les ux dentre / sortie Les classes basic_istringstream et basic_stringstream sont dclares de manire identique, ceci prs que les classes de base et les valeurs par dfaut pour les modes douverture du tampon sur la chane de caractres ne sont pas les mmes. Comme vous pouvez le constater, il est possible de construire un ux dentre / sortie sur une chane de caractres de diffrentes manires. La premire mthode est de construire un objet ux, puis de prciser, pour les ux dentre, la chane de caractres dans laquelle les donnes lire se trouvent laide de la mthode str. La deuxime mthode est tout simplement de fournir tous les paramtres en une seule fois au constructeur. En gnral, les valeurs par dfaut spcies dans les constructeurs correspondent ce que lon veut faire avec les ux, ce qui fait que la construction de ces ux est extrmement simple. Une fois construit, il est possible de raliser toutes les oprations que lon dsire sur le ux. Dans le cas des ux dentre, il est ncessaire que le ux ait t initialis avec une chane de caractres contenant les donnes lire, et lon ne cherche gnralement pas rcuprer cette chane aprs usage du ux. Pour les ux de sortie, cette initialisation est inutile. En revanche, le rsultat des oprations de formatage sera gnralement rcupr laide de la mthode str une fois celles-ci ralises. Exemple 15-9. Utilisation de ux dentre / sortie sur chanes de caractres
#include <iostream> #include <sstream> #include <string> using namespace std; int main(void) { // Lit une ligne en entre : cout << "Entier Rel Chane : "; string input; getline(cin, input); // Interprte la ligne lue : istringstream is(input); int i; double d; string s; is >> i >> d; is >> ws; getline(is, s); // Formate la rponse : ostringstream os; os << "La rponse est : " << endl; os << s << " " << 2*i << " " << 2*d << endl; // Affiche la chane de la rponse : cout << os.str(); return 0; }

Comme lexemple prcdent vous le montre, lutilisation des ux dentre / sortie de la bibliothque standard sur les chanes de caractres est rellement aise. Comme ces oprations ne peuvent tre ralises quen mmoire, cest dire en dehors du contexte du systme dexploitation utilis, les classes de ux de la bibliothque restent utiles mme si les oprations dentre / sortie du systme se font de telle manire que les objets cin et cout ne sont pratiquement pas utilisables.

381

Chapitre 15. Les ux dentre / sortie

15.6. Les ux dentre / sortie sur chiers


Les classes dentre / sortie sur les chiers sont implmentes de manire similaire aux classes dentre / sortie sur les chanes de caractres, ceci prs que leurs mthodes spciques permettent de manipuler un chier au lieu dune chane de caractres. Ainsi, la classe basic_ofstream drive de basic_ostream, la classe basic_ifstream de la classe basic_istream, et la classe basic_fstream de la classe basic_iostream. Toutes ces classes sont dclares dans len-tte fstream. Vous trouverez titre dexemple la dclaration de la classe basic_ofstream tel quil apparat dans cet en-tte :
template <class charT, class traits = char_traits<charT> > class basic_ofstream : public basic_ostream<charT, traits> { public: // Les types de donnes : typedef charT char_type; typedef typename traits::int_type int_type; typedef typename traits::pos_type pos_type; typedef typename traits::off_type off_type; // Les constructeurs et destructeurs : basic_ofstream(); explicit basic_ofstream(const char *nom, ios_base::openmode mode = ios_base::out | ios_base::trunc); // Les mthodes de gestion du fichier : basic_filebuf<charT, traits> *rdbuf() const; bool is_open(); void open(const char *nom, ios_base::openmode mode = out | trunc); void close(); };

Comme pour les ux dentre / sortie sur les chanes de caractres, il est possible dinitialiser le ux ds sa construction ou a posteriori. Les mthodes importantes sont bien entendu la mthode open, qui permet douvrir un chier, la mthode is_open, qui permet de savoir si le ux contient dj un chier ouvert ou non, et la mthode close, qui permet de fermer le chier ouvert. Exemple 15-10. Utilisation de ux dentre / sortie sur un chier
#include <iostream> #include <fstream> #include <string> using namespace std; int main(void) { // Lit les donnes : int i; double d, e; cout << "Entier Rel Rel : "; cin >> i >> d >> e; // Enregistre ces donnes dans un fichier : ofstream f("fichier.txt"); if (f.is_open())

382

Chapitre 15. Les ux dentre / sortie


{ f << "Les donnes lues sont : " << i << " " << d << " " << e << endl; f.close(); } return 0; }

Note : Il est important de bien noter que le destructeur des ux ne ferme pas les chiers. En effet, ce sont les tampons utiliss de manire sous-jacente par les ux qui ralisent les oprations sur les chiers. Il est mme tout fait possible daccder un mme chier avec plusieurs classes de ux, bien que cela ne soit pas franchement recommand. Vous devrez donc prendre en charge vous-mme les oprations douverture et de fermeture des chiers.

Bien entendu, les classes de ux permettant daccder des chiers hritent des mthodes de positionnement de leurs classes de base. Ainsi, les classes de lecture dans un chier disposent des mthodes tellg et seekg, et les classes dcriture disposent des mthodes tellp et seekp. Ces oprations permettent respectivement de lire et de xer une nouvelle valeur du pointeur de position du chier courant. Exemple 15-11. Repositionnement du pointeur de chier dans un ux dentre / sortie
#include <iostream> #include <fstream> #include <string> using namespace std; int main(void) { // Ouvre le fichier de donnes : fstream f("fichier.txt", ios_base::in | ios_base::out | ios_base::trunc); if (f.is_open()) { // crit les donnes : f << 2 << " " << 45.32 << " " << 6.37 << endl; // Replace le pointeur de fichier au dbut : f.seekg(0); // Lit les donnes : int i; double d, e; f >> i >> d >> e; cout << "Les donnes lues sont : " << i << " " << d << " " << e << endl; // Ferme le fichier : f.close(); } return 0; }

Note : Les classes dentre / sortie sur chier nutilisent quun seul tampon pour accder aux chiers. Par consquent, il nexiste quune seule position dans le chier, qui sert la fois la lecture et lcriture.

383

Chapitre 15. Les ux dentre / sortie

384

Chapitre 16. Les locales


Il existe de nombreux alphabets et de nombreuses manires dcrire les nombres, les dates et les montants de part le monde. Chaque pays, chaque culture dispose en effet de ses propres conventions et de ses propres rgles, et ce dans de nombreux domaines. Par exemple, les Anglo-saxons ont pour coutume dutiliser le point (caractre .) pour sparer les units de la virgule lorsquils crivent des nombres virgule et dutiliser une virgule (caractre ,) entre chaque groupe de trois chiffres pour sparer les milliers des millions, les millions des milliards, etc. En France, cest la virgule est utilise pour sparer les units de la partie fractionnaire des nombres virgule, et le sparateur des milliers est simplement un espace. De mme, ils ont lhabitude dcrire les dates en mettant le mois avant les jours, alors que les Franais font linverse. Il va de soi que ce genre de diffrences rend techniquement trs difcile linternationalisation des programmes. Une solution est tout simplement de dire que les programmes travaillent dans une langue neutre , ce qui en pratique revient souvent dire langlais puisque cest la langue historiquement la plus utilise en informatique. Hlas, si cela convenait parfaitement aux programmeurs, ce ne serait certainement pas le cas des utilisateurs ! Il faut donc, un moment donn ou un autre, que les programmes prennent en compte les conventions locales de chaque pays ou de chaque peuple. Ces conventions sont extrmement nombreuses et portent sur des domaines aussi divers et varis que la manire dcrire les nombres, les dates ou de coder les caractres et de classer les mots dans un dictionnaire. En informatique, il est courant dappeler lensemble des conventions dun pays la locale de ce pays. Les programmes qui prennent en compte la locale sont donc dits localiss et sont capables de sadapter aux prfrences nationales de lutilisateur.
Note : Le fait dtre localis ne signie pas pour autant pour un programme que tous ses messages sont traduits dans la langue de lutilisateur. La localisation ne prend en compte que les aspects concernant lcriture des nombres et les alphabets utiliss. An de bien faire cette distinction, on dit que les programmes capables de communiquer avec lutilisateur dans sa langue sont internationaliss. La conversion dun programme dun pays un autre ncessite donc la fois la localisation de ce programme et son internationalisation.

Si la traduction de tous les messages dun programme ne peut pas tre ralise automatiquement, il est toutefois possible de prendre en compte les locales relativement facilement. En effet, les fonctionnalits des bibliothques C et C++, en particulier les fonctionnalits dentre / sortie, peuvent gnralement tre paramtres par la locale de lutilisateur. La gestion des locales est donc compltement prise en charge par ces bibliothques et un mme programme peut donc tre utilis sans modication dans divers pays.
Note : En revanche, la traduction des messages ne peut bien videmment pas tre prise en charge par la bibliothque standard, sauf ventuellement pour les messages derreur du systme. La ralisation dun programme international ncessite donc de prendre des mesures particulires pour faciliter la traduction de ces messages. En gnral, ces mesures consistent isoler les messages dans des modules spciques et ne pas les utiliser directement dans le code du programme. Ainsi, il suft simplement de traduire les messages de ces modules pour ajouter le support dune nouvelle langue un programme existant. Le code source na ainsi pas tre touch, ce qui limite les risques derreurs.

La gestion des locales en C++ se fait par lintermdiaire dune classe gnrale, la classe locale, qui permet de stocker tous les paramtres locaux des pays. Cette classe est bien entendu utilise par les ux dentre / sortie de la bibliothque standard, ce qui fait que vous naurez gnralement qu

385

Chapitre 16. Les locales initialiser cette classe au dbut de vos programmes pour leur faire prendre en compte les locales. Cependant, il se peut que vous ayez manipuler vous-mme des locales ou dnir de nouvelles conventions nationales, surtout si vous crivez des surcharges des oprateurs de formatage des ux operator<< et operator>>. Ce chapitre prsente donc les notions gnrales des locales, les diffrentes classes mises en uvre au sein dune mme locale pour prendre en charge tous les aspects de la localisation, et la manire de dnir ou de rednir un de ces aspects an de complter une locale existante.

16.1. Notions de base et principe de fonctionnement des facettes


Comme il la t dit plus haut, les locales comprennent diffrents aspects qui traitent chacun dune des conventions nationales de la locale. Par exemple, la manire dcrire les nombres constitue un de ces aspects, tout comme la manire de classer les caractres ou la manire dcrire les heures et les dates. Chacun de ces aspects constitue ce que la bibliothque standard C++ appelle une facette. Les facettes sont gres par des classes C++, dont les mthodes permettent dobtenir les informations spciques aux donnes quelles manipulent. Certaines facettes fournissent galement des fonctions permettant de formater et dinterprter ces donnes en tenant compte des conventions de leur locale. Chaque facette est identie de manire unique dans le programme, et chaque locale contient une collection de facettes dcrivant tous ses aspects. La bibliothque standard C++ fournit bien entendu un certain nombre de facettes prdnies. Ces facettes sont regroupes en catgories qui permettent de les classer en fonction du type des oprations quelles permettent de raliser. La bibliothque standard dnit six catgories de facettes, auxquelles correspondent les valeurs de six constantes de la classe locale :

la catgorie ctype, qui regroupe toutes les facettes permettant de classier les caractres et de les convertir dun jeu de caractre en un autre ; la catgorie collate, qui comprend une unique facette permettant de comparer les chanes de caractres en tenant compte des caractres de la locale courante et de la manire de les utiliser dans les classements alphabtiques ; la catgorie numeric, qui comprend toutes les facettes prenant en charge le formatage des nombres ; la catgorie monetary, qui comprend les facettes permettant de dterminer les symboles montaires et la manire dcrire les montants ; la catgorie time, qui comprend les facettes capables deffectuer le formatage et lcriture des dates et des heures ; la catgorie message, qui contient une unique facette permettant de faciliter linternationalisation des programmes en traduisant les messages destins aux utilisateurs.

Bien entendu, il est possible de dnir de nouvelles facettes et de les inclure dans une locale existante. Ces facettes ne seront videmment pas utilises par les fonctions de la bibliothque standard, mais le programme peut les rcuprer et les utiliser de la mme manire que les facettes standards. Les mcanismes de dnition, didentication et de rcupration des facettes, sont tous pris en charge au niveau de la classe locale. La dclaration de cette classe est ralise de la manire suivante dans len-tte locale :

386

Chapitre 16. Les locales


class locale { public: // Les types de donnes : // Les catgories de facettes : typedef int category; static const category // Les valeurs de ces constantes { // sont spcifiques chaque implmentation none = VAL0, ctype = VAL1, collate = VAL2, numeric = VAL3, monetary = VAL4, time = VAL5, messages = VAL6, all = collate | ctype | monetary | numeric | time | messages; }; // La classe de base des facettes : class facet { protected: explicit facet(size_t refs = 0); virtual ~facet(); private: // Ces mthodes sont dclares mais non dfinies : facet(const facet &); void operator=(const facet &); }; // La classe didentification des facettes : class id { public: id(); private: // Ces mthodes sont dclares mais non dfinies : void id(const id &); void operator=(const id &); }; // Les constructeurs : locale() throw() explicit locale(const char *nom); locale(const locale &) throw(); locale(const locale &init, const char *nom, category c); locale(const locale &init1, const locale &init2, category c); template <class Facet> locale(const locale &init, Facet *f); template <class Facet> locale(const locale &init1, const locale &init2); // Le destructeur : ~locale() throw(); // Oprateur daffectation : const locale &operator=(const locale &source) throw();

387

Chapitre 16. Les locales


// Les mthodes de manipulation des locales : basic_string<char> name() const; bool operator==(const locale &) const; bool operator!=(const locale &) const; template <class charT, class Traits, class Allocator> bool operator()( const basic_string<charT, Traits, Allocator> &s1, const basic_string<charT, Traits, Allocator> &s2) const; // Les mthodes de slection des locales : static locale global(const locale &); static const locale &classic(); };

Comme vous pouvez le constater, outre les constructeurs, destructeur et mthodes gnrales, la classe locale contient la dclaration de deux sous-classes utilises pour la dnition des facettes : la classe facet et la classe id. La classe facet est la classe de base de toutes les facettes. Son rle est essentiellement dviter que lon puisse dupliquer une facette ou en copier une, ce qui est ralis en dclarant en zone prive le constructeur de copie et loprateur daffectation. Comme ces mthodes ne doivent pas tre utilises, elles ne sont pas dnies non plus, seule la dclaration est fournie par la bibliothque standard. Notez que cela nest pas drangeant pour lutilisation de la classe facet, puisque comme ces mthodes ne sont pas virtuelles et quelles ne seront jamais utilises dans les programmes, lditeur de liens ne cherchera pas les localiser dans les chiers objets du programme. Ainsi, les instances de facettes ne peuvent tre ni copies, ni dupliques. En fait, les facettes sont destines tre utilises au sein des locales, qui prennent en charge la gestion de leur dure de vie. Toutefois, si lon dsire grer soimme la dure de vie dune facette, il est possible de le signaler lors de la construction de la facette. Le constructeur de base de la classe facet prend en effet un paramtre unique qui indique si la dure de vie de la facette doit tre prise en charge par la locale dans laquelle elle se trouve ou si elle devra tre explicitement dtruite par le programmeur (auquel cas ce paramtre doit tre x 1). En gnral, la valeur par dfaut 0 convient dans la majorit des cas et il nest pas ncessaire de fournir de paramtre au constructeur des facettes. La classe id quant elle est utilise pour dnir des identiants uniques pour chaque classe de facette. Ces identiants permettent la bibliothque standard de distinguer les facettes les unes des autres, laide dune clef unique dont le format nest pas spci, mais qui est dtermine par la classe id automatiquement lors de la premire cration de chaque facette. Cet identiant est en particulier utilis pour retrouver les facettes dans la collection des facettes gres par la classe locale. Pour que ce mcanisme denregistrement fonctionne, il faut que chaque classe de facette dnisse une donne membre statique id en zone publique, dont le type est la sous-classe id de la classe locale. Cette donne membre tant statique, elle appartient la classe et non chaque instance, et permet donc bien didentier chaque classe de facette. Lors du chargement du programme, les variables statiques sont initialises 0, ce qui fait que les facettes disposent toutes dun identiant nul. Ceci permet aux mthodes de la bibliothque standard de dterminer si un identiant a dj t attribu la classe dune facette ou non lorsquelle est utilise pour la premire fois. Si cest le cas, cet identiant est utilis tel quel pour rfrencer cette classe, sinon, il est gnr automatiquement et stock dans la donne membre id de la classe. Ainsi, pour dnir une facette, il suft simplement dcrire une classe drivant de la classe locale::facet et contenant une donne membre statique et publique id de type locale::id. Ds lors, cette facette se verra attribuer automatiquement un identiant unique, qui permettra dutiliser les fonctions de recherche de facettes dans les locales. Ces fonctions utilisent bien entendu la donne membre id

388

Chapitre 16. Les locales du type de la facette rechercher et sen servent en tant quindex dans la collection des facettes de la locale utiliser. La manire de raliser ces oprations nest pas dcrite par la norme du C++, mais le principe est l. Les fonctions de recherche des facettes sont galement dclares dans len-tte locale. Ce sont des fonctions template paramtres par le type des facettes, qui prennent en paramtre la locale dans laquelle la facette doit tre recherche :
template <class Facet> const Facet &use_facet(const locale &); template <class Facet> bool has_facet(const locale &);

La fonction use_facet permet de rcuprer linstance dune facette dans la locale passe en paramtre. Comme la signature de cette fonction template ne permet pas de dterminer le type de la facette, et donc linstance utiliser pour lappel, il est ncessaire de spcier explicitement ce type entre crochets aprs le nom de la fonction. Par exemple, pour rcuprer la facette num_put<char> de la locale classique de la bibliothque C, on fera lappel suivant :
use_facet<num_put<char> >(locale::classic());

Les facettes fournies par la bibliothque standard sont gnralement disponibles et peuvent tre utilises avec les locales. Les mthodes spciques chacune de ces facettes peuvent donc tre appeles sur la rfrence de la facette retourne par la fonction use_facet. En revanche, les facettes dnies par lutilisateur peuvent ne pas tre prsentes dans la locale fournie en paramtre la fonction use_facet. Dans ce cas, cette fonction lance une exception de type bad_cast. Comme il peut tre utile en certaines circonstances de dterminer dynamiquement si une locale contient ou non une facette, la bibliothque standard met disposition la fonction globale has_facet. Cette fonction sutilise de la mme manire que la fonction use_facet, mais au lieu de renvoyer la facette demande, elle retourne un boolen indiquant si la locale fournie en paramtre contient ou non cette facette. Les programmes peuvent crer plusieurs locales an de prendre en compte plusieurs jeux de paramtres internationaux sils le dsirent, mais ils doivent dans ce cas manipuler ces locales eux-mmes dans toutes les oprations susceptibles dutiliser la notion de locale. Par exemple, ils doivent spcier la locale utiliser avant chaque opration dentre / sortie en appelant la mthode imbue des ux utiliss. Comme cela nest pas trs pratique, la bibliothque standard dnit une locale globale, qui est la locale utilise par dfaut lorsquun programme ne dsire pas spcier explicitement la locale utiliser. Cette locale peut tre rcupre tout moment en crant un simple objet de type locale, en utilisant le constructeur par dfaut de la classe locale. Ce constructeur initialise en effet la locale en cours de construction avec tous les paramtres de la locale globale. Ainsi, pour rcuprer la facette num_put<char> de la locale globale, on fera lappel suivant :
use_facet<num_put<char> >(locale());

Vous remarquerez que la locale fournie en paramtre la fonction use_facet nest plus, contrairement lexemple prcdent, la locale renvoye par la mthode statique classic de la classe locale, mais une copie de la locale globale. Il est possible de construire une locale spcique explicitement avec le constructeur de la classe locale qui prend le nom de la locale utiliser en paramtre. Ce nom peut tre lun des noms standards

389

Chapitre 16. Les locales


"C", "", ou toute autre valeur dont la signication nest pas normalise. Le nom de locale vide ("")

permet de construire une locale dont les paramtres sont initialiss en fonction de lenvironnement dexcution du programme. Cest donc la valeur que lon utilisera en gnral, car cela permet de paramtrer le comportement des programmes facilement, sans avoir les modier et les recompiler.
Note : La manire de dnir la locale dans lenvironnement dexcution des programmes est spcique chaque systme dexploitation et nest normalis ni par la norme C++, ni par la norme C. La norme POSIX prcise cependant que cela peut tre ralis par lintermdiaire de variables denvironnement. Par exemple, si la variable denvironnement LANG nest pas dnie, la locale utilise sera la locale de la bibliothque C. En revanche, si cette variable denvironnement contient la valeur "fr_FR", la locale utilise sera celle des francophones de France. Les formats des nombres, des dates, etc. utiliss seront donc ceux qui sont en vigueur en France.

Les autres constructeurs de la classe locale permettent de crer de nouvelles locales en recopiant les facettes dune locale existante, ventuellement en ajoutant de nouvelles facettes non standards ou en rednissant certaines facettes de la locale modle. Bien entendu, le constructeur de copie recopie toutes les facettes de la locale source dans la locale en cours de construction. Les deux constructeurs suivants permettent de recopier toutes les facettes dune locale, sauf les facettes identies par la catgorie spcie en troisime paramtre. Pour cette catgorie, les facettes utilises sont celles de la locale spcie en deuxime paramtre. Il est possible ici didentier cette deuxime locale soit par son nom, soit directement par lintermdiaire dune rfrence. Enn, les deux constructeurs template permettent de crer une locale dont toutes les facettes sont initialises partir dune locale fournie en paramtre, sauf la facette dont le type est utilis en tant que paramtre template. Pour cette facette, la valeur utiliser peut tre spcie directement en deuxime paramtre ou extraite dune autre locale, elle aussi spcie en deuxime paramtre. Nous verrons plus en dtail dans la Section 16.3 la manire de procder pour insrer une nouvelle facette ou remplacer une facette existante. Enn, la classe locale dispose de deux mthodes statiques permettant de manipuler les locales du programme. La mthode classic que lon a utilise dans lun des exemples prcdents permet dobtenir la locale reprsentant les options de la bibliothque C standard. Cette locale est la locale utilise par dfaut par les programmes qui ne dnissent pas les locales utilises. La mthode global quant elle permet de spcier la locale globale du programme. Cette mthode prend en paramtre un objet de type locale partir duquel la locale globale est initialise. Il est donc courant de faire un appel cette mthode ds le dbut des programmes C++. Exemple 16-1. Programme C++ prenant en compte la locale de lenvironnement
#include <ctime> #include <iostream> #include <locale> using namespace std; int main(void) { // Utilise la locale dfinie dans lenvironnement // dexcution du programme : locale::global(locale("")); // Affiche la date courante : time_t date; time(&date); struct tm *TL = localtime(&date);

390

Chapitre 16. Les locales


use_facet<time_put<char> >(locale()).put( cout, cout, , TL, c); cout << endl; // Affiche la date avec la fonction strftime // de la bibliothque C : char s[64]; strftime(s, 64, "%c", TL); cout << s << endl; return 0; }

La mthode global de la classe global appelle automatiquement la mthode setlocale si la locale fournie en paramtre a un nom. Cela signie que les locales de la bibliothque standard C++ et celles de la bibliothque standard C sont compatibles et utilisent les mmes conventions de nommage. En particulier, les programmes qui veulent utiliser la locale dnie dans leur environnement dexcution peuvent utiliser la locale anonyme "". Cest ce que fait le programme de lexemple prcdent, qui afche la date au format de la locale dnie par lutilisateur en passant par les mcanismes du C++ (via la facette time_put, qui sera dcrite en dtail dans la Section 16.2.6) et par la fonction strftime de la bibliothque C.
Note : Les fonctions time, localtime et strftime sont des fonctions de la bibliothque C standard. Elles permettent respectivement dobtenir une valeur de type time_t reprsentant la date courante, de la convertir en une structure contenant les diffrentes composantes de la date en temps local, et de formater cette date selon les conventions de la locale courante. Ces fonctions ne seront pas dcrites plus en dtail ici. Vous pouvez consulter la bibliographie si vous dsirez obtenir plus de dtails sur la bibliothque C et les fonctions quelle contient.

16.2. Les facettes standards


Cette section prsente lensemble des facettes standards dnies par la bibliothque standard. La premire partie dcrit larchitecture gnrale laquelle les facettes standards se conforment, et les parties suivantes donnent une description des principales fonctionnalits fournies par chacune des catgories de facettes.

16.2.1. Gnralits
Les facettes fournies par la bibliothque standard sont des classes template paramtres par le type des caractres sur lesquels elles travaillent. Pour quelques-unes de ces facettes, la bibliothque standard dnit une spcialisation pour les types char ou wchar_t, spcialisation dont le but est doptimiser les traitements des facettes pour les ux dentre / sortie standards sur ces types. Certaines de ces facettes ne sont utilises que pour fournir des informations aux autres parties de la bibliothque standard C++. Dautres, en revanche, permettent de raliser les oprations de formatage et danalyse syntaxique sur lesquelles les ux dentre / sortie sappuient pour implmenter les oprateurs operator<< et operator>>. Ces facettes disposent alors de mthodes put et get qui permettent deffectuer ces deux types dopration. Les traitements effectus par les facettes doivent prendre en compte les paramtres de leur locale ainsi que les options de formatage stockes dans les ux sur lesquelles les entres / sorties doivent tre effectues. Pour cela, les facettes doivent disposer dun moyen de rcuprer la locale dont elles font

391

Chapitre 16. Les locales partie ou dont elles doivent utiliser les paramtres. Gnralement, les facettes qui ralisent les oprations dentre / sortie pour le compte des ux utilisent la mthode getloc de ces derniers pour obtenir la locale dont elles doivent utiliser les paramtres. Les autres facettes ne peuvent pas procder de la mme manire, car elles ne disposent pas forcment dun objet ux pour dterminer la locale courante. An de rsoudre ce problme, la bibliothque standard dnit des classes de facettes drives et dont le constructeur prend en paramtre le nom de la locale laquelle ces facettes appartiennent. Ces classes sont donc initialises, ds leur construction, avec le nom de la locale dans laquelle elles se trouvent, ce qui leur permet ventuellement deffectuer des traitements dpendants de cette locale. Les noms de ces classes de facettes drives sont les mmes que ceux de leurs classes de base, ceci prs quils sont sufxs par la chane _byname . Par exemple, la facette ctype, qui, comme on le verra plus loin, permet de classer les caractres selon leur nature, dispose dune classe drive ctype_byname dont le constructeur prend en paramtre le nom de la locale dont la facette fait partie.
Note : Les implmentations de la bibliothque standard fournies avec les environnements de dveloppement C++ ne sont pas tenues de fournir ces facettes pour chaque locale existante dans le monde. En ralit, quasiment aucun environnement ne le fait lheure actuelle. En revanche, toutes les facettes standards doivent au moins tre fournies et fonctionner correctement avec les locales "C" et "".

Les facettes sont crites de telle manire quelles peuvent facilement tre remplaces par des facettes plus spciques. Ainsi, leurs mthodes publiques appellent toutes des mthodes virtuelles, qui peuvent parfaitement tre rednies par des classes drives dsirant remplacer lun des traitements effectus par la bibliothque standard. Gnralement, les noms des mthodes virtuelles sont les mmes que ceux des mthodes publiques qui les utilisent, prcds du prxe do_ . Par exemple, si une facette fournit une mthode publique nomme put, la mthode virtuelle appele par celle-ci se nommera do_put. La manire de rednir les mthodes dune facette existante et de remplacer cette facette par une de ses classes drives dans une locale sera dcrite en dtail dans la Section 16.3.2.

16.2.2. Les facettes de manipulation des caractres


La bibliothque standard dnit deux facettes permettant de manipuler les caractres. La premire facette, la facette ctype, fournit les fonctions permettant de classer les caractres en diffrentes catgories. Ces catgories comprennent les lettres, les chiffres, les caractres imprimables, les caractres graphiques, etc. La deuxime facette permet quant elle deffectuer les conversions entre les diffrents types existants dencodage de caractres. Il sagit de la facette code_cvt.

16.2.2.1. La facette ctype


La facette ctype drive dune classe de base dans laquelle sont dnies les diffrentes catgories de caractres. Cette classe est dclare comme suit dans len-tte locale :
class ctype_base { public: enum mask { space = SPACE_VALUE, print = PRINT_VALUE, cntrl = CNTRL_VALUE, alpha = ALPHA_VALUE, digit = DIGIT_VALUE, xdigit = XDIGIT_VALUE,

392

Chapitre 16. Les locales


upper = UPPER_VALUE, lower = LOWER_VALUE, punct = PUNCT_VALUE, alnum = alpha | digit, graph = alnum | punct }; };

Les valeurs numriques utilises par cette numration sont dnies de telle manire que les constantes de type mask constituent un champ de bits. Ainsi, il est possible de dnir des combinaisons entre ces valeurs, certains caractres pouvant appartenir plusieurs catgories en mme temps. Deux combinaisons standards sont dailleurs dnies, alnum, qui caractrise les caractres alphanumriques, et graph, qui reprsente tous les caractres alphanumriques et de ponctuation. Les autres constantes permettent de caractriser les caractres selon leur nature et leur signication est en gnral claire. La seule constante qui dont linterprtation nest pas immdiate est la constante xdigit, qui identie tous les caractres pouvant servir de chiffre dans la notation des nombres hexadcimaux. Cela comprend les chiffres normaux et les lettres A F. La classe template ctype quant elle est dclare comme suit dans len-tte locale :
template <class charT> class ctype : public locale::facet, public ctype_base { public: // Les types de donnes : typedef charT char_type; // Le constructeur : explicit ctype(size_t refs = 0); // Les mthode de classification : bool is(mask m, charT c) const; const charT *is(const charT *premier, const charT *dernier, mask *vecteur) const; const charT *scan_is(mask m, const charT *premier, const charT *dernier) const; const charT *scan_not(mask m, const charT *premier, const charT *dernier) const; charT toupper(charT c) const; const charT *toupper(const charT *premier, const charT *dernier) const; charT tolower(charT c) const; const charT *tolower(const charT *premier, const charT *dernier) const; charT widen(char c) const; const charT *widen(const char *premier, const char *dernier, charT *destination) const; char narrow(charT c, char defaut) const; const char *narrow(const charT *premier, const charT *dernier, char defaut, char *destination) const; // Lidentificateur de la facette : static locale::id id; };

Note : Comme pour toutes les facettes standards, les mthodes publiques dlguent leur travail des mthodes virtuelles dclares en zone protge dont le nom est celui de la mthode publique prx par la chane de caractres do_ . Ces mthodes peuvent tre rednies par les classes drives de la facette ctype et font donc partie de linterface des facettes standards. Cependant,

393

Chapitre 16. Les locales


elles ne sont pas reprsentes dans la dclaration donne ci-dessus par souci de simplicit. Leur smantique est exactement la mme que celle des mthodes publiques correspondantes. Nous verrons dans la Section 16.3.2 la manire de procder pour rednir certaines des mthodes des facettes standards.

Les mthodes scan_is et scan_not permettent de rechercher un caractre selon un critre particulier dans un tableau de caractres. La mthode scan_is recherche le premier caractre qui est du type indiqu par son paramtre m, et la mthode scan_not le premier caractre qui nest pas de ce type. Ces deux mthodes prennent en paramtre un pointeur sur le premier caractre du tableau dans lequel la recherche doit seffectuer et le pointeur suivant lemplacement du dernier caractre de ce tableau. Elles renvoient toutes les deux un pointeur rfrenant le caractre trouv, ou le pointeur de n si aucun caractre ne vrie le critre spci. Les autres mthodes de la facette ctype sont fournies sous deux versions. La premire permet deffectuer une opration sur un caractre unique et la deuxime permet de reproduire cette opration sur une squence de caractres conscutifs. Dans ce dernier cas, les caractres sur lesquels lopration peut tre effectue sont spcis laide de deux pointeurs, lun sur le premier caractre et lautre sur le caractre suivant le dernier caractre de la squence, comme il est dusage de le faire dans tous les algorithmes de la bibliothque standard. Les deux mthodes is permettent donc respectivement de dterminer si un caractre est du type indiqu par le paramtre m ou non, ou dobtenir la suite des descriptions de chaque caractre dans le tableau de valeur de type mask point par le paramtre vecteur. De mme, les mthodes toupper et tolower permettent respectivement de convertir un caractre unique ou tous les caractres dun tableau en majuscule ou en minuscule. La mthode widen permet de transtyper un caractre ou tous les caractres dun tableau de type char en caractres du type par lequel la classe ctype est paramtre. Enn, les mthodes narrow permettent de raliser lopration inverse, ce qui peut provoquer une perte de donnes puisque le type char est le plus petit des types de caractres qui puisse exister. Il est donc possible que le transtypage ne puisse se faire, dans ce cas, les mthodes narrow utilisent la valeur par dfaut spcie par le paramtre defaut. Exemple 16-2. Conversion dune wstring en string
#include <iostream> #include <string> #include <locale> using namespace std; int main(void) { // Fixe la locale globale aux prfrences de lutilisateur : locale::global(locale("")); // Lit une chane de caractres larges : wstring S; wcin >> S; // Rcupre la facette ctype<wchar_t> de la locale courante : const ctype<wchar_t> &f = use_facet<ctype<wchar_t> >(locale()); // Construit un tampon pour recevoir le rsultat de la conversion : size_t l = S.length() + 1; char *tampon = new char[l]; // Effectue la conversion : f.narrow(S.c_str(), S.c_str() + l, E, tampon);

394

Chapitre 16. Les locales


// Affiche le rsultat : cout << tampon << endl; delete[] tampon; return 0; }

Note : Les conversions effectues par les mthodes narrow et widen ne travaillent quavec les reprsentations de caractres classiques du langage C++. Cela signie que les caractres sont tous reprsents par une unique valeur de type char ou wchar_t (ces mthodes nutilisent donc pas de reprsentation des caractres bases sur des squences de caractres de longueurs variables). La mthode narrow de lexemple prcdent crit donc autant de caractres dans le tampon destination quil y en a dans la chane convertir.

Vous constaterez que lutilisation de la mthode is pour dterminer la nature des caractres peut tre relativement fastidieuse, car il faut rcuprer la facette ctype, dterminer la valeur du masque utiliser, puis appeler la mthode. La bibliothque standard dnit donc un certain nombre de fonctions globales utilitaires dans len-tte locale :
template template template template template template template template template template template template template <class <class <class <class <class <class <class <class <class <class <class <class <class charT> charT> charT> charT> charT> charT> charT> charT> charT> charT> charT> charT> charT> bool isspace (charT bool isprint (charT bool iscntrl (charT bool isupper (charT bool islower (charT bool isalpha (charT bool isdigit (charT bool ispunct (charT bool isxdigit(charT bool isalnum (charT bool isgraph (charT charT toupper(charT charT tolower(charT c, c, c, c, c, c, c, c, c, c, c, c, c, const const const const const const const const const const const const const locale locale locale locale locale locale locale locale locale locale locale locale locale &l) &l) &l) &l) &l) &l) &l) &l) &l) &l) &l) &l) &l) const; const; const; const; const; const; const; const; const; const; const; const; const;

Lutilisation de ces fonctions ne doit pas poser de problme particulier. Elles prennent toutes en premier paramtre le caractre caractriser et en deuxime paramtre la locale dont la facette ctype doit tre utilise pour raliser cette caractrisation. Chaque fonction permet de tester le caractre pour lappartenance lune des catgories de caractres dnies dans la classe de base ctype_base. Notez cependant que si un grand nombre de caractres doivent tre caractriss pour une mme locale, il est plus performant dobtenir la facette ctype de cette locale une bonne fois pour toutes et deffectuer les appels la mthode is en consquence. La classe ctype tant une classe template, elle peut tre utilise pour nimporte quel type de caractre a priori. Toutefois, il est vident que cette classe peut tre optimise pour les types de caractre simples, et tout particulirement pour le type char, parce quil ne peut pas prendre plus de 256 valeurs diffrentes. La bibliothque standard dnit donc une spcialisation totale de la classe template ctype pour le type char. Limplmentation de cette spcialisation se base sur un tableau de valeurs de type mask indexe par les valeurs que peuvent prendre les variables de type char. Ce tableau permet donc de dterminer rapidement les caractristiques de chaque caractre existant. Le constructeur de cette spcialisation diffre lgrement du constructeur de sa classe template car il peut prendre en paramtre un pointeur sur ce tableau de valeurs et un boolen indiquant si ce tableau doit tre dtruit automatiquement par la facette lorsquelle est elle-mme dtruite ou non. Ce constructeur prend galement en troisime paramtre une valeur de type entier indiquant, comme pour toutes les facettes standards, si la locale doit prendre en charge la gestion de la dure de vie de la facette ou non.

395

Chapitre 16. Les locales Les autres mthodes de cette spcialisation sont identiques aux mthodes de la classe template de base et ne seront donc pas dcrites ici.

16.2.2.2. La facette codecvt


La facette codecvt permet de raliser les oprations de conversion dun mode de reprsentation des caractres un autre. En gnral, en informatique, les caractres sont cods par des nombres. Le type de ces nombres, ainsi que la manire de les utiliser, peut varier grandement dune reprsentation une autre, et les conversions peuvent ne pas se faire simplement. Par exemple, certaines reprsentations codent chaque caractre avec une valeur unique du type de caractre utilis, mais dautres codent les caractres sur des squences de longueur variable. On ne peut dans ce cas bien entendu pas convertir directement une reprsentation en une autre, car linterprtation que lon peut faire des nombres reprsentant les caractres dpend du contexte dtermin par les nombres dj lus. Les oprations de conversion ne sont donc pas toujours directes. De plus, dans certains encodages taille variable, linterprtation des caractres peut dpendre des caractres dj convertis. La facette codecvt maintient donc un tat pendant les conversions quelle effectue, tat qui lui permet de reprendre la conversion dune squence de caractres dans le cas de conversions ralises en plusieurs passes. Bien entendu, tous les encodages ne ncessitent pas forcment le maintien dun tel tat. Cependant, certains lexigent et il faut donc toujours le prendre en compte dans les oprations de conversion si lon souhaite que le programme soit portable. Pour les squences de caractres encodage variable utilisant le type de caractre de base char, le type de la variable dtat permettant de stocker ltat courant du convertisseur est le type mbstate_t. Dautres types peuvent tre utiliss pour les squences bases sur des types de caractres diffrents du type char, mais en gnral, tous les encodages taille variable se basent sur ce type. Quoi quil en soit, la classe codecvt dnit un type de donne capable de stocker ltat dune conversion partielle. Ce type est le type state_type, qui pourra donc toujours tre rcupr dans la classe codecvt. La variable dtat du convertisseur devra tre systmatiquement fournie aux mthodes de conversion de la facette codecvt et devra bien entendu tre initialise sa valeur par dfaut au dbut de chaque nouvelle conversion.
Note : La facette codecvt permet de raliser les conversions dune reprsentation des caractres une autre, mais na pas pour but de changer lencodage des caractres, cest--dire lassociation qui est faite entre les squences de nombres et les caractres. Cela signie que la facette codecvt permet par exemple de convertir des chanes de caractres larges wchar_t en squences de longueurs variables de caractres de type char, mais elle ne permet pas de passer dune page de codes une autre.

La facette codecvt drive dune classe de base nomme codecvt_base. Cette classe dnit les diffrents rsultats que peuvent avoir les oprations de conversion. Elle est dclare comme suit dans len-tte locale :
class codecvt_base { public: enum result { ok, partial, error, noconv }; };

396

Chapitre 16. Les locales Comme vous pouvez le constater, une conversion peut se raliser compltement (code de rsultat ok), partiellement par manque de place dans la squence destination ou par manque de donnes en entres (code partial), ou pas du tout, soit en raison dune erreur de conversion (code derreur error), soit parce quaucune conversion nest ncessaire (code de rsultat noconv). La classe template codecvt elle-mme est dnie comme suit dans len-tte locale :
template <class internT, class externT, class stateT> class codecvt : public locale::facet, public codecvt_base { public: // Les types de donnes : typedef internT intern_type; typedef externT extern_type; typedef stateT state_type; // Le constructeur : explicit codecvt(size_t refs=0); // Les fonctions de conversion : result out(stateT &etat, const internT *premier, const internT *dernier, const internT *&suiv_source, externT *dernier, externT *limite, externT *&suiv_dest) const; result in(stateT &etat, const externT *premier, const externT *dernier, const externT *&suiv_source, internT *dernier, internT *limite, internT *&suiv_dest) const; result unshift(stateT &etat, externT *dernier, externT *limite, externT *&suiv_dest) const; int length(const stateT &etat, const externT *premier, const externT *dernier, size_t max) const; int max_length() const throw(); int encoding() const throw(); bool always_noconv() const throw(); // Lidentificateur de la facette : static locale::id id; };

Note : Les mthodes virtuelles dimplmentation des mthodes publiques nont pas t crites dans la dclaration prcdente par souci de simplication. Elles existent malgr tout, et peuvent tre rednies par les classes drives an de personnaliser le comportement de la facette.

Cette classe template est paramtre par le type de caractre interne la classe codecvt, par un deuxime type de caractre qui sera par la suite dnomm type externe, et par le type des variables destines recevoir ltat courant dune conversion. Les implmentations de la bibliothque standard doivent obligatoirement instancier cette classe template pour les types char et wchar_t. Le type de gestion de ltat des conversions utilis est alors le type prdni mbstate_t, qui permet de conserver ltat des conversions entre le type natif wchar_t et les squences de caractres simples taille variable. Ainsi, vous pourrez toujours utiliser les instances codecvt<wchar_t, char, mbstate_t> et codecvt<char, char, mbstate_t> de la facette codecvt dans vos programmes. Si vous dsirez raliser des conversions pour dautres types de caractres, vous devrez fournir vous-mme des spcialisations de la facette codecvt.

397

Chapitre 16. Les locales Les mthodes in et out permettent respectivement, comme leurs signatures lindiquent, de raliser les conversions entre les types interne et externe et vice versa. Elles prennent toutes deux sept paramtres. Le premier paramtre est une rfrence sur la variable dtat qui devra tre fournie chaque appel lors de conversions successives dun mme ux de donnes. Cette variable est destine recevoir ltat courant de la conversion et permettra aux appels suivants de convertir correctement les caractres suivants du ux dentre. Les deux paramtres suivants permettent de spcier la squence de caractres convertir. Ils doivent contenir le pointeur sur le dbut de la squence et le pointeur sur le caractre suivant le dernier caractre de la squence. Le quatrime paramtre est un paramtre de retour, la fonction lui affectera la valeur du pointeur o la conversion sest arrte. Une conversion peut sarrter cause dune erreur ou tout simplement parce que le tampon destination ne contient pas assez de place pour accueillir un caractre de plus. Ce pointeur pourra tre utilis dans un appel ultrieur comme pointeur de dpart avec la valeur de la variable dtat lissue de la conversion pour effectuer la suite de cette conversion. Enn, les trois derniers paramtres spcient le tampon destination dans lequel la squence convertie doit tre crite. Ils permettent dindiquer le pointeur de dbut de ce tampon, le pointeur suivant le dernier emplacement utilisable, et un pointeur de retour qui indiquera la dernire position crite par lopration de conversion. Ces deux mthodes renvoient une des constantes de lnumration result dnie dans la classe de base codecvt_base pour indiquer comment la conversion sest effectue. Si aucune conversion nest ncessaire, les pointeurs sur les caractres suivants sont initialiss la valeur des pointeurs de dbut de squence et aucune criture na lieu dans le tampon destination. Exemple 16-3. Conversion dune chane de caractres larges en chane encodage variable
#include <iostream> #include <string> #include <locale> using namespace std; int main(void) { // Fixe la locale globale : locale::global(locale("")); // Lit une ligne : wstring S; getline(wcin, S); // Rcupre la facette de conversion vers wchar_t : const codecvt<wchar_t, char, mbstate_t> &f = use_facet<codecvt<wchar_t, char, mbstate_t> >(locale()); // Effectue la conversion : const wchar_t *premier = S.c_str(); const wchar_t *dernier = premier + S.length(); const wchar_t *suivant = premier; string s; char tampon[10]; char *fincvt = tampon; codecvt_base::result r; mbstate_t etat = mbstate_t(); while (premier != dernier) { // Convertit un morceau de la chane : r = f.out(etat, premier, dernier, suivant, tampon, tampon+10, fincvt); // Vrifie les erreurs possibles : if (r == codecvt_base::ok || r == codecvt_base::partial)

398

Chapitre 16. Les locales


cout << "." << flush; else if (r == codecvt_base::noconv) { cout << "conversion non ncessaire" << endl; break; } else if (r == codecvt_base::error) { cout << "erreur" << endl; cout << suivant - premier << endl; cout << fincvt - tampon << endl; break ; } // Rcupre le rsultat et prpare la conversion suivante : s.append(tampon, fincvt - tampon); premier = suivant; } cout << endl; // Affiche le rsultat : cout << s << endl; return 0; }

Note : Si lon dsire effectuer une simple conversion dune chane de caractres de type wchar_t en chane de caractres C classique, on cherchera plutt utiliser la mthode narrow de la facette ctype prsente dans la section prcdente. En effet, la facette codecvt utilise, a priori, une squence de caractres avec un encodage taille variable, ce qui ne correspond pas la reprsentation des chanes de caractres C classiques, pour lesquelles chaque valeur de type char reprsente un caractre.

Il est possible de complter une squence de caractres encodage variable de telle sorte que la variable dtat du convertisseur soit rinitialise. Cela permet de terminer une chane de caractres partiellement convertie, ce qui en pratique revient complter la squence de caractres avec les donnes qui reprsenteront le caractre nul terminal. Cette opration peut tre ralise laide de la mthode unshift de la facette codecvt. Cette mthode prend en paramtre une rfrence sur la variable dtat du convertisseur, ainsi que les pointeurs de dbut et de n du tampon dans lequel les valeurs ajouter sont crites. Le dernier paramtre de la mthode unshift est une rfrence sur un pointeur qui recevra ladresse suivant celle la dernire valeur crite par la mthode si lopration se droule correctement. Il va de soi que la dtermination de la longueur dune chane de caractres dont les caractres ont une reprsentation taille variable nest pas simple. La facette codecvt comporte donc une mthode length permettant de calculer, en nombre de caractres de type intern_type, la longueur dune squence de caractres de type extern_type. Cette mthode prend en paramtre la variable dtat du convertisseur ainsi que les pointeurs spciant la squence de caractres dont la longueur doit tre calcule. Le dernier paramtre est la valeur maximale que la fonction peut retourner. Elle permet de limiter la dtermination de la longueur de la squence source une borne maximale, par exemple la taille dun tampon destination. La valeur retourne est bien entendu la longueur de cette squence ou, autrement dit, le nombre de valeurs de type intern_type ncessaires pour stocker le rsultat de la conversion que la mthode in ferait avec les mmes paramtres. Dautre part, il est possible de dterminer le nombre maximal de valeurs de type intern_type ncessaires pour reprsenter un unique caractre reprsent par une squence de caractres de type extern_type. Pour cela, il suft dappeler la mthode max_length de la facette codecvt.

399

Chapitre 16. Les locales Exemple 16-4. Dtermination de la longueur dune chane de caractres encodage variable
#include #include #include #include <iostream> <string> <locale> <limits>

using namespace std; int main(void) { // Fixe la locale globale : locale::global(locale("")); // Lit une ligne : string s; getline(cin, s); // Rcupre la facette de conversion vers wchar_t : const codecvt<wchar_t, char, mbstate_t> &f = use_facet<codecvt<wchar_t, char, mbstate_t> >(locale()); // Affiche la longueur de la chane dentre : int l1 = s.length(); // Calcule la longueur de la ligne en wchar_t : mbstate_t etat = mbstate_t(); int l2 = f.length(etat, s.c_str(), s.c_str() + l1, numeric_limits<size_t>::max()); // Affiche les deux longueurs : cout << l1 << endl; cout << l2 << endl; return 0; }

Comme on la dj indiqu ci-dessus, toutes les reprsentations des caractres ne sont pas taille variable et toutes les reprsentations ne ncessitent pas forcment lutilisation dune variable dtat de type state_type. Vous pouvez dterminer dynamiquement si le mode de reprsentation des caractres du type intern_type utilise un encodage taille variable ou non laide de la mthode encoding. Cette mthode renvoie -1 si la reprsentation des caractres de type extern_type dpend de ltat du convertisseur, ou le nombre de caractres de type extern_type ncessaires au codage dun caractre de type intern_type si ce nombre est constant. Si la valeur renvoye est 0, ce nombre nest pas constant, mais, contrairement ce qui se passe lorsque la valeur renvoye est -1, ce nombre ne dpend pas de la valeur de la variable dtat du convertisseur. Enn, certains modes de reprsentation des caractres sont compatibles, voire franchement identiques. Dans ce cas, jamais aucune conversion nest ralise, et les mthodes in et out renvoient toujours noconv. Cest par exemple le cas de la spcialisation codecvt<char, char, mbstate_t> de la facette codecvt. Vous pouvez dterminer si une facette effectuera des conversions ou non en appelant la mthode always_noconv. Elle retourne true si jamais aucune conversion ne se fera et false sinon.

16.2.3. Les facettes de comparaison de chanes


Les chanes de caractres sont gnralement classes par ordre alphabtique, ou, plus prcisment, dans lordre lexicographique. Lordre lexicographique est lordre dni par la squence des symboles lexicaux utiliss (cest--dire les symboles utiliss pour former les mots du langage, donc, en pratique, les lettres, les nombres, la ponctuation, etc.). Cet ordre est celui qui est dni par la comparaison successive des caractres des deux chanes comparer, le premier couple de caractres diffrents

400

Chapitre 16. Les locales permettant de donner un jugement de classement. Ainsi, les chanes les plus petites au sens de lordre lexicographique sont les chanes qui commencent par les premiers symboles du lexique utilis. Cette manire de procder suppose bien entendu que les symboles utiliss pour former les mots du lexique sont classs dans un ordre correct. Par exemple, il faut que la lettre a apparaisse avant la lettre b, qui elle-mme doit apparatre avant la lettre c, etc. Malheureusement, cela nest pas si simple, car cet ordre nest gnralement pas celui utilis par les pages de codes dune part, et il existe toujours des symboles spciaux dont la classication ncessite un traitement spcial dautre part. Par exemple, les caractres accentus sont gnralement placs en n de page de code et apparaissent donc la n de lordre lexicographique, ce qui perturbe automatiquement le classement des chanes de caractres contenant des accents. De mme, certaines lettres sont en ralit des compositions de lettres et doivent tre prises en compte en tant que telles dans les oprations de classement. Par exemple, la lettre doit tre interprte comme un a suivi dun e. Et que dire du cas particulier des majuscules et des minuscules ? Comme vous pouvez le constater, il nest pas possible de se baser uniquement sur lordre des caractres dans leur page de code pour effectuer les oprations de classement de chanes de caractres. De plus, il va de soi que lordre utilis pour classer les symboles lexicographiques dpend de ces symboles et donc de la locale utilis. La bibliothque standard fournit donc une facette prenant en compte tous ces paramtres : la classe template collate. Le principe de fonctionnement de la facette collate est de transformer les chanes de caractres utilisant les conventions de la locale laquelle la facette appartient en une chane de caractres indpendante de la locale, comprenant ventuellement des codes de contrle spciaux pour les caractres spciques cette locale. Les chanes de caractres ainsi transformes peuvent alors tre compares entre elles directement, avec les mthodes de comparaison classique de chanes de caractres qui utilisent lordre lexicographique du jeu de caractres du langage C. La transformation est effectue de telle manire que cette comparaison produit le mme rsultat que la comparaison tenant compte de la locale des chanes de caractres non transformes. La facette collate est dclare comme suit dans len-tte locale :
template <class charT> class collate : public locale::facet { public: // Les types de donnes : typedef charT char_type; typedef basic_string<charT> string_type; // Le constructeur : explicit collate(size_t refs = 0); // Les mthodes de comparaison de chanes : string_type transform(const charT *debut, const charT *fin) const; int compare(const charT *deb_premier, const charT *fin_premier, const charT *deb_deuxieme, const charT *fin_deuxieme) const; long hash(const charT *debut, const charT *fin) const; // Lidentificateur de la facette : static locale::id id; };

401

Chapitre 16. Les locales


Note : Les mthodes virtuelles dimplmentation des mthodes publiques nont pas t crites dans la dclaration prcdente par souci de simplication. Elles existent malgr tout, et peuvent tre rednies par les classes drives an de personnaliser le comportement de la facette.

La mthode transform est la mthode fondamentale de la facette collate. Cest cette mthode qui permet dobtenir la chane de caractres transforme. Elle prend en paramtre le pointeur sur le dbut de la chane de caractres transformer et le pointeur sur le caractre suivant le dernier caractre de cette chane. Elle retourne une basic_string contenant la chane transforme, sur laquelle les oprations de comparaison classiques pourront tre appliques. Il est possible deffectuer directement la comparaison entre deux chanes de caractres, sans avoir rcuprer les chanes de caractres transformes. Cela peut tre ralis grce la mthode compare, qui prend en paramtre les pointeurs de dbut et de n des deux chanes de caractres comparer et qui renvoie un entier indiquant le rsultat de la comparaison. Cet entier est ngatif si la premire chane est infrieure la deuxime, positif si elle est suprieure, et nul si les deux chanes sont quivalentes. Exemple 16-5. Comparaison de chanes de caractres localises
#include <iostream> #include <string> #include <locale> using namespace std; int main(void) { // Fixe la locale globale : locale::global(locale("")); // Lit deux lignes en entre : cout << "Entrez la premire ligne :" << endl; string s1; getline(cin, s1); cout << "Entrez la deuxime ligne :" << endl; string s2; getline(cin, s2); // Rcupre la facette de comparaison de chanes : const collate<char> &f = use_facet<collate<char> >(locale()); // Compare les deux chanes : int res = f.compare( s1.c_str(), s1.c_str() + s1.length(), s2.c_str(), s2.c_str() + s2.length()); if (res < 0) { cout << "\"" << s1 << "\" est avant \"" << s2 << "\"." << endl; } else if (res > 0) { cout << "\"" << s1 << "\" est aprs \"" << s2 << "\"." << endl; } else { cout << "\"" << s1 << "\" est gale \"" <<

402

Chapitre 16. Les locales


s2 << "\"." << endl; return 0;

} }

Note : La mthode compare est trs pratique pour comparer deux chanes de caractres de manire ponctuelle. Cependant, on lui prfrera la mthode transform si un grand nombre de comparaisons doit tre effectu. En effet, il est plus simple de transformer toutes les chanes de caractres une bonne fois pour toutes et de travailler ensuite directement sur les chanes transformes. Ce nest que lorsque les oprations de comparaison auront t termines que lon pourra revenir sur les chanes de caractres initiales. On vite ainsi de faire des transformation rptition des chanes comparer et on gagne ainsi beaucoup de temps. Bien entendu, cela ncessite de conserver lassociation entre les chanes de caractres transformes et les chanes de caractres initiales, et donc de doubler la consommation mmoire du programme due au chanes de caractres pendant le traitement de ces chanes.

Enn, il est courant de chercher dterminer une clef pour chaque chane de caractres. Cette clef peut tre utilise pour effectuer une recherche rapide des chanes de caractres. La mthode hash de la facette collate permet de calculer une telle clef, en garantissant que deux chanes de caractres identiques au sens de la mthode compare auront la mme valeur de clef. On notera cependant que cette clef nest pas unique, deux chanes de caractres peuvent avoir deux valeurs de clefs identiques mme si la mthode compare renvoie une valeur non nulle. Cependant, ce cas est extrmement rare, et permet dutiliser malgr tout des algorithmes de recherche rapide. La seule chose laquelle il faut faire attention est que ces algorithmes doivent pouvoir supporter les clefs multiples.
Note : Les clefs probabilit de recouvrement faible comme celle retourne par la mhtode hash sont gnralement utilises dans les structures de donnes appeles tables de hachage, ce qui explique le nom donn cette mthode. Les tables de hachage sont en ralit des tableaux de listes chanes indexs par la clef de hachage (si la valeur de la clef dpasse la taille du tableau, elle est ramene dans les limites de celui-ci par une opration de rduction). Ce sont des structures permettant de rechercher rapidement des valeurs pour lesquelles une fonction de hachage simple existe. Cependant, elles se comportent moins bien que les arbres binaires lorsque le nombre dlments augmente (quelques milliers). On leur prfrera donc gnralement les associations de la bibliothque standard, comme les map et multimap par exemple. Vous pouvez consulter la bibliographie si vous dsirez obtenir plus de renseignements sur les tables de hachage et les structures de donnes en gnral. Les associations et les conteneurs de la bibliothque standard seront dcrites dans le Chapitre 17.

16.2.4. Les facettes de gestion des nombres


Les oprations de formatage et les oprations dinterprtation des donnes numriques dpendent bien entendu des conventions nationales de la locale incluse dans les ux qui effectuent ces oprations. En ralit, ces oprations ne sont pas prises en charge directement par les ux, mais plutt par les facettes de gestion des nombres, qui regroupent toutes les oprations propres aux conventions nationales. La bibliothque standard dnit en tout trois facettes qui interviennent dans les oprations de formatage : une facette utilitaire, qui contient les paramtres spciques la locale, et deux facettes ddies respectivement aux oprations de lecture et aux oprations dcriture des nombres.

403

Chapitre 16. Les locales

16.2.4.1. La facette num_punct


La facette qui regroupe tous les paramtres de la locale est la facette num_punct. Elle est dclare comme suit dans len-tte locale :
template <class charT> class numpunct : public locale::facet { public: // Les types de donnes : typedef charT char_type; typedef basic_string<charT> string_type; // Le constructeur : explicit numpunct(size_t refs = 0); // Les mthodes de lecture des options de formatage des nombres : char_type decimal_point() const; char_type thousands_sep() const; string grouping() const; string_type truename() const; string_type falsename() const; // Lidentificateur de la facette : static locale::id id; };

Note : Les mthodes virtuelles dimplmentation des mthodes publiques nont pas t crites dans la dclaration prcdente par souci de simplication. Elles existent malgr tout, et peuvent tre rednies par les classes drives an de personnaliser le comportement de la facette.

La mthode decimal_point permet dobtenir le caractre qui doit tre utilis pour sparer le chiffre des units des chiffres aprs la virgule lors des oprations de formatage des nombres virgule. La valeur par dfaut est le caractre ., mais en France, le caractre utilis est la virgule (caractre ,). De mme, la mthode thousands_sep permet de dterminer le caractre qui est utilis pour sparer les groupes de chiffres lors de lcriture des grands nombres. La valeur par dfaut renvoye par cette fonction est le caractre virgule (caractre ,), mais dans les locales franaises, on utilise gnralement un espace (caractre ). Enn, la mthode grouping permet de dterminer les emplacements o ces sparateurs doivent tre introduits. La chane de caractres renvoye dtermine le nombre de chiffres de chaque groupe de chiffres. Le nombre de chiffres du premier groupe est ainsi stock dans le premier caractre de la chane de caractres renvoye par la mthode grouping, celui du deuxime groupe est stock dans le deuxime caractre, et ainsi de suite. Le dernier nombre ainsi obtenu dans cette chane de caractres est ensuite utilis pour tous les groupes de chiffres suivants, ce qui vite davoir dnir une chane de caractres arbitrairement longue. Un nombre de chiffres nul indique que le mcanisme de groupage des chiffres des grands nombres est dsactiv. Les facettes de la plupart des locales renvoient la valeur "\03", ce qui permet de grouper les chiffres par paquets de trois (milliers, millions, milliards, etc.).
Note : Remarquez que les valeurs stockes dans la chane de caractres renvoye par la mthode grouping sont des valeurs numriques et non des chiffres formats dans la chane de caractres. Ainsi, la valeur par dfaut renvoye est bien "\03" et non "3".

404

Chapitre 16. Les locales Les mthodes truename et falsename quant elles permettent aux facettes de formatage dobtenir les chanes de caractres qui reprsentent les valeurs true et false des boolens. Ce sont ces chanes de caractres qui sont utilises lorsque loption de formatage boolalpha a t active dans les ux dentre / sortie. Les valeurs retournes par ces mthodes sont, par dfaut, les mots anglais true et false. Il est concevable dans dautres locales, cependant, davoir des noms diffrents pour ces deux valeurs. Nous verrons dans la Section 16.3.2 la manire de procder pour rednir ces mthodes et construire ainsi une locale personnalise et francise.
Note : Bien entendu, les facettes dcriture et de lecture des nombres utilisent galement les options de formatage qui sont dnis au niveau des ux dentre / sortie. Pour cela, les oprations dentre / sortie reoivent en paramtre une rfrence sur le ux contenant ces options.

16.2.4.2. La facette dcriture des nombres


Lcriture et le formatage des nombres sont pris en charge par la facette num_put. Cette facette est dclare comme suit dans len-tte locale :
template <class charT, class OutputIterator = ostreambuf_iterator<charT> > class num_put : public locale::facet { public: // Les types de donnes : typedef charT char_type; typedef OutputIterator iter_type; // Le constructeur : explicit num_put(size_t refs = 0); // Les mthodes dcriture des nombres : iter_type put(iter_type s, ios_base &f, iter_type put(iter_type s, ios_base &f, iter_type put(iter_type s, ios_base &f, unsigned long v) const; iter_type put(iter_type s, ios_base &f, double v) const; iter_type put(iter_type s, ios_base &f, long double v) const; iter_type put(iter_type s, ios_base &f, void *v) const; // Lidentificateur de la facette : static locale::id id; };

char_type remplissage, bool v) const; char_type remplissage, long v) const; char_type remplissage, char_type remplissage, char_type remplissage, char_type remplissage,

Note : Les mthodes virtuelles dimplmentation des mthodes publiques nont pas t crites dans la dclaration prcdente par souci de simplication. Elles existent malgr tout, et peuvent tre rednies par les classes drives an de personnaliser le comportement de la facette.

405

Chapitre 16. Les locales Comme vous pouvez le constater, cette facette dispose dune surcharge de la mthode put pour chacun des types de base du langage. Ces surcharges prennent en paramtre un itrateur dcriture sur le ux de sortie sur lequel les donnes formates devront tre crites, une rfrence sur le ux de sortie contenant les options de formatage utiliser lors du formatage des nombres, le caractre de remplissage utiliser, et bien entendu la valeur crire. En gnral, ces mthodes sont appeles au sein des oprateurs dinsertion operator<< pour chaque type de donne existant. De plus, le ux de sortie sur lequel les critures doivent tre effectues est le mme que le ux servant spcier les options de formatage, si bien que lappel aux mthodes put est extrmement simpli. Nous verrons plus en dtail la manire dappeler ces mthodes dans la Section 16.3.1, lorsque nous crirons une nouvelle facette pour un nouveau type de donne.

16.2.4.3. La facette de lecture des nombres


Les oprations de lecture des nombres partir dun ux de donnes sont prises en charge par la facette num_get. Cette facette est dclare comme suit dans len-tte locale :
template <class charT, class InputIterator = istreambuf_iterator<charT> > class num_get : public locale::facet { public: // Les types de donnes : typedef charT char_type; typedef InputIterator iter_type; // Le constructeur : explicit num_get(size_t refs = 0); // Les mthodes de lecture des nombres : iter_type get(iter_type in, iter_type end, ios_base &, ios_base::iostate &err, bool &v) const; iter_type get(iter_type in, iter_type end, ios_base &, ios_base::iostate &err, long &v) const; iter_type get(iter_type in, iter_type end, ios_base &, ios_base::iostate &err, unsigned short &v) const; iter_type get(iter_type in, iter_type end, ios_base &, ios_base::iostate &err, unsigned int &v) const; iter_type get(iter_type in, iter_type end, ios_base &, ios_base::iostate &err, unsigned long &v) const; iter_type get(iter_type in, iter_type end, ios_base &, ios_base::iostate &err, float &v) const; iter_type get(iter_type in, iter_type end, ios_base &, ios_base::iostate &err, double &v) const; iter_type get(iter_type in, iter_type end, ios_base &, ios_base::iostate &err, long double &v) const; iter_type get(iter_type in, iter_type end, ios_base &, ios_base::iostate &err, void *&v) const; // Lidentificateur de la facette : static locale::id id; };

406

Chapitre 16. Les locales


Note : Les mthodes virtuelles dimplmentation des mthodes publiques nont pas t crites dans la dclaration prcdente par souci de simplication. Elles existent malgr tout, et peuvent tre rednies par les classes drives an de personnaliser le comportement de la facette.

Comme vous pouvez le constater, cette facette ressemble beaucoup la facette num_put. Il existe en effet une surcharge de la mthode get pour chaque type de base du langage. Ces mthodes sont capables deffectuer la lecture des donnes de ces types partir du ux dentre, en tenant compte des paramtres des locales et des options de formatage du ux. Ces mthodes prennent en paramtre un itrateur de ux dentre, une valeur limite de cet itrateur au-del de laquelle la lecture du ux ne se fera pas, la rfrence sur le ux dentre contenant les options de formatage et deux paramtres de retour. Le premier paramtre recevra un code derreur de type iostate qui pourra tre positionn dans le ux dentre pour signaler lerreur. Le deuxime est une rfrence sur la variable devant accueillir la valeur lue. Si une erreur se produit, cette variable nest pas modie. Les mthodes get sont gnralement utilises par les oprateurs dextraction operator>> des ux dentre / sortie pour les types de donnes de base du langage. En gnral, ces oprateurs rcuprent la locale incluse dans le ux dentre sur lequel ils travaillent et utilisent la facette num_get de cette locale. Ils appellent alors la mthode get permettant de lire la donne quils doivent extraire, en fournissant ce mme ux en paramtre. Ils testent ensuite la variable dtat retourne par la mthode get et, si une erreur sest produite, modient ltat du ux dentre en consquence. Cette dernire opration peut bien entendu provoquer le lancement dune exception, selon le masque dexceptions utilis pour le ux.

16.2.5. Les facettes de gestion des monnaies


La bibliothque standard ne dnit pas de type de donne ddis la reprsentation des montants. Elle suppose en effet que les montants sont stocks dans des nombres virgule ottante dans la plupart des programmes ou, pour les programmes qui dsirent saffranchir des erreurs darrondis invitables lors de lutilisation de ottants, sous forme textuelle dans des chanes de caractres. En revanche, la bibliothque standard fournit, tout comme pour les types standards, deux facettes localises prenant en compte la lecture et lcriture des montants. Ces facettes se basent galement sur une troisime facette qui regroupe tous les paramtres spciques aux conventions nationales.
Note : En ralit, les seuls types capables de reprsenter correctement les montants en informatique sont les entiers et les nombres virgule xe cods sur les entiers. En effet, les types intgraux sont les seuls types qui ne soulvent pas de problme de reprsentation des nombres ( condition quil ny ait pas de dbordements bien entendu) et les nombres virgule xe sont particulirement adapts aux montants, car en gnral le nombre de chiffres signicatifs aprs la virgule est x pour une monnaie donne. Les nombres virgule ottante ne permettent pas de reprsenter des valeurs avec prcision et introduisent des erreurs incontrlables dans les calculs et dans les arrondis. Les chanes de caractres quant elles souffrent de leur lourdeur et, dans la plupart des cas, de la ncessit de passer par des nombres virgule ottantes pour interprter leur valeur. Les facettes prsentes dans cette section sont donc dune utilit rduite pour les programmes qui cherchent obtenir des rsultats rigoureux et prcis, et qui ne tolrent pas les erreurs de reprsentation et les erreurs darrondis.

Toutes les facettes de gestion des montants sont des classes template. Cependant, contrairement aux autres facettes, ces facettes disposent dun autre paramtre template que le type de caractre sur lequel elles travaillent. Ce paramtre est un paramtre de type boolen qui permet, selon sa valeur,

407

Chapitre 16. Les locales de spcier si les facettes doivent travailler avec la reprsentation internationale des montants ou non. Il existe en effet une reprsentation universelle des montants qui, entre autres particularits, utilise les codes internationaux de monnaie ( USD pour le dollar amricain, CAN pour le dollar canadien, EUR pour leuro, etc.). Comme pour les facettes de gestion des nombres, les facettes prenant en charge les monnaies sont au nombre de trois. Une de ces trois facettes permet dobtenir des informations sur la monnaie de la locale et les deux autres ralisent respectivement les oprations dcriture et de lecture sur un ux.

16.2.5.1. La facette money_punct


La facette moneypunct est la facette permettant aux deux facettes dcriture et de lecture des montants dobtenir les informations relatives la monnaie de leur locale. Cette facette est dclare comme suit dans len-tte locale :
template <class charT, bool International = false> class moneypunct : public locale::facet, public money_base { public: // Les types de donnes : typedef charT char_type; typedef basic_string<charT> string_type; // Le constructeur : explicit moneypunct(size_t refs = 0); // Les mthodes de lecture des options de formatage des montants : charT decimal_point() const; charT thousands_sep() const; string grouping() const; int frac_digits() const; string_type curr_symbol() const; pattern pos_format() const; pattern neg_format() const; string_type positive_sign() const; string_type negative_sign() const; static const bool intl = International; // Lidentificateur de la facette : static locale::id id; };

Note : Les mthodes virtuelles dimplmentation des mthodes publiques nont pas t crites dans la dclaration prcdente par souci de simplication. Elles existent malgr tout, et peuvent tre rednies par les classes drives an de personnaliser le comportement de la facette.

Comme vous pouvez le constater, cette facette dispose de mthodes permettant de rcuprer les divers symboles qui sont utiliss pour crire les montants de la monnaie quelle dcrit. Ainsi, la mthode decimal_point renvoie le caractre qui doit tre utilis en tant que sparateur du chiffre des units de la partie fractionnaire des montants, si celle-ci doit tre reprsente. De mme, la mthode thousands_sep renvoie le caractre qui doit tre utilis pour sparer les groupes de chiffres pour

408

Chapitre 16. Les locales les grands montants, et la mthode grouping renvoie une chane contenant, dans chacun de ses caractres, le nombre de chiffres de chaque groupe. Ces mthodes sont donc semblables aux mthodes correspondantes de la facette numpunct. Le nombre de chiffres signicatifs aprs la virgule utilis pour cette monnaie peut tre obtenue grce la mthode frac_digits. Ce nest que si la valeur renvoye par cette mthode est suprieure 0 que le symbole de sparation des units de la partie fractionnaire de la mthode decimal_point est utilis. La mthode curr_symbol permet dobtenir le symbole montaire de la monnaie. Ce symbole dpend de la valeur du paramtre template International. Si ce paramtre vaut true, le symbole montaire renvoy sera le symbole montaire international. Dans le cas contraire, ce sera le symbole montaire en usage dans le pays de circulation de la monnaie. La valeur du paramtre International pourra tre obtenu grce la constante statique intl de la facette. Les mthodes suivantes permettent de spcier le format dcriture des montants positifs et ngatifs. Ces mthodes utilisent les dnitions de constantes et de types de la classe de base money_base dont la facette moneypunct hrite. La classe money_base est dclare comme suit dans len-tte locale :
class money_base { public: enum part { none, space, symbol, sign, value }; struct pattern { char field[4]; }; };

Cette classe contient la dnition dune numration dont les valeurs permettent didentier les diffrentes composantes dun montant, ainsi quune structure pattern qui contient un tableau de quatre caractres. Chacun de ces caractres peut prendre lune des valeurs de lnumration part. La structure pattern dnit donc lordre dans lequel les composantes dun montant doivent apparatre. Ce sont des motifs de ce genre qui sont renvoys par les mthodes pos_format et neg_format, qui permettent dobtenir respectivement le format des montants positifs et celui des montants ngatifs. Les diffrentes valeurs que peuvent prendre les lments du motif pattern reprsentent chacune une partie de lexpression dun montant. La valeur value reprsente bien entendu la valeur de ce montant, sign son signe et symbol le symbole montaire. La valeur space permet dinsrer un espace dans lexpression dun montant, mais les espaces ne peuvent pas tre utiliss en dbut et en n de montants. Enn, la valeur none permet de ne rien mettre la position o il apparat dans le motif. La manire dcrire les montants positifs et ngatifs varie grandement selon les pays. En gnral, il est courant dutiliser le signe - pour signaler un montant ngatif et aucun signe distinctif pour les montants positifs. Cependant, certains pays crivent les montants ngatifs entre parenthses et la marque des montants ngatifs nest donc plus un simple caractre. Les mthodes positive_sign et negative_sign permettent dobtenir les symboles utiliser pour noter les montants positifs et ngatifs. Elles retournent toutes les deux une chane de caractres, dont le premier est plac systmatiquement lemplacement auquel la valeur sign a t affecte dans la chane de format renvoye par les mthodes pos_format et neg_format. Les caractres rsiduels, sils existent, sont placs la n de lexpression du montant compltement formate. Ainsi, dans les locales pour lesquelles les montants ngatifs sont crits entre parenthses, la chane renvoye par la mthode negative_sign

409

Chapitre 16. Les locales est () , et pour les locales utilisant simplement le signe ngatif, cette chane ne contient que le caractre -.

16.2.5.2. Les facettes de lecture et dcriture des montants


Les facettes dcriture et de lecture des montants sont sans doute les facettes standards les plus simples. En effet, elles ne disposent que de mthodes permettant dcrire et de lire les montants sur les ux. Ces facettes sont respectivement les facettes money_put et money_get. Elles sont dnies comme suit dans len-tte locale :
template <class charT, bool Intl = false, class OutputIterator = ostreambuf_iterator<charT> > class money_put : public locale::facet { public: // Les types de donnes : typedef charT char_type; typedef OutputIterator iter_type; typedef basic_string<charT> string_type; // Le constructeur : explicit money_put(size_t refs = 0); // Les mthodes dcriture des iter_type put(iter_type s, char_type remplissage, iter_type put(iter_type s, char_type remplissage, montants : bool intl, ios_base &f, long double units) const; bool intl, ios_base &f, const string_type &digits) const;

// Lidentificateur de la facette : static locale::id id; }; template <class charT, class InputIterator = istreambuf_iterator<charT> > class money_get : public locale::facet { public: // Les types de donnes : typedef charT char_type; typedef InputIterator iter_type; typedef basic_string<charT> string_type; // Le constructeur : explicit money_get(size_t refs = 0); // Les mthodes de lecture des montants : iter_type get(iter_type s, iter_type end, bool intl, ios_base &f, ios_base::iostate &err, long double &units) const; iter_type get(iter_type s, iter_type end, bool intl, ios_base &f, ios_base::iostate &err, string_type &digits) const; static const bool intl = Intl; // Lidentificateur de la facette :

410

Chapitre 16. Les locales


static locale::id id; };

Note : Les mthodes virtuelles dimplmentation des mthodes publiques nont pas t crites dans la dclaration prcdente par souci de simplication. Elles existent malgr tout, et peuvent tre rednies par les classes drives an de personnaliser le comportement de la facette.

Comme vous pouvez le constater, les mthodes dcriture et de lecture put et get de ces facettes sont semblables aux mthodes correspondantes des facettes de gestion des nombres. Toutefois, elles se distinguent par un paramtre boolen complmentaire qui permet dindiquer si les oprations de formatage doivent se faire en utilisant les conventions internationales dcriture des montants. Les autres paramtres ont la mme signication que pour les mthodes put et get des facettes de gestion des nombres. En particulier, litrateur fourni indique lemplacement o les donnes doivent tre crites ou lues, et le ux dentre / sortie spci permet de rcuprer les options de formatage des montants. Lune des options les plus utiles est sans doute loption qui permet dafcher la base des nombres, car, dans le cas des facettes de gestion des montants, elle permet dactiver ou non lcriture du symbole montaire. Enn, les mthodes put et get sont fournies en deux exemplaires, un pour chaque type de donne utilisable pour reprsenter les montants, savoir les double et les chanes de caractres.

16.2.6. Les facettes de gestion du temps


La bibliothque standard ne fournit que deux facettes pour lcriture et la lecture des dates : la facette time_put et la facette time_get. Ces deux facettes utilisent le type de base struct tm de la bibliothque C pour reprsenter le temps. Bien que ce document ne dcrive pas les fonctions de la bibliothque C, il est peut-tre bon de rappeler comment les programmes C manipulent les dates en gnral. La gestion du temps dans un programme peut trs vite devenir un vritable cauchemar, principalement en raison de la complexit que les tres humains se sont efforcs de dvelopper dans leur manire de reprsenter le temps. En effet, il faut tenir compte non seulement des spcicits des calendriers (annes bissextiles ou non par exemple), mais aussi des multiples bases de numration utilises dans lcriture des dates (24 heures par jour, 60 minutes par heure et 60 secondes par minutes, puis 10 diximes dans une seconde et ainsi de suite) et des conventions locales de gestion des heures (fuseau horaire, heure dt et dhiver). La rgle dor lors de la manipulation des dates est donc de toujours travailler dans un rfrentiel unique avec une reprsentation linaire du temps, autrement dit, de simplier tout cela. En pratique, cela revient dire que les programmes doivent utiliser une reprsentation linaire du temps (gnralement, le nombre de secondes coules depuis une date de rfrence) et travailler en temps universel. De mme, le stockage des dates doit tre fait dans ce format an de garantir la possibilit dchanger les donnes sans pour autant laisser la place aux erreurs dinterprtation de ces dates. En pratique, la bibliothque C utilise le type time_t. Les valeurs de ce type reprsentent le nombre dinstants couls depuis le premier janvier 1970 0 heure (date considre comme le dbut de lre informatique par les inventeurs du langage C, Kernighan et Ritchie, et que lon appelle couramment Epoch ). La dure de ces instants nest pas normalise par la bibliothque C, mais il sagit de secondes pour les systmes POSIX. Le type time_t permet donc de raliser des calculs simplement sur les dates. Les dates reprsentes avec des time_t sont toujours exprimes en temps universel.

411

Chapitre 16. Les locales Bien entendu, il existe des fonctions permettant de convertir les dates codes sous la forme de time_t en dates humaines et rciproquement. Le type de donne utilis pour stocker les dates au format humain est la structure struct tm. Cette structure contient plusieurs champs, qui reprsentent entre autres lanne, le jour, le mois, les heures, les minutes et les secondes. Ce type contient donc les dates au format clat et permet dobtenir les diffrentes composantes dune date. Gnralement, les dates sont formates en temps local, car les utilisateurs dsirent souvent avoir les dates afches dans leur propre base de temps. Cependant, il est galement possible de formater les dates en temps universel. Ces oprations de formatages sont ralises par les bibliothques C et C++, et les programmes nont donc pas se soucier des paramtres de fuseaux horaires, dheure dt et dhiver et des conventions locales dcriture des dates : tout est pris en charge par les locales. Les principales fonctions permettant de manipuler les dates sont rcapitules dans le tableau cidessous : Tableau 16-1. Fonctions C de gestion des dates Fonction
time_t time(time_t *)

Description Permet dobtenir la date courante. Peut tre appele avec ladresse dune variable de type time_t en paramtre ou avec la constante NULL. Initialise la variable passe par pointeur avec la date courante, et renvoie galement la valeur crite. Permet de convertir une date stocke dans une variable de type time_t en sa version clate en temps universel. Le pointeur renvoy rfrence une structure alloue en zone statique par la bibliothque C et ne doit pas tre libr. Permet de convertir une date stocke dans une variable de type time_t en sa version clate en temps local. Le pointeur renvoy rfrence une structure alloue en zone statique par la bibliothque C et ne doit pas tre libr. Permet de construire une date de type time_t partir dune date en temps local stocke dans une structure struct tm. Les donnes membres de la structure struct tm peuvent tre corriges par la fonction mktime si besoin est. Cette fonction est donc la fonction inverse de localtime. Permet de formater une date stocke dans une structure struct tm dans une chane de caractres. Cette chane doit tre fournie en premier paramtre, ainsi que le nombre maximal de caractres que la fonction pourra crire. La fonction renvoie le nombre de caractres crits ou, si le premier paramtre est nul, la taille de la chane de caractres quil faudrait pour effectuer une criture complte. La fonction strftime prend en paramtre une chane de format et fonctionne de manire similaire aux fonctions printf et sprintf. Elle comprend un grand nombre de formats, mais les plus utiles sont sans doute les formats %X et %x , qui permettent respectivement de formater lheure et la date selon les conventions de la locale du programme.

struct tm *gmtime(const time_t *) struct tm *localtime(const time_t *) time_t mktime(struct tm *) size_t strftime(char *tampon, size_t max, const char *format, constr struct tm *t)

Note : Il nexiste pas de fonction permettant de convertir une date exprime en temps universel et stocke dans une structure struct tm en une date de type time_t. De mme, la bibliothque C ne fournit pas de fonction permettant danalyser une chane de caractres reprsentant une date. Cependant, la norme Unix 98 dnit la fonction strptime, qui est la fonction inverse de la

412

Chapitre 16. Les locales


fonction strftime. Les fonctions localtime et gmtime ne sont pas sres dans un environnement multithread. En effet, la zone de mmoire renvoye est en zone statique et est partage par tous les threads. La bibliothque C dnit donc deux fonctions complmentaires, localtime_r et gmtime_r, qui prennent un paramtre complmentaire qui doit recevoir un pointeur sur la structure struct tm dans lequel le rsultat doit tre crit. Cette structure est alloue par le thread appelant et ne risque donc pas dtre dtruite par un appel la mme fonction par un autre thread. Les facettes de la bibliothque standard C++ ne permettent pas de manipuler les dates en soi. Elles ne sont destines qu raliser le formatage des dates en tenant compte des spcicits de reprsentation des dates de la locale. Elles se comportent exactement comme la fonction strftime le fait lorsque lon utilise les chanes de format %X et %x .

16.2.6.1. La facette dcriture des dates


La facette dcriture des dates est dclare comme suit dans len-tte locale :
template <class charT, class OutputIterator = ostreambuf_iterator<charT> > class time_put : public locale::facet { public: // Les types de donnes : typedef charT char_type; typedef OutputIterator iter_type; // Le constructeur : explicit time_put(size_t refs = 0); // Les mthodes dcriture des dates : iter_type put(iter_type s, ios_base &f, char_type remplissage, const tm *t, char format, char modificateur = 0) const; iter_type put(iter_type s, ios_base &f, char_type remplissage, const tm *t, const charT *debut_format, const charT *fin_format) const; // Lidentificateur de la facette : static locale::id id; };

Note : Les mthodes virtuelles dimplmentation des mthodes publiques nont pas t crites dans la dclaration prcdente par souci de simplication. Elles existent malgr tout, et peuvent tre rednies par les classes drives an de personnaliser le comportement de la facette.

Cette facette dispose de deux surcharges de la mthode put permettant dcrire une date sur un ux de sortie. La premire permet dcrire une date sur le ux de sortie dont un itrateur est donn en premier paramtre. Le formatage de la date se fait comme avec la fonction strftime de la bibliothque C. Le paramtre modificateur ne doit pas tre utilis en gnral, sa signication ntant pas prcise par la norme C++. La deuxime forme de la mthode put ralise galement une criture sur le ux, en prenant comme chane de format la premire sous-chane commenant par le caractre % dans la chane indique par les paramtres debut_format et fin_format.

413

Chapitre 16. Les locales

16.2.6.2. La facette de lecture des dates


La facette de lecture des dates permet de lire les dates dans le mme format que celui utilis par la fonction strftime de la bibliothque C lorsque la chane de format vaut %X ou %x . Cette facette est dclare comme suit dans len-tte locale :
template <class charT, class InputIterator = istreambuf_iterator<charT> > class time_get : public locale::facet, public time_base { public: // Les types de donnes : typedef charT char_type; typedef InputIterator iter_type; // Le constructeur : explicit time_get(size_t refs = 0); // Les mthodes de gestion de la lecture des dates : iter_type get_time(iter_type s, iter_type end, ios_base &f, ios_base::iostate &err, tm *t) const; iter_type get_date(iter_type s, iter_type end, ios_base &f, ios_base::iostate &err, tm *t) const; iter_type get_weekday(iter_type s, iter_type end, ios_base &f, ios_base::iostate &err, tm *t) const; iter_type get_monthname(iter_type s, iter_type end, ios_base &f, ios_base::iostate &err, tm *t) const; iter_type get_year(iter_type s, iter_type end, ios_base &f, ios_base::iostate &err, tm *t) const; dateorder date_order() const; // Lidentificateur de la facette : static locale::id id; };

Note : Les mthodes virtuelles dimplmentation des mthodes publiques nont pas t crites dans la dclaration prcdente par souci de simplication. Elles existent malgr tout, et peuvent tre rednies par les classes drives an de personnaliser le comportement de la facette.

Les diffrentes mthodes de cette facette permettent respectivement dobtenir lheure, la date, le jour de la semaine, le nom du mois et lanne dune date dans le ux dentre spci par litrateur fourni en premier paramtre. Toutes ces donnes sont interprtes en fonction de la locale laquelle la facette appartient. Enn, la mthode date_order permet dobtenir lune des valeurs de lnumration dnie dans la classe de base time_base et qui indique lordre dans lequel les composants jour / mois / anne des dates apparaissent dans la locale de la facette. La classe de base time_base est dclare comme suit dans len-tte locale :
class time_base { public: enum dateorder {

414

Chapitre 16. Les locales


no_order, dmy, mdy, ymd, ydm }; };

La signication des diffrentes valeurs de lnumration est immdiate. La seule valeur ncessitant des explications complmentaires est la valeur no_order. Cette valeur est renvoye par la mthode date_order si le format de date utilis par la locale de la facette contient dautres champs que le jour, le mois et lanne.
Note : La mthode date_order est fournie uniquement titre de facilit par la bibliothque standard. Elle peut ne pas tre implmente pour certaines locales. Dans ce cas, elle renvoie systmatiquement la valeur no_order.

16.2.7. Les facettes de gestion des messages


An de faciliter linternationalisation des programmes, la bibliothque standard fournit la facette messages, qui permet de prendre en charge la traduction de tous les messages dun programme de manire indpendante du systme sous-jacent. Cette facette permet dexternaliser tous les messages des programmes dans des chiers de messages que lon appelle des catalogues. Le format et lemplacement de ces chiers ne sont pas spcis par la norme C++, cependant, la manire dy accder est standardise et permet dcrire des programmes portables. Ainsi, lorsquun programme devra tre traduit, il sufra de traduire les messages stocks dans les chiers de catalogue pour chaque langue et de les distribuer avec le programme.
Note : La manire de crer et dinstaller ces chiers tant spcique chaque implmentation de la bibliothque standard et, dans une large mesure, spcique au systme dexploitation utilis, ces chiers ne seront pas dcrits ici. Seule la manire dutiliser la facette messages sera donc indique. Reportez-vous la documentation de votre environnement de dveloppement pour plus de dtails sur les outils permettant de gnrer les chiers de catalogue.

La facette messages rfrence les chiers de catalogue laide dun type de donne spcique. Ce type de donne est dni dans la classe de base messages_base comme tant un type intgral :
class messages_base { public: typedef int catalog; };

La classe template messages de gestion de la facette hrite donc de cette classe de base et utilise le type catalog pour identier les chiers de catalogue de lapplication. La classe messages est dclare comme suit dans len-tte locale :
template <class charT> class messages : public locale::facet, public messages_base { public: // Les types de donnes :

415

Chapitre 16. Les locales


typedef charT char_type; typedef basic_string<charT> string_type; // Le constructeur : explicit messages(size_t refs = 0); // Les mthodes de gestion des catalogues de messages : catalog open(const basic_string<char> &nom, const locale &l) const; void close(catalog c) const; string_type get(catalog c, int groupe, int msg, const string_type &defaut) const; // Lidentificateur de la facette : static locale::id id; };

Note : Les mthodes virtuelles dimplmentation des mthodes publiques nont pas t crites dans la dclaration prcdente par souci de simplication. Elles existent malgr tout, et peuvent tre rednies par les classes drives an de personnaliser le comportement de la facette.

Les principales mthodes de gestion des catalogues sont les mthodes open et close. Comme leurs noms lindiquent, ces mthodes permettent douvrir un nouveau chier de catalogue et de le fermer pour en librer les ressources. La mthode open prend en paramtre le nom du catalogue ouvrir. Ce nom doit identier de manire unique le catalogue, mais la norme C++ nindique pas comment il doit tre interprt. Cela relve donc de limplmentation de la bibliothque standard utilise. Toutefois, en pratique, il est probable quil sagit dun nom de chier. Le deuxime paramtre permet dindiquer la locale utiliser pour effectuer les conversions de jeux de caractres si cela est ncessaire. Il permet donc de laisser au programmeur le choix du jeu de caractres dans lequel les messages seront crits dans le catalogue. La valeur renvoye par la mthode open est lidentiant du catalogue, identiant qui devra tre fourni la mthode get pour rcuprer les messages du catalogue et la mthode close pour fermer le chier de catalogue. Si louverture du chier na pas pu tre effectue, la mthode open renvoie une valeur infrieure 0. Les messages du catalogue peuvent tre rcuprs laide de la mthode get. Cette mthode prend en paramtre lidentiant dun catalogue prcdemment obtenu par lintermdiaire de la mthode open, un identiant de groupe de message et un identiant dun message. Le dernier paramtre doit recevoir la valeur par dfaut du message en cas dchec de la recherche du message dans le catalogue. Cette valeur par dfaut est souvent un message en anglais, ce qui permet au programme de fonctionner correctement mme lorsque ses chiers de catalogue sont vides. La manire dont les messages sont identis nest pas spcie par la norme C++, tout comme la manire dont ils sont classs en groupes de messages au sein dun mme chier de catalogue. Cela relve donc de limplmentation de la bibliothque utilise. Consultez la documentation de votre environnement de dveloppement pour plus de dtails ce sujet.
Note : Cette facette est relativement peu utilise, pour plusieurs raison. Premirement, peu denvironnements C++ respectent la norme C++ ce jour. Deuximement, les systmes dexploitation disposent souvent de mcanismes de localisation performants et pratiques. Enn, lidentication dun message par des valeurs numriques nest pas toujours pratique et il est courant dutiliser le message par dfaut, souvent en anglais, comme clef de recherche pour les messages internationaux. Cette manire de procder est en effet beaucoup plus simple,

416

Chapitre 16. Les locales


puisque le contenu des messages est crit en clair dans la langue par dfaut dans les chiers sources du programme.

16.3. Personnalisation des mcanismes de localisation


Les mcanismes de localisation ont t conus de telle sorte que le programmeur peut, sil le dsire (et sil en a rellement le besoin), personnaliser leur fonctionnement. Ainsi, il est parfaitement possible de dnir de nouvelles facettes, par exemple pour permettre la localisation des types de donnes complmentaires dnis par le programme. De mme, il est possible de rednir les mthodes virtuelles des classes de gestion des facettes standards de la bibliothque et de remplacer les facettes originales par des facettes personnalises. Cependant, il faut bien reconnatre que la manire de procder nest pas trs pratique, et en fait les mcanismes internes de gestion des facettes semblent tre rservs aux classes et aux mthodes de la bibliothque standard elle-mme.

16.3.1. Cration et intgration dune nouvelle facette


Comme il la t expliqu dans la Section 16.1, une facette nest rien dautre quune classe drivant de la classe locale::facet et contenant une donne membre statique id . Cette donne membre est utilise par les classes de locale pour identier le type de la facette et pour lintgrer dans le mcanisme de gestion des facettes standards. Lexemple suivant montre comment on peut raliser deux facettes permettant dencapsuler les spcicits dun type de donne dni par le programme, le type answer_t. Ce type est suppos permettre la cration de variables contenant la rponse de lutilisateur une question. Ce nest rien dautre quune numration contenant les valeurs no (pour la rponse ngative), yes (pour lafrmative), all (pour rpondre par lafrmative pour tout un ensemble dlments) et none (pour rpondre par la ngative pour tout un ensemble dlments). Dans cet exemple, deux facettes sont dnies : la facette answerpunct, qui prend en charge la localisation des noms des diffrentes valeurs de lnumration answer_t, et la facette answer_put, qui prend en charge le formatage des valeurs de cette numration dans un ux standard. Loprateur operator<< est galement dni, an de prsenter la manire dont ces facettes peuvent tre utilises. La facette answer_get et loprateur correspondant operator>> nont pas t dnis et sont laisss en exercice pour le lecteur intress. Exemple 16-6. Dnition de nouvelles facettes
#include <iostream> #include <locale> using namespace std; // Nouveau type de donne permettant de grer les rponses // aux questions (yes / no / all / none) : enum answer_t { no, yes, all, none }; // Facette prenant dfinissant les noms des rponses :

417

Chapitre 16. Les locales


template <class charT> class answerpunct : public locale::facet { public: // Les types de donnes : typedef charT char_type; typedef basic_string<charT> string_type; // Lidentifiant de la facette : static locale::id id; // Le constructeur : answerpunct(size_t refs = 0) : locale::facet(refs) { } // Les mthodes permettant dobtenir les noms des valeurs : string_type yesname() const { return do_yesname(); } string_type noname() const { return do_noname(); } string_type allname() const { return do_allname(); } string_type nonename() const { return do_nonename(); } protected: // Le destructeur : virtual ~answerpunct() { } // Les mthodes virtuelles : virtual string_type do_yesname() const { return "yes"; } virtual string_type do_noname() const { return "no"; } virtual string_type do_allname() const { return "all";

418

Chapitre 16. Les locales


} virtual string_type do_nonename() const { return "none"; } }; // Instanciation de lidentifiant de la facette answerpunct : template <class charT> locale::id answerpunct<charT>::id; // Facette prenant en charge le formatage des rponses : template <class charT, class OutputIterator = ostreambuf_iterator<charT> > class answer_put : public locale::facet public: // Les types de donnes : typedef charT char_type; typedef OutputIterator iter_type; typedef basic_string<charT> string_type; // Lidentifiant de la facette : static locale::id id; // Le constructeur : answer_put(size_t refs = 0) : locale::facet(refs) { } // La mthode de formatage iter_type put(iter_type i, char_type remplissage, { return do_put(i, flux, } protected: // Le destructeur : virtual ~answer_put() { } // Limplmentation de la mthode de formatage : virtual iter_type do_put(iter_type i, ios_base &flux, char_type remplissage, answer_t valeur) const { // Rcupre la facette dcrivant les noms de types : const answerpunct<charT> &facet = use_facet<answerpunct<charT> >(flux.getloc()); // Rcupration du nom qui sera crit : string_type result; switch (valeur) { case yes: result = facet.yesname(); break; publique : ios_base &flux, answer_t valeur) const remplissage, valeur);

419

Chapitre 16. Les locales


case no: result = facet.noname(); break; case all: result = facet.allname(); break; case none: result = facet.nonename(); break; } // criture de la valeur : const char *p = result.c_str(); while (*p != 0) { *i = *p; ++i; ++p; } return i; } }; // Instanciation de lidentifiant de la facette answer_put : template <class charT, class OutputIterator = ostreambuf_iterator<charT> > locale::id answer_put<charT, OutputIterator>::id; // Oprateur permettant de formater une valeur // de type answer_t dans un flux de sortie : template <class charT, class Traits> basic_ostream<charT, Traits> &operator<<( basic_ostream<charT, Traits> &flux, answer_t valeur) { // Initialisation du flux de sortie : typename basic_ostream<charT, Traits>::sentry init(flux); if (init) { // Rcupration de la facette de gestion de ce type : const answer_put<charT> &facet = use_facet<answer_put<charT> >(flux.getloc()); // criture des donnes : facet.put(flux, flux, , valeur); } return flux; } int main(void) { // Cre une nouvelle locale utilisant nos deux facettes : locale temp(locale(""), new answerpunct<char>); locale loc(temp, new answer_put<char>); // Installe cette locale dans le flux de sortie : cout.imbue(loc); // Affiche quelques valeurs de type answer_t : cout << yes << endl; cout << no << endl; cout << all << endl;

420

Chapitre 16. Les locales


cout << none << endl; return 0; }

Note : Cet exemple, bien que dj compliqu, passe sous silence un certain nombre de points quil faudrait thoriquement prendre en compte pour raliser une implmentation correcte des facettes et des oprateurs dinsertion et dextraction des donnes de type answer_t dans les ux standards. Il faudrait en effet traiter les cas derreurs lors des critures sur le ux de sortie dans la mthode do_put de la facette answer_put, capter les exceptions qui peuvent se produire, corriger ltat du ux dentre / sortie au sein de loprateur operator<< et relancer ces exceptions. De mme, les paramtres de la locale ne sont absolument pas pris en compte dans la facette answerpunct, alors quune implmentation complte devrait sen soucier. Pour cela, il faudrait rcuprer le nom de la locale incluse dans les ux dentre / sortie dune part, et dnir une facette spcialise answerpunct_byname, en fonction du nom de laquelle les mthodes do_yesname, do_noname, do_allname et do_nonename devraient sadapter. La section suivante donne un exemple de rednition dune facette existante.

16.3.2. Remplacement dune facette existante


La rednition des mthodes de facettes dj existantes est lgrement plus simple que lcriture dune nouvelle facette. En effet, il nest plus ncessaire de dnir la donne membre statique id . De plus, seules les mthodes qui doivent rellement tre rednies doivent tre rcrites. Lexemple suivant prsente comment un programme peut rednir les mthodes do_truename et do_falsename de la facette standard numpunct_byname an den fournir une version localise en franais. Cela permet dutiliser ces noms franais dans les oprations de formatage des ux dentre / sortie standards, lorsque le manipulateur boolalpha a t utilis. Exemple 16-7. Spcialisation dune facette existante
#include #include #include #include <iostream> <locale> <clocale> <cstring>

using namespace std; // Facette destine remplacer numpunct_byname : class MyNumpunct_byname : public numpunct_byname<char> { // Les noms des valeurs true et false : const char *m_truename; const char *m_falsename; public: MyNumpunct_byname(const char* nom) : numpunct_byname<char>(nom) { // Dtermine le nom de la locale active : const char *loc = nom; if (strcmp(nom, "") == 0)

421

Chapitre 16. Les locales


{ // Rcupre le nom de la locale globale active : loc = setlocale(0, NULL); } // Prend en charge les noms franais : if (strcmp(loc, "fr_FR") == 0) { m_truename = "vrai"; m_falsename = "faux"; } else { // Pour les autres locales, utilise les noms anglais : m_truename = "true"; m_falsename = "false"; } } protected: ~MyNumpunct_byname() { } string do_truename() const { return m_truename; } string do_falsename() const { return m_falsename; } }; int main(void) { // Fixe la locale globale du programme : locale::global(locale("")); // Cre une nouvelle locale utilisant notre facette : locale l(locale(""), new MyNumpunct_byname("")); // Installe cette locale dans le flux de sortie : cout.imbue(l); // Affiche deux boolens : cout << boolalpha << true << endl; cout << false << endl; return 0; }

Note : La classe de base de la facette MyNumpunct_byname est la classe numpunct_byname parce que la facette a besoin de connatre le nom de la locale pour laquelle elle est construite. En effet, aucun autre mcanisme standard ne permet une facette de rcuprer ce nom et donc de sadapter aux diffrentes locales existantes. Vous remarquerez que les facettes de formatage nont pas besoin de connatre ce nom puisquelles peuvent le rcuprer grce la mthode name de la locale du ux sur lequel elles travaillent. La facette MyNumpunct_byname utilise la fonction setlocale de la bibliothque C pour rcuprer le nom de la locale courante si elle est initialise avec un nom vide. En ralit, elle devrait

422

Chapitre 16. Les locales


rcuprer ce nom par ses propres moyens et effectuer les traductions des noms des valeurs true et false par elle-mme, car cela suppose que la locale globale du programme est initialise avec le mme nom. Cest pour cela que le programme principal commence par appeler la mthode global de la classe local avec comme paramtre une locale anonmyme. Cela dit, les mcanismes permettant un programme de rcuprer les paramtres de la locale dnie dans lenvironnement dexcution du programme sont spciques chaque systme et ne peuvent donc pas tre dcrits ici. Bien entendu, si dautres langues que le franais devaient tre prises en compte, dautre mcanismes plus gnriques devraient galement tre mis en place pour dnir les noms des valeurs true et false an dviter de compliquer exagrment le code de la facette.

423

Chapitre 16. Les locales

424

Chapitre 17. Les conteneurs


La plupart des programmes informatiques doivent, un moment donn ou un autre, conserver un nombre arbitraire de donnes en mmoire, gnralement pour y accder ultrieurement et leur appliquer des traitements spciques. En gnral, les structures de donnes utilises sont toujours manipules par des algorithmes classiques, que lon retrouve donc souvent, si ce nest plusieurs fois, dans chaque programme. Ces structures de donnes sont communment appeles des conteneurs en raison de leur capacit contenir dautres objets. An dviter aux programmeurs de rinventer systmatiquement la roue et de reprogrammer les structures de donnes et leurs algorithmes associs les plus classiques, la bibliothque standard dnit un certain nombre de classes template pour les conteneurs les plus courants. Ces classes sont paramtres par le type des donnes des conteneurs et peuvent donc tre utilises virtuellement pour toutes les situations qui se prsentent. Les conteneurs de la bibliothque standard ne sont pas dnis par les algorithmes quils utilisent, mais plutt par linterface qui peut tre utilise par les programmes clients. La bibliothque standard impose galement des contraintes de performances sur ces interfaces en termes de complexit. En ralit, ces contraintes sont tout simplement les plus fortes qui soient, ce qui garantit aux programmes qui les utilisent quils auront les meilleures performances possibles. La bibliothque classie les conteneurs en deux grandes catgories selon leurs fonctionnalits : les squences et les conteneurs associatifs. Une squence est un conteneur capable de stocker ses lments de manire squentielle, les uns la suite des autres. Les lments sont donc parfaitement identis par leur position dans la squence, et leur ordre relatif est donc important. Les conteneurs associatifs, en revanche, manipulent leurs donnes au moyen de valeurs qui les identient indirectement. Ces identiants sont appeles des clefs par analogie avec la terminologie utilise dans les bases de donnes. Lordre relatif des lments dans le conteneur est laiss dans ce cas la libre discrtion de ce dernier et leur recherche se fait donc, gnralement, par lintermdiaire de leurs clefs. La bibliothque fournit plusieurs conteneurs de chaque type. Chacun a ses avantages et ses inconvnients. Comme il nexiste pas de structure de donnes parfaite qui permette dobtenir les meilleures performances sur lensemble des oprations ralisables, lutilisateur des conteneurs de la bibliothque standard devra effectuer son choix en fonction de lutilisation quil dsire en faire. Par exemple, certains conteneurs sont plus adapts la recherche dlments mais sont relativement coteux pour les oprations dinsertion ou de suppression, alors que pour dautres conteneurs, cest exactement linverse. Le choix des conteneurs utiliser sera donc dterminant quant aux performances nales des programmes.

17.1. Fonctionnalits gnrales des conteneurs


Au niveau de leurs interfaces, tous les conteneurs de la bibliothque standard prsentent des similitudes. Cet tat de fait nest pas d au hasard, mais bel et bien la volont de simplier la vie des programmeurs en vitant de dnir une multitude de mthodes ayant la mme signication pour chaque conteneur. Cependant, malgr cette volont duniformisation, il existe des diffrences entre les diffrents types de conteneurs (squences ou conteneurs associatifs). Ces diffrences proviennent essentiellement de la prsence dune clef dans ces derniers, qui permet de manipuler les objets contenus plus facilement. Quelle que soit leur nature, les conteneurs fournissent un certain nombre de services de base que le programmeur peut utiliser. Ces services comprennent la dnition des itrateurs, de quelques types complmentaires, des oprateurs et de fonctions standards. Les sections suivantes vous prsentent ces

425

Chapitre 17. Les conteneurs fonctionnalits gnrales. Toutefois, les descriptions donnes ici ne seront pas dtailles outre mesure car elles seront reprises en dtail dans la description de chaque conteneur.

17.1.1. Dnition des itrateurs


Pour commencer, il va de soi que tous les conteneurs de la bibliothque standard disposent ditrateurs. Comme on la vu dans la Section 13.4, les itrateurs constituent une abstraction de la notion de pointeur pour les tableaux. Ils permettent donc de parcourir tous les lments dun conteneur squentiellement laide de loprateur de drfrencement * et de loprateur dincrmentation ++. Les conteneurs dnissent donc tous un type iterator et un type const_iterator, qui sont les types des itrateurs sur les lments du conteneur. Le type ditrateur const_iterator est dni pour accder aux lments dun conteneur en les considrant comme des constantes. Ainsi, si le type des lments stocks dans le conteneur est T, le drfrencement dun const_iterator renverra un objet de type const T. Les conteneurs dnissent galement les types de donnes difference_type et size_type que lon peut utiliser pour effectuer des calculs darithmtique des pointeurs avec leurs itrateurs. Le type difference_type se distingue du type size_type par le fait quil peut contenir toute valeur issue de la diffrence entre deux itrateurs, et accepte donc les valeurs ngatives. Le type size_type quant lui est utilis plus spcialement pour compter un nombre dlments, et ne peut prendre que des valeurs positives. An de permettre linitialisation de leurs itrateurs, les conteneurs fournissent deux mthodes begin et end, qui renvoient respectivement un itrateur rfrenant le premier lment du conteneur et la valeur de n de litrateur, lorsquil a pass le dernier lment du conteneur. Ainsi, le parcours dun conteneur se fait typiquement de la manire suivante :
// Obtient un itrateur sur le premier lment : Conteneur::iterateur i = instance.begin(); // Boucle sur toutes les valeurs de litrateur // jusqu la dernire : while (i != instance.end()) { // Travaille sur llment rfrenc par i : f(*i); // Passe llment suivant : ++i; }

o Conteneur est la classe de du conteneur et instance en est une instance.


Note : Pour des raisons de performances et de portabilit, la bibliothque standard ne fournit absolument aucun support du multithreading sur ses structures de donnes. En fait, la gestion du multithreading est laisse la discrtion de chaque implmentation. Gnralement, seul le code gnr par le compilateur est sr vis--vis des threads (en particulier, les oprateurs dallocation mmoire new et new[], ainsi que les oprateurs delete et delete[] peuvent tre appels simultanment par plusieurs threads pour des objets diffrents). Il nen est pas de mme pour les implmentations des conteneurs et des algorithmes de la bibliothque standard. Par consquent, si vous voulez accder un conteneur partir de plusieurs threads, vous devez prendre en charge vous-mme la gestion des sections critiques an de vous assurer que ce conteneur sera toujours dans un tat cohrent. En fait, il est recommand de le faire mme si limplmentation de la bibliothque standard se protge elle-mme contre les accs concurrents

426

Chapitre 17. Les conteneurs


partir de plusieurs threads, an de rendre vos programmes portables vers dautres environnements.

Les itrateurs utiliss par les conteneurs sont tous au moins du type ForwardIterator. En pratique, cela signie que lon peut parcourir les itrateurs du premier au dernier lment, squentiellement. Cependant, la plupart des conteneurs disposent ditrateurs au moins bidirectionnels, et peuvent donc tre parcourus dans les deux sens. Les conteneurs qui disposent de ces proprits sont appels des conteneurs rversibles. Les conteneurs rversibles disposent, en plus des itrateurs directs, ditrateurs inverses. Ces itrateurs sont repectivement de type reverse_iterator et const_reverse_iterator. Leur initialisation peut tre ralise laide de la fonction rbegin, et leur valeur de n peut tre rcupre laide de la fonction rend.

17.1.2. Dnition des types de donnes relatifs aux objets contenus


Outre les types ditrateurs, les conteneurs dnissent galement des types spciques aux donnes quils contiennent. Ces types de donnes permettent de manipuler les donnes des conteneurs de manire gnrique, sans avoir de connaissance prcises sur la nature relle des objets quils stockent. Ils sont donc couramment utiliss par les algorithmes de la bibliothque standard. Le type rellement utilis pour stocker les objets dans un conteneur nest pas toujours le type template utilis pour instancier ce conteneur. En effet, certains conteneurs associatifs stockent les clefs des objets avec la valeur des objets eux-mmes. Ils utilisent pour cela la classe pair, qui permet de stocker, comme on la vu en Section 14.2.2, des couples de valeurs. Le type des donnes stockes par ces conteneurs est donc plus complexe que le simple type template par lequel ils sont paramtrs. An de permettre luniformisation des algorithmes travaillant sur ces types de donnes, les conteneurs dnissent tous le type value_type dans leur classe template. Cest en particulier ce type quil faut utiliser lors des insertions dlments dans les conteneurs. Bien entendu, pour la plupart des conteneurs, et pour toutes les squences, le type value_type est effectivement le mme type que le type template par lequel les conteneurs sont paramtrs. Les conteneurs dnissent galement dautres types permettant de manipuler les donnes quils stockent. En particulier, le type reference est le type des rfrences sur les donnes, et le type const_reference est le type des rfrences constantes sur les donnes. Ces types sont utiliss par les mthodes des conteneurs qui permettent daccder leurs donnes.

17.1.3. Spcication de lallocateur mmoire utiliser


Toutes les classes template des conteneurs de la bibliothque standard utilisent la notion dallocateur pour raliser les oprations de manipulation de la mmoire quelles doivent effectuer lors du stockage de leurs lments ou lors de lapplication dalgorithmes spciques au conteneur. Le type des allocateurs peut tre spci dans la liste des paramtres template des conteneurs, en marge du type des donnes contenues. Les constructeurs des conteneurs prennent tous un paramtre de ce type, qui sera lallocateur mmoire utilis pour ce conteneur. Ainsi, il est possible de spcier un allocateur spcique pour chaque conteneur, qui peut tre particulirement optimis pour le type des donnes gres par ce conteneur.

427

Chapitre 17. Les conteneurs Toutefois, le paramtre template spciant la classe de lallocateur mmoire utiliser dispose dune valeur par dfaut, qui reprsente lallocateur standard de la bibliothque allocator<T>. Il nest donc pas ncessaire de spcier cet allocateur lors de linstanciation dun conteneur. Cela rend plus simple lutilisation de la bibliothque standard C++ pour ceux qui ne dsirent pas dvelopper eux-mme un allocateur mmoire. Par exemple, la dclaration template du conteneur list est la suivante :
template <class T, class Allocator = allocator<T> >

Il est donc possible dinstancier une liste dentiers simplement en ne spciant que le type des objets contenus, en loccurrence, des entiers :
typedef list<int> liste_entier;

De mme, le paramtre des constructeurs permettant de spcier lallocateur utiliser pour les conteneurs dispose systmatiquement dune valeur par dfaut, qui est linstance vide du type dallocateur spci dans la liste des paramtres template. Par exemple, la dclaration du constructeur le plus simple de la classe list est la suivante :
template <class T, class Allocator> list<T, Allocator>::list(const Allocator & = Allocator());

Il est donc parfaitement lgal de dclarer une liste dentier simplement de la manire suivante :
liste_entier li;

Note : Il est peut-tre bon de rappeler que toutes les instances dun allocateur accdent la mme mmoire. Ainsi, il nest pas ncessaire, en gnral, de prciser linstance de lallocateur dans le constructeur des conteneurs. En effet, le paramtre par dfaut fourni par la bibliothque standard nest quune instance parmi dautres qui permet daccder la mmoire gre par la classe de lallocateur fournie dans la liste des paramtres template.

Si vous dsirez spcier une classe dallocateur diffrente de celle de lallocateur standard, vous devrez faire en sorte que cette classe implmente toutes les mthodes des allocateurs de la bibliothque standard. La notion dallocateur a t dtaille dans la Section 13.6.

17.1.4. Oprateurs de comparaison des conteneurs


Les conteneurs disposent doprateurs de comparaison permettant dtablir des relations dquivalence ou des relations dordre entre eux. Les conteneurs peuvent tous tre compars directement avec les oprateurs == et !=. La relation dgalit entre deux conteneurs est dnie par le respect des deux proprits suivantes :

les deux conteneurs doivent avoir la mme taille ; leurs lments doivent tre identiques deux deux.

Si le type des objets contenus dispose des oprateurs dinfriorit et de supriorits strictes, les mmes oprateurs seront galement dnis pour le conteneur. Ces oprateurs utilisent lordre lexicographique

428

Chapitre 17. Les conteneurs pour dterminer le classement entre deux conteneurs. Autrement dit, loprateur dinfriorit compare les lments des deux conteneurs un un, et xe son verdict ds la premire diffrence constate. Si un conteneur est un sous-ensemble du deuxime, le conteneur le plus petit est celui qui est inclus dans lautre.
Note : Remarquez que la dnition des oprateurs de comparaison dinfriorit et de supriorit existe quel que soit le type des donnes que le conteneur peut stocker. Cependant, comme les conteneurs sont dnis sous la forme de classes template, ces mthodes ne sont instancies que si elles sont effectivement utilises dans les programmes. Ainsi, il est possible dutiliser les conteneurs mme sur des types de donnes pour lesquels les oprateurs dinfriorit et de supriorit ne sont pas dnis. Cependant, cette utilisation provoquera une erreur de compilation, car le compilateur cherchera instancier les oprateurs ce moment.

17.1.5. Mthodes dintrt gnral


Enn, les conteneurs disposent de mthodes gnrales permettant dobtenir des informations sur leurs proprits. En particulier, le nombre dlments quils contiennent peut tre dtermin grce la mthode size. La mthode empty permet de dterminer si un conteneur est vide ou non. La taille maximale que peut prendre un conteneur est indique quant elle par la mthode max_size. Pour nir, tous les conteneurs disposent dune mthode swap, qui prend en paramtre un autre conteneur du mme type et qui ralise lchange des donnes des deux conteneurs. On utilisera de prfrence cette mthode toute autre technique dchange car seules les rfrences sur les structures de donnes des conteneurs sont changes avec cette fonction, ce qui garantit une complexit indpendante de la taille des conteneurs.

17.2. Les squences


Les squences sont des conteneurs qui ont principalement pour but de stocker des objets an de les traiter dans un ordre bien dni. Du fait de labsence de clef permettant didentier les objets quelles contiennent, elles ne disposent daucune fonction de recherche des objets. Les squences disposent donc gnralement que des mthodes permettant de raliser linsertion et la suppression dlments, ainsi que le parcours des lments dans lordre quelles utilisent pour les classer.

17.2.1. Fonctionnalits communes


Il existe un grand nombre de classes template de squences dans la bibliothque standard qui permettent de couvrir la majorit des besoins des programmeurs. Ces classes sont relativement varies tant dans leurs implmentations que dans leurs interfaces. Cependant, un certain nombre de fonctionnalits communes sont gres par la plupart des squences. Ce sont ces fonctionnalits que cette section se propose de vous dcrire. Les fonctionnalits spciques chaque classe de squence seront dtailles sparment dans la Section 17.2.2.1. Les exemples fournis dans cette section se baseront sur le conteneur list, qui est le type de squence le plus simple de la bibliothque standard. Cependant, ils sont parfaitement utilisables avec les autres types de squences de la bibliothque standard, avec des niveaux de performances ventuellement diffrents en fonction des squences choisies bien entendu.

429

Chapitre 17. Les conteneurs

17.2.1.1. Construction et initialisation


La construction et linitialisation dune squence peuvent se faire de multiples manires. Les squences disposent en effet de plusieurs constructeurs et de deux surcharges de la mthode assign qui permet de leur affecter un certain nombre dlments. Le constructeur le plus simple ne prend aucun paramtre, hormis un allocateur standard utiliser pour la gestion de la squence, et permet de construire une squence vide. Le deuxime constructeur prend en paramtre le nombre dlments initial de la squence et la valeur de ces lments. Ce constructeur permet donc de crer une squence contenant dj un certain nombre de copies dun objet donn. Enn, le troisime constructeur prend deux itrateurs sur une autre squence dobjets qui devront tre copis dans la squence en cours de construction. Ce constructeur peut tre utilis pour initialiser une squence partir dune autre squence ou dun sous-ensemble de squence. Les surcharges de la mthode assign se comportent un peu comme les deux derniers constructeurs, ceci prs quelles ne prennent pas dallocateur en paramtre. La premire mthode permet donc de rinitialiser la liste et de la remplir avec un certain nombre de copies dun objet donn, et la deuxime permet de rinitialiser la liste et de la remplir avec une squence dobjets dnie par deux itrateurs. Exemple 17-1. Construction et initialisation dune liste
#include <iostream> #include <list> using namespace std; typedef list<int> li; void print(li &l) { li::iterator i = l.begin(); while (i != l.end()) { cout << *i << " " ; ++i; } cout << endl; } int main(void) { // Initialise une liste avec trois lments valant 5 : li l1(3, 5); print(l1); // Initialise une autre liste partir de la premire // (en fait on devrait appeler le constructeur de copie) : li l2(l1.begin(), l1.end()); print(l2); // Affecte 4 lments valant 2 l1 : l1.assign(4, 2); print(l1); // Affecte l1 l2 (de mme, on devrait normalement // utiliser loprateur daffectation) : l2.assign(l1.begin(), l1.end()); print(l2); return 0; }

430

Chapitre 17. Les conteneurs Bien entendu, il existe galement un constructeur et un oprateur de copie capables dinitialiser une squence partir dune autre squence du mme type. Ainsi, il nest pas ncessaire dutiliser les constructeurs vus prcdemment ni les mthodes assign pour initialiser une squence partir dune autre squence de mme type.

17.2.1.2. Ajout et suppression dlments


Linsertion de nouveaux lments dans une squence se fait normalement laide de lune des surcharges de la mthode insert. Bien entendu, il existe dautres mthodes spciques chaque conteneur de type squence et qui leur sont plus appropries, mais ces mthodes ne seront dcrites que dans les sections consacres ces conteneurs. Les diffrentes versions de la mthode insert sont rcapitules ci-dessous :
iterator insert(iterator i, value_type valeur)

Permet dinsrer une copie de la valeur spcie en deuxime paramtre dans le conteneur. Le premier paramtre est un itrateur indiquant lendroit o le nouvel lment doit tre insr. Linsertion se fait immdiatement avant llment rfrenc par cet itrateur. Cette mthode renvoie un itrateur sur le dernier lment insr dans la squence.
void insert(iterator i, size_type n, value_type valeur)

Permet dinsrer n copies de llment spci en troisime paramtre avant llment rfrenc par litrateur i donn en premier paramtre.
void insert(iterator i, iterator premier, iterator dernier)

Permet dinsrer tous les lments de lintervalle dni par les itrateurs premier et dernier avant llment rfrenc par litrateur i.

Exemple 17-2. Insertion dlments dans une liste


#include <iostream> #include <list> using namespace std; typedef list<int> li; void print(li &l) { li::iterator i = l.begin(); while (i != l.end()) { cout << *i << " " ; ++i; } cout << endl; return ; } int main(void) { li l1;

431

Chapitre 17. Les conteneurs


// Ajoute 5 la liste : li::iterator i = l1.insert(l1.begin(), 5); print(l1); // Ajoute deux 3 la liste : l1.insert(i, 2, 3); print(l1); // Insre le contenu de l1 dans une autre liste : li l2; l2.insert(l2.begin(), l1.begin(), l1.end()); print(l2); return 0; }

De manire similaire, il existe deux surcharges de la mthode erase qui permettent de spcier de diffrentes manires les lments qui doivent tre supprims dune squence. La premire mthode prend en paramtre un itrateur sur llment supprimer, et la deuxime un couple ditrateurs donnant lintervalle des lments de la squence qui doivent tre supprims. Ces deux mthodes retournent un itrateur sur llment suivant le dernier lment supprim ou litrateur de n de squence sil nexiste pas de tel lment. Par exemple, la suppression de tous les lments dune liste peut tre ralise de la manire suivante :
// Rcupre un itrateur sur le premier // lment de la liste : list<int>::iterator i = instance.begin(); while (i != instance.end()) { i = instance.erase(i); }

o instance est une instance de la squence Sequence. Vous noterez que la suppression dun lment dans une squence rend invalide tous les itrateurs sur cet lment. Il est la charge du programmeur de sassurer quil nutilisera plus les itrateurs ainsi invalids. La bibliothque standard ne fournit aucun support pour le diagnostic de ce genre derreur.
Note : En ralit, linsertion dun lment peut galement invalider des itrateurs existants pour certaines squences. Les effets de bord des mthodes dinsertion et de suppression des squences seront dtaills pour chacune delle dans les sections qui leur sont ddies. Il existe une mthode clear dont le rle est de vider compltement un conteneur. On utilisera donc cette mthode dans la pratique, le code donn ci-dessous ne ltait qu titre dexemple.

La complexit de toutes ces mthodes dpend directement du type de squence sur lequel elles sont appliques. Les avantages et les inconvnients de chaque squence seront dcrits dans la Section 17.2.2.

17.2.2. Les diffrents types de squences


La bibliothque standard fournit trois classes fondamentales de squence. Ces trois classes sont respectivement la classe list, la classe vector et la classe deque. Chacune de ces classes possde ses spcicits en fonction desquelles le choix du programmeur devra se faire. De plus, la bibliothque

432

Chapitre 17. Les conteneurs standard fournit galement des classes adaptatrices permettant de construire des conteneurs quivalents, mais disposant dune interface plus standard et plus habituelle aux notions couramment utilises en informatique. Toutes ces classes sont dcrites dans cette section, les adaptateurs tant abords en dernire partie.

17.2.2.1. Les listes


La classe template list est certainement lune des plus importantes car, comme son nom lindique, elle implmente une structure de liste chane dlments, ce qui est sans doute lune des structures les plus utilises en informatique. Cette structure est particulirement adapte pour les algorithmes qui parcourent les donnes dans un ordre squentiel. Les proprits fondamentales des listes sont les suivantes :

elles implmentent des itrateurs bidirectionnels. Cela signie quil est facile de passer dun lment au suivant ou au prcdent, mais quil nest pas possible daccder aux lments de la liste de manire alatoire ; elles permettent linsertion et la suppression dun lment avec un cot constant, et sans invalider les itrateurs ou les rfrences sur les lments de la liste existants. Dans le cas dune suppression, seuls les itrateurs et les rfrences sur les lments supprims sont invalids.

Les listes offrent donc la plus grande souplesse possible sur les oprations dinsertion et de suppression des lments, en contrepartie de quoi les accs sont restreints un accs squentiel. Comme linsertion et la suppression des lments en tte et en queue de liste peuvent se faire sans recherche, ce sont videmment les oprations les plus courantes. Par consquent, la classe template list propose des mthodes spciques permettant de manipuler les lments qui se trouvent en ces positions. Linsertion dun lment peut donc tre ralise respectivement en tte et en queue de liste avec les mthodes push_front et push_back. Inversement, la suppression des lments situs en ces emplacements est ralise avec les mthodes pop_front et pop_back. Toutes ces mthodes ne renvoient aucune valeur, aussi laccs aux deux lments situs en tte et en queue de liste peut-il tre ralis respectivement par lintermdiaire des accesseurs front et back, qui renvoient tous deux une rfrence (ventuellement constante si la liste est elle-mme constante) sur ces lments. Exemple 17-3. Accs la tte et la queue dune liste
#include <iostream> #include <list> using namespace std; typedef list<int> li; int main(void) { li l1; l1.push_back(2); l1.push_back(5); cout << "Tte : " << l1.front() cout << "Queue : " << l1.back() l1.push_front(7); cout << "Tte : " << l1.front() cout << "Queue : " << l1.back()

<< endl; << endl; << endl; << endl;

433

Chapitre 17. Les conteneurs


l1.pop_back(); cout << "Tte : " << l1.front() << endl; cout << "Queue : " << l1.back() << endl; return 0; }

Les listes disposent galement de mthodes spciques qui permettent de leur appliquer des traitements qui leur sont propres. Ces mthodes sont dcrites dans le tableau ci-dessous : Tableau 17-1. Mthodes spciques aux listes Mthode remove(const T &) Fonction Permet dliminer tous les lments dune liste dont la valeur est gale la valeur passe en paramtre. Lordre relatif des lments qui ne sont pas supprims est inchang. La complexit de cette mthode est linaire en fonction du nombre dlments de la liste.

remove_if(Predicat)Permet dliminer tous les lments dune liste qui vrient le prdicat unaire pass en paramtre. Lordre relatif des lments qui ne sont pas supprims est inchang. La complexit de cette mthode est linaire en fonction du nombre dlments de la liste. unique(Predicat) Permet dliminer tous les lments pour lesquels le prdicat binaire pass en paramtre est vri avec comme valeur llment courant et son prdcesseur. Cette mthode permet dliminer les doublons successifs dans une liste selon un critre dni par le prdicat. Par souci de simplicit, il existe une surcharge de cette mthode qui ne prend pas de paramtres, et qui utilise un simple test dgalit pour liminer les doublons. Lordre relatif des lments qui ne sont pas supprims est inchang, et le nombre dapplications du prdicat est exactement le nombre dlments de la liste moins un si la liste nest pas vide. Injecte le contenu de la liste fournie en deuxime paramtre dans la liste courante partir de la position fournie en premier paramtre. Les lments injects sont les lments de la liste source identis par les itrateurs premier et dernier. Ils sont supprims de la liste source la vole. Cette mthode dispose de deux autres surcharges, lune ne fournissant pas ditrateur de dernier lment et qui insre uniquement le premier lment, et lautre ne fournissant aucun itrateur pour rfrencer les lments injecter. Cette dernire surcharge ne prend donc en paramtre que la position laquelle les lments doivent tre insrs et la liste source elle-mme. Dans ce cas, la totalit de la liste source est insre en cet emplacement. Gnralement, la complexit des mthodes splice est proportionnelle au nombre dlments injects, sauf dans le cas de la dernire surcharge, qui sexcute avec une complexit constante. Trie les lments de la liste dans lordre dni par le prdicat binaire de comparaison pass en paramtre. Encore une fois, il existe une surcharge de cette mthode qui ne prend pas de paramtre et qui utilise loprateur dinfriorit pour comparer les lments de la liste entre eux. Lordre relatif des lments quivalents (cest--dire des lments pour lesquels le prdicat de comparaison na pas pu statuer dordre bien dni) est inchang lissue de lopration de tri. On indique souvent cette proprit en disant que cette mthode est stable. La mthode sort sapplique avec une complexit gale Nln(N), o N est le nombre dlments de la liste.

splice(iterator position, list<T, Allocator> liste, iterator premier, iterateur dernier)

sort(Predicat)

434

Chapitre 17. Les conteneurs Mthode merge(list<T, Allocator>, Predicate) Fonction Injecte les lments de la liste fournie en premier paramtre dans la liste courante en conservant lordre dni par le prdicat binaire fourni en deuxime paramtre. Cette mthode suppose que la liste sur laquelle elle sapplique et la liste fournie en paramtre sont dj tries selon ce prdicat, et garantit que la liste rsultante sera toujours trie. La liste fournie en argument est vide lissue de lopration. Il existe galement une surcharge de cette mthode qui ne prend pas de second paramtre et qui utilise loprateur dinfriorit pour comparer les lments des deux listes. La complexit de cette mthode est proportionnelle la somme des tailles des deux listes ainsi fusionnes. Inverse lordre des lments de la liste. Cette mthode sexcute avec une complexit linaire en fonction du nombre dlments de la liste.

reverse

Exemple 17-4. Manipulation de listes


#include <iostream> #include <functional> #include <list> using namespace std; typedef list<int> li; void print(li &l) { li::iterator i = l.begin(); while (i != l.end()) { cout << *i << " "; ++i; } cout << endl; return ; } bool parity_even(int i) { return (i & 1) == 0; } int main(void) { // Construit une liste exemple : li l; l.push_back(2); l.push_back(5); l.push_back(7); l.push_back(7); l.push_back(3); l.push_back(3); l.push_back(2); l.push_back(6); l.push_back(6);

435

Chapitre 17. Les conteneurs


l.push_back(6); l.push_back(3); l.push_back(4); cout << "Liste de dpart :" << endl; print(l); li l1; // Liste en ordre inverse : l1 = l; l1.reverse(); cout << "Liste inverse :" << endl; print(l1); // Trie la liste : l1 = l; l1.sort(); cout << "Liste trie : " << endl; print(l1); // Supprime tous les 3 : l1 = l; l1.remove(3); cout << "Liste sans 3 :" << endl; print(l1); // Supprime les doublons : l1 = l; l1.unique(); cout << "Liste sans doublon :" << endl; print(l1); // Retire tous les nombres pairs : l1 = l; l1.remove_if(ptr_fun(&parity_even)); cout << "Liste sans nombre pair :" << endl; print(l1); // Injecte une autre liste entre les 7 : l1 = l; li::iterator i = l1.begin(); ++i; ++i; ++i; li l2; l2.push_back(35); l2.push_back(36); l2.push_back(37); l1.splice(i, l2, l2.begin(), l2.end()); cout << "Fusion des deux listes :" << endl; print(l1); if (l2.size() == 0) cout << "l2 est vide" << endl; return 0; }

17.2.2.2. Les vecteurs


La classe template vector de la bibliothque standard fournit une structure de donnes dont la smantique est proche de celle des tableaux de donnes classiques du langage C/C++. Laccs aux donnes de manire alatoire est donc ralisable en un cot constant, mais linsertion et la suppression des lments dans un vecteur ont des consquences nettement plus lourdes que dans le cas des listes. Les proprits des vecteurs sont les suivantes :

436

Chapitre 17. Les conteneurs


les itrateurs permettent les accs alatoires aux lments du vecteur ; linsertion ou la suppression dun lment la n du vecteur se fait avec une complexit constante, mais linsertion ou la suppression en tout autre point du vecteur se fait avec une complexit linaire. Autrement dit, les oprations dinsertion ou de suppression ncessitent a priori de dplacer tous les lments suivants, sauf si llment insr ou supprim se trouve en dernire position ; dans tous les cas, linsertion dun lment peut ncessiter une rallocation de mmoire. Cela a pour consquence quen gnral, les donnes du vecteur peuvent tre dplaces en mmoire et que les itrateurs et les rfrences sur les lments dun vecteur sont a priori invalids la suite dune insertion. Cependant, si aucune rallocation na lieu, les itrateurs et les rfrences ne sont pas invalids pour tous les lments situs avant llment insr ; la suppression dun lment ne provoquant pas de rallocation, seuls les itrateurs et les rfrences sur les lments suivant llment supprim sont invalids.

Note : Notez bien que les vecteurs peuvent effectuer une rallocation mme lorsque linsertion se fait en dernire position. Dans ce cas, le cot de linsertion est bien entendu trs lev. Toutefois, lalgorithme de rallocation utilis est sufsament volu pour garantir que ce cot est constant en moyenne (donc de complexit constante). Autrement dit, les rallocations ne se font que trs rarement.

Tout comme la classe list, la classe template vector dispose de mthodes front et back qui permettent daccder respectivement au premier et au dernier lment des vecteurs. Cependant, contrairement aux listes, seule les mthodes push_back et pop_back sont dnies, car les vecteurs ne permettent pas dinsrer et de supprimer leurs premiers lments de manire rapide. En revanche, comme nous lavons dj dit, les vecteurs ont la mme smantique que les tableaux et permettent donc un accs rapide tous leurs lments. La classe vector dnit donc une mthode at qui prend en paramtre lindice dun lment dans le vecteur et qui renvoie une rfrence, ventuellement constante si le vecteur lest lui-mme, sur cet lment. Si lindice fourni en paramtre rfrence un lment situ en dehors du vecteur, la mthode at lance une exception out_of_range. De mme, il est possible dappliquer loprateur [] utilis habituellement pour accder aux lments des tableaux. Cet oprateur se comporte exactement comme la mthode at, et est donc susceptible de lancer une exception out_of_range. Exemple 17-5. Accs aux lments dun vecteur
#include <iostream> #include <vector> using namespace std; int main(void) { typedef vector<int> vi; // Cre un vecteur de 10 lments : vi v(10); // Modifie quelques lments : v.at(2) = 2; v.at(5) = 7; // Redimensionne le vecteur : v.resize(11); v.at(10) = 5;

437

Chapitre 17. Les conteneurs


// Ajoute un lment la fin du vecteur : v.push_back(13); // Affiche le vecteur en utilisant loprateur [] : for (int i=0; i<v.size(); ++i) { cout << v[i] << endl; } return 0; }

Par ailleurs, la bibliothque standard dnit une spcialisation de la classe template vector pour le type bool. Cette spcialisation a essentiellement pour but de rduire la consommation mmoire des vecteurs de boolens, en codant ceux-ci raison dun bit par boolen seulement. Les rfrences des lments des vecteurs de boolens ne sont donc pas rellement des boolens, mais plutt une classe spciale qui simule ces boolens tout en manipulant les bits rellement stocks dans ces vecteurs. Ce mcanisme est donc compltement transparent pour lutilisateur, et les vecteurs de boolens se manipulent exactement comme les vecteurs classiques.
Note : La classe de rfrence des vecteurs de boolens disposent toutefois dune mthode flip dont le rle est dinverser la valeur du bit correspondant au boolen que la rfrence reprsente. Cette mthode peut tre pratique utiliser lorsquon dsire inverser rapidement la valeur dun des lments du vecteur.

17.2.2.3. Les deques


Pour ceux qui les listes et les vecteurs ne conviennent pas, la bibliothque standard fournit un conteneur plus volu qui offre un autre compromis entre la rapidit daccs aux lments et la souplesse dans les oprations dajout ou de suppression. Il sagit de la classe template deque, qui implmente une forme de tampon circulaire dynamique. Les proprits des deques sont les suivantes :

les itrateurs des deques permettent les accs alatoires leurs lments ; linsertion et la suppression des lments en premire et en dernire position se fait avec un cot constant. Notez ici que ce cot est toujours le mme, et que, contrairement aux vecteurs, il ne sagit pas dun cot amorti (autrement dit, ce nest pas une moyenne). En revanche, tout comme pour les vecteurs, linsertion et la suppression aux autres positions se fait avec une complexit linaire ; contrairement aux vecteurs, tous les itrateurs et toutes les rfrences sur les lments de la deque deviennent systmatiquement invalides lors dune insertion ou dune suppression dlment aux autres positions que la premire et la dernire ; de mme, linsertion dun lment en premire et dernire position invalide tous les itrateurs sur les lments de la deque. En revanche, les rfrences sur les lments restent valides. Remarquez que la suppression dun lment en premire et en dernire position na aucun impact sur les itrateurs et les rfrences des lments autres que ceux qui sont supprims.

Comme vous pouvez le constater, les deques sont donc extrmement bien adapts aux oprations dinsertion et de suppression en premire et en dernire position, tout en fournissant un accs rapide leurs lments. En revanche, les itrateurs existants sont systmatiquement invalids, quel que soit le type dopration effectue, hormis la suppression en tte et en n de deque.

438

Chapitre 17. Les conteneurs Comme elle permet un accs rapide tous ses lments, la classe template deque dispose de toutes les mthodes dinsertion et de suppression dlments des listes et des vecteurs. Outre les mthodes push_front, pop_front, push_back, pop_back et les accesseurs front et back, la classe deque dnit donc la mthode at, ainsi que loprateur daccs aux lments de tableaux []. Lutilisation de ces mthodes est strictement identique celle des mthodes homonymes des classes list et vector et ne devrait donc pas poser de problme particulier.

17.2.2.4. Les adaptateurs de squences


Les classes des squences de base list, vector et deque sont supposes satisfaire la plupart des besoins courants des programmeurs. Cependant, la bibliothque standard fournit des adaptateurs pour transformer ces classes en dautres structures de donnes plus classiques. Ces adaptateurs permettent de construire des piles, des les et des les de priorit. 17.2.2.4.1. Les piles Les piles sont des structures de donnes qui se comportent, comme leur nom lindique, comme un empilement dobjets. Elles ne permettent donc daccder quaux lments situs en haut de la pile, et la rcupration des lments se fait dans lordre inverse de leur empilement. En raison de cette proprit, on les appelle galement couramment LIFO, acronyme de langlais Last In First Out (dernier entr, premier sorti). La classe adaptatrice dnie par la bibliothque standard C++ pour implmenter les piles est la classe template stack. Cette classe utilise deux paramtres template : le type des donnes lui-mme et le type dune classe de squence implmentant au moins les mthodes back, push_back et pop_back. Il est donc parfaitement possible dutiliser les listes, deques et vecteurs pour implmenter une pile laide de cet adaptateur. Par dfaut, la classe stack utilise une deque, et il nest donc gnralement pas ncessaire de spcier le type du conteneur utiliser pour raliser la pile. Linterface des piles se rduit au strict minimum, puisquelles ne permettent de manipuler que leur sommet. La mthode push permet dempiler un lment sur la pile, et la mthode pop de len retirer. Ces deux mthodes ne renvoient rien, laccs llment situ au sommet de la pile se fait donc par lintermdiaire de la mthode top. Exemple 17-6. Utilisation dune pile
#include <iostream> #include <stack> using namespace std; int main(void) { typedef stack<int> si; // Cre une pile : si s; // Empile quelques lments : s.push(2); s.push(5); s.push(8); // Affiche les lments en ordre inverse : while (!s.empty()) { cout << s.top() << endl;

439

Chapitre 17. Les conteneurs


s.pop(); } return 0; }

17.2.2.4.2. Les les Les les sont des structures de donnes similaires aux piles, la diffrence prs que les lments sont mis les uns la suite des autres au lieu dtre empils. Leur comportement est donc celui dune le dattente o tout le monde serait honnte (cest--dire que personne ne doublerait les autres). Les derniers entrs sont donc ceux qui sortent galement en dernier, do leur dnomination de FIFO (de langlais First In First Out ). Les les sont implmentes par la classe template queue. Cette classe utilise comme paramtre template le type des lments stocks ainsi que le type dun conteneur de type squence pour lequel les mthodes front, back, push_back et pop_front sont implmentes. En pratique, il est possible dutiliser les listes et les deques, la classe queue utilisant dailleurs ce type de squence par dfaut comme conteneur sous-jacent.
Note : Ne confondez pas la classe queue et la classe deque. La premire nest quun simple adaptateur pour les les dlments, alors que la deuxime est un conteneur trs volu et beaucoup plus complexe.

Les mthodes fournies par les les sont les mthodes front et back, qui permettent daccder respectivement au premier et au dernier lment de la le dattente, ainsi que les mthodes push et pop, qui permettent respectivement dajouter un lment la n de la le et de supprimer llment qui se trouve en tte de le. Exemple 17-7. Utilisation dune le
#include <iostream> #include <queue> using namespace std; int main(void) { typedef queue<int> qi; // Cre une file : qi q; // Ajoute quelques lments : q.push(2); q.push(5); q.push(8); // Affiche rcupre et affiche les lments : while (!q.empty()) { cout << q.front() << endl; q.pop(); } return 0; }

440

Chapitre 17. Les conteneurs 17.2.2.4.3. Les les de priorits Enn, la bibliothque standard fournit un adaptateur permettant dimplmenter les les de priorits. Les les de priorits ressemblent aux les classiques, mais ne fonctionnent pas de la mme manire. En effet, contrairement aux les normales, llment qui se trouve en premire position nest pas toujours le premier lment qui a t plac dans la le, mais celui qui dispose de la plus grande valeur. Cest cette proprit qui a donn son nom aux les de priorits, car la priorit dun lment est ici donne par sa valeur. Bien entendu, la bibliothque standard permet lutilisateur de dnir son propre oprateur de comparaison, an de lui laisser spcier lordre quil veut utiliser pour dnir la priorit des lments.
Note : On prendra garde au fait que la bibliothque standard nimpose pas aux les de priorits de se comporter comme des les classiques avec les lments de priorits gales. Cela signie que si plusieurs lments de priorit gale sont insrs dans une le de priorit, ils nen sortiront pas forcment dans lordre dinsertion. On dit gnralement que les algorithmes utiliss par les les de priorits ne sont pas stables pour traduire cette proprit.

La classe template fournie par la bibliothque standard pour faciliter limplmentation des les de priorit est la classe priority_queue. Cette classe prend trois paramtres template : le type des lments stocks, le type dun conteneur de type squence permettant un accs direct ses lments et implmentant les mthodes front, push_back et pop_back, et le type dun prdicat binaire utiliser pour la comparaison des priorits des lments. On peut donc implmenter une le de priorit partir dun vecteur ou dune deque, sachant que, par dfaut, la classe priority_queue utilise un vecteur. Le prdicat de comparaison utilis par dfaut est le foncteur less<T>, qui effectue une comparaison laide de loprateur dinfriorit des lments stocks dans la le. Comme les les de priorits se rorganisent chaque fois quun nouvel lment est ajout en n de le, et que cet lment ne se retrouve par consquent pas forcment en dernire position sil est de priorit leve, accder au dernier lment des les de priorit na pas de sens. Il nexiste donc quune seule mthode permettant daccder llment le plus important de la pile : la mthode top. En revanche, les les de priorit implmentent effectivement les mthodes push et pop, qui permettent respectivement dajouter un lment dans la le de priorit et de supprimer llment le plus important de cette le. Exemple 17-8. Utilisation dune le de priorit
#include <iostream> #include <queue> using namespace std; // Type des donnes stockes dans la file : struct A { int k; // Priorit const char *t; // Valeur A() : k(0), t(0) {} A(int k, const char *t) : k(k), t(t) {} }; // Foncteur de comparaison selon les priorits : class C { public:

441

Chapitre 17. Les conteneurs


bool operator()(const A &a1, const A &a2) { return a1.k < a2.k ; } }; int main(void) { // Construit quelques objets : A a1(1, "Priorit faible"); A a2(2, "Priorit moyenne 1"); A a3(2, "Priorit moyenne 2"); A a4(3, "Priorit haute 1"); A a5(3, "Priorit haute 2"); // Construit une file de priorit : priority_queue<A, vector<A>, C> pq; // Ajoute les lments : pq.push(a5); pq.push(a3); pq.push(a1); pq.push(a2); pq.push(a4); // Rcupre les lments par ordre de priorit : while (!pq.empty()) { cout << pq.top().t << endl; pq.pop(); } return 0; }

Note : En raison de la ncessit de rorganiser lordre du conteneur sous-jacent chaque ajout ou suppression dun lment, les mthodes push et pop sexcutent avec une complexit en ln(N), o N est le nombre dlments prsents dans la le de priorit. Les les de priorit utilisent en interne la structure de tas, que lon dcrira dans le chapitre traitant des algorithmes de la bibliothque standard la section Section 18.3.1.

17.3. Les conteneurs associatifs


Contrairement aux squences, les conteneurs associatifs sont capables didentier leurs lments laide de la valeur de leur clef. Grce ces clefs, les conteneurs associatifs sont capables deffectuer des recherches dlments de manire extrmement performante. En effet, les oprations de recherche se font gnralement avec un cot logarithmique seulement, ce qui reste gnralement raisonnable mme lorsque le nombre dlments stocks devient grand. Les conteneurs associatifs sont donc particulirement adapts lorsquon a besoin de raliser un grand nombre dopration de recherche. La bibliothque standard distingue deux types de conteneurs associatifs : les conteneurs qui diffrencient la valeur de la clef de la valeur de lobjet lui-mme et les conteneurs qui considrent que les objets sont leur propre clef. Les conteneurs de la premire catgorie constituent ce que lon appelle

442

Chapitre 17. Les conteneurs des associations car ils permettent dassocier des clefs aux valeurs des objets. Les conteneurs associatifs de la deuxime catgorie sont appels quant eux des ensembles, en raison du fait quils servent gnralement indiquer si un objet fait partie ou non dun ensemble dobjets. On ne sintresse dans ce cas pas la valeur de lobjet, puisquon la connat dj si on dispose de sa clef, mais plutt son appartenance ou non un ensemble donn. Si tous les conteneurs associatifs utilisent la notion de clef, tous ne se comportent pas de manire identique quant lutilisation quils en font. Pour certains conteneurs, que lon qualie de conteneurs clefs uniques , chaque lment contenu doit avoir une clef qui lui est propre. Il est donc impossible dinsrer plusieurs lments distincts avec la mme clef dans ces conteneurs. En revanche, les conteneurs associatif dits clefs multiples permettent lutilisation dune mme valeur de clef pour plusieurs objets distincts. Lopration de recherche dun objet partir de sa clef peut donc, dans ce cas, renvoyer plus dun seul objet. La bibliothque standard fournit donc quatre types de conteneurs au total, selon que ce sont des associations ou des ensembles, et selon que ce sont des conteneurs associatifs clefs multiples ou non. Les associations clefs uniques et clefs multiple sont implmentes respectivement par les classes template map et multimap, et les ensembles clefs uniques et clefs multiples par les classes template set et multiset. Cependant, bien que ces classes se comportent de manire profondment diffrentes, elles fournissent les mmes mthodes permettant de les manipuler. Les conteneurs associatifs sont donc moins htroclites que les squences, et leur manipulation en est de beaucoup facilite. Les sections suivantes prsentent les diffrentes fonctionnalits des conteneurs associatifs dans leur ensemble. Les exemples seront donns en utilisant la plupart du temps la classe template map, car cest certainement la classe la plus utilise en pratique en raison de sa capacit stocker et retrouver rapidement des objets identis de manire unique par un identiant. Cependant, certains exemples utiliseront des conteneurs clefs multiples an de bien montrer les rares diffrences qui existent entre les conteneurs clefs uniques et les conteneurs clefs multiples.

17.3.1. Gnralits et proprits de base des clefs


La contrainte fondamentale que les algorithmes des conteneurs associatifs imposent est quil existe une relation dordre pour le type de donne utilis pour les clefs des objets. Cette relation peut tre dnie soit implicitement par un oprateur dinfriorit, soit par un foncteur que lon peut spcier en tant que paramtre template des classes des conteneurs. Alors que lordre de la suite des lments stocks dans les squences est trs important, ce nest pas le cas avec les conteneurs associatifs, car ceux-ci se basent exclusivement sur lordre des clefs des objets. En revanche, la bibliothque standard C++ garantit que le sens de parcours utilis par les itrateurs des conteneurs associatifs est non dcroissant sur les clefs des objets itrs. Cela signie que le test dinfriorit strict entre la clef de llment suivant et la clef de llment courant est toujours faux, ou, autrement dit, llment suivant nest pas plus petit que llment courant.
Note : Attention, cela ne signie aucunement que les lments sont classs dans lordre croissant des clefs. En effet, lexistence dun oprateur dinfriorit nimplique pas forcment celle dun oprateur de supriorit dune part, et deux valeurs comparables par cet oprateur ne le sont pas forcment par loprateur de supriorit. Llment suivant nest donc pas forcment plus grand que llment courant. En particulier, pour les conteneurs clefs multiples, les clefs de deux lments successifs peuvent tre gales. En revanche, le classement utilis par les itrateurs des conteneurs clefs uniques est plus fort, puisque dans ce cas, on na pas se soucier des clefs ayant la mme valeur. La squence des valeurs itres est donc cette fois strictement croissante, cest--dire que la clef de llment courant est toujours strictement infrieure la clef de llment suivant.

443

Chapitre 17. Les conteneurs

Comme pour tous les conteneurs, le type des lments stocks par les conteneurs associatifs est le type value_type. Cependant, contrairement aux squences, ce type nest pas toujours le type template par lequel le conteneur est paramtr. En effet, ce type est une paire contenant le couple de valeurs form par la clef et par lobjet lui-mme pour toutes les associations (cest--dire pour les map et les multimap). Dans ce cas, les mthodes du conteneur qui doivent effectuer des comparaisons sur les objets se basent uniquement sur le champ first de la paire encapsulant le couple (clef, valeur) de chaque objet. Autrement dit, les comparaisons dobjets sont toujours dnies sur les clefs, et jamais sur les objets eux-mmes. Bien entendu, pour les ensembles, le type value_type est strictement quivalent au type template par lequel ils sont paramtrs. Pour simplier lutilisation de leurs clefs, les conteneurs associatifs dnissent quelques types complmentaires de ceux que lon a dj prsents dans la Section 17.1.2. Le plus important de ces types est sans doute le type key_type qui, comme son nom lindique, reprsente le type des clefs utilises par ce conteneur. Ce type constitue donc, avec le type value_type, lessentiel des informations de typage des conteneurs associatifs. Enn, les conteneurs dnissent galement des types de prdicats permettant deffectuer des comparaisons entre deux clefs et entre deux objets de type value_type. Il sagit des types key_compare et value_compare.

17.3.2. Construction et initialisation


Les conteneurs associatifs disposent de plusieurs surcharges de leurs constructeurs qui permettent de les crer et de les initialiser directement. De manire gnrale, ces constructeurs prennent tous deux paramtres an de laisser au programmeur la possibilit de dnir la valeur du foncteur quils doivent utiliser pour comparer les clefs, ainsi quune instance de lallocateur utiliser pour les oprations mmoire. Comme pour les squences, ces paramtres disposent de valeurs par dfaut, si bien quen gnral il nest pas ncessaire de les prciser. Hormis le constructeur de copie et le constructeur par dfaut, les conteneurs associatifs fournissent un troisime constructeur permettant de les initialiser partir dune srie dobjets. Ces objets sont spcis par deux itrateurs, le premier indiquant le premier objet insrer dans le conteneur et le deuxime litrateur rfrenant llment suivant le dernier lment insrer. Lutilisation de ce constructeur est semblable au constructeur du mme type dni pour les squences et ne devrait donc pas poser de problme particulier. Exemple 17-9. Construction et initialisation dune association simple
#include <iostream> #include <map> #include <list> using namespace std; int main(void) { typedef map<int, char *> Int2String; // Remplit une liste dlments pour ces maps : typedef list<pair<int, char *> > lv; lv l; l.push_back(lv::value_type(1, "Un")); l.push_back(lv::value_type(2, "Deux")); l.push_back(lv::value_type(5, "Trois")); l.push_back(lv::value_type(6, "Quatre"));

444

Chapitre 17. Les conteneurs


// Construit une map et linitialise avec la liste : Int2String i2s(l.begin(), l.end()); // Affiche le contenu de la map : Int2String::iterator i = i2s.begin(); while (i != i2s.end()) { cout << i->second << endl; ++i; } return 0; }

Note : Contrairement aux squences, les conteneurs associatifs ne disposent pas de mthode assign permettant dinitialiser un conteneur avec des objets provenant dune squence ou dun autre conteneur associatif. En revanche, ils disposent dun constructeur et dun oprateur de copie.

17.3.3. Ajout et suppression dlments


Du fait de lexistence des clefs, les mthodes dinsertion et de suppression des conteneurs associatifs sont lgrement diffrentes de celles des squences. De plus, elles nont pas tout fait la mme signication. En effet, les mthodes dinsertion des conteneurs associatifs ne permettent pas, contrairement celles des squences, de spcier lemplacement o un lment doit tre insr puisque lordre des lments est impos par la valeur de leur clef. Les mthodes dinsertion des conteneurs associatifs sont prsentes ci-dessous :
iterator insert(iterator i, const value_type &valeur)

Insre la valeur valeur dans le conteneur. Litrateur i indique lemplacement probable dans le conteneur o linsertion doit tre faite. Cette mthode peut donc tre utilise pour les algorithmes qui connaissent dj plus ou moins lordre des lments quils insrent dans le conteneur an doptimiser les performances du programme. En gnral, linsertion se fait avec une complexit de ln(N) (o N est le nombre dlments dj prsents dans le conteneur). Toutefois, si llment est insr aprs litrateur i dans le conteneur, la complexit est constante. Linsertion se fait systmatiquement pour les conteneurs clefs multiples, mais peut ne pas avoir lieu si un lment de mme clef que celui que lon veut insrer est dj prsent pour les conteneurs clefs uniques. Dans tous les cas, la valeur retourne est un itrateur rfrenant llment insr ou llment ayant la mme clef que llment insrer.
void insert(iterator premier, iterator dernier)

Insre les lments de lintervalle dni par les itrateurs premier et dernier dans le conteneur. La complexit de cette mthode est nln(n+N) en gnral, o N est le nombre dlments dj prsents dans le conteneur et n est le nombre dlments insrer. Toutefois, si les lments insrer sont classs dans lordre de loprateur de comparaison utilis par le conteneur, linsertion se fait avec un cot proportionnel au nombre dlments insrer.
pair<iterator, bool> insert(const value_type &valeur)

Insre ou tente dinsrer un nouvel lment dans un conteneur clefs uniques. Cette mthode renvoie une paire contenant litrateur rfrenant cet lment dans le conteneur et un boolen

445

Chapitre 17. Les conteneurs indiquant si linsertion a effectivement eu lieu. Cette mthode nest dnie que pour les conteneurs associatifs clefs uniques (cest--dire les map et les set). Si aucun lment du conteneur ne correspond la clef de llment pass en paramtre, cet lment est insr dans le conteneur et la valeur renvoye dans le deuxime champ de la paire vaut true. En revanche, si un autre lment utilisant cette clef existe dj dans le conteneur, aucune insertion na lieu et le deuxime champ de la paire renvoye vaut alors false. Dans tous les cas, litrateur stock dans le premier champ de la valeur de retour rfrence llment insr ou trouv dans le conteneur. La complexit de cette mthode est logarithmique.
iterator insert(const value_type &valeur)

Insre un nouvel lment dans un conteneur clefs multiples. Cette insertion se produit quil y ait dj ou non un autre lment utilisant la mme clef dans le conteneur. La valeur retourne est un itrateur rfrenant le nouvel lment insr. Vous ne trouverez cette mthode que sur les conteneurs associatifs clefs multiples, cest-a-dire sur les multimap et les multiset. La complexit de cette mthode est logarithmique.

Comme pour les squences, la suppression des lments des conteneurs associatifs se fait laide des surcharges de la mthode erase. Les diffrentes versions de cette mthode sont indiques ci-dessous :

void erase(iterator i)

Permet de supprimer llment rfrenc par litrateur i. Cette opration a un cot amorti constant car aucune recherche nest ncessaire pour localiser llment.
void erase(iterator premier, iterator dernier)

Supprime tous les lments de lintervalle dni par les deux itrateurs premier et dernier. La complexit de cette opration est ln(N)+n, o N est le nombre dlments du conteneur avant suppression et n est le nombre dlments qui seront supprims.
size_type erase(key_type clef)

Supprime tous les lments dont la clef est gale la valeur passe en paramtre. Cette opration a pour complexit ln(N)+n, o N est le nombre dlments du conteneur avant suppression et n est le nombre dlments qui seront supprims. Cette fonction retourne le nombre dlments effectivement supprims. Ce nombre peut tre nul si aucun lment ne correspond la clef fournie en paramtre, ou valoir 1 pour les conteneurs clefs uniques, ou tre suprieur 1 pour les conteneurs clefs multiples. Les conteneurs associatifs disposent galement, tout comme les squences, dune mthode clear permettant de vider compltement un conteneur. Cette opration est ralise avec un cot proportionnel au nombre dlments se trouvant dans le conteneur. Exemple 17-10. Insertion et suppression dlments dune association
#include <iostream> #include <map> using namespace std; typedef map<int, char *> Int2String; void print(Int2String &m)

446

Chapitre 17. Les conteneurs


{ Int2String::iterator i = m.begin(); while (i != m.end()) { cout << i->second << endl; ++i; } return ; } int main(void) { // Construit une association Entier -> Chane : Int2String m; // Ajoute quelques lments : m.insert(Int2String::value_type(2, "Deux")); pair<Int2String::iterator, bool> res = m.insert(Int2String::value_type(3, "Trois")); // On peut aussi spcifier un indice sur // lemplacement o linsertion aura lieu : m.insert(res.first, Int2String::value_type(5, "Cinq")); // Affiche le contenu de lassociation : print(m); // Supprime llment de clef 2 : m.erase(2); // Supprime llment "Trois" par son itrateur : m.erase(res.first); print(m); return 0; }

17.3.4. Fonctions de recherche


Les fonctions de recherche des conteneurs associatifs sont puissantes et nombreuses. Ces mthodes sont dcrites ci-dessous :
iterator find(key_type clef)

Renvoie un itrateur rfrenant un lment du conteneur dont la clef est gale la valeur passe en paramtre. Dans le cas des conteneurs clefs multiples, litrateur renvoy rfrence un des lments dont la clef est gale la valeur passe en paramtre. Attention, ce nest pas forcment le premier lment du conteneur vriant cette proprit. Si aucun lment ne correspond la clef, litrateur de n du conteneur est renvoy.
iterator lower_bound(key_type clef)

Renvoie un itrateur sur le premier lment du conteneur dont la clef est gale la valeur passe en paramtre. Les valeurs suivantes de litrateur rfrenceront les lments suivants dont la clef est suprieure ou gale la clef de cet lment.

447

Chapitre 17. Les conteneurs


iterator upper_bound(key_type clef)

Renvoie un itrateur sur llment suivant le dernier lment dont la clef est gale la valeur passe en paramtre. Sil ny a pas de tel lment, cest--dire si le dernier lment du conteneur utilise cette valeur de clef, renvoie litrateur de n du conteneur.
pair<iterator, iterator> equal_range(key_type clef)

Renvoie une paire ditrateurs gaux respectivement aux itrateurs renvoys par les mthodes lower_bound et upper_bound. Cette paire ditrateurs rfrence donc tous les lments du conteneur dont la clef est gale la valeur passe en paramtre.

Exemple 17-11. Recherche dans une association


#include <iostream> #include <map> using namespace std; int main(void) { // Dclare une map clefs multiples : typedef multimap<int, char *> Int2String; Int2String m; // Remplit la map : m.insert(Int2String::value_type(2, "Deux")); m.insert(Int2String::value_type(3, "Drei")); m.insert(Int2String::value_type(1, "Un")); m.insert(Int2String::value_type(3, "Three")); m.insert(Int2String::value_type(4, "Quatre")); m.insert(Int2String::value_type(3, "Trois")); // Recherche un lment de clef 4 et laffiche : Int2String::iterator i = m.find(4); cout << i->first << " : " << i->second << endl; // Recherche le premier lment de clef 3 : i = m.lower_bound(3); // Affiche tous les lments dont la clef vaut 3 : while (i != m.upper_bound(3)) { cout << i->first << " : " << i->second << endl; ++i; } // Effectue la mme opration, mais de manire plus efficace // (upper_bound nest pas appele chaque itration) : pair<Int2String::iterator, Int2String::iterator> p = m.equal_range(3); for (i = p.first; i != p.second; ++i) { cout << i->first << " : " << i->second << endl; } return 0; }

Note : Il existe galement des surcharges const pour ces quatre mthodes de recherche an de pouvoir les utiliser sur des conteneurs constants. Ces mthodes retournent des valeurs de type

448

Chapitre 17. Les conteneurs


const_iterator au lieu des itrateurs classiques, car il est interdit de modier les valeurs stockes dans un conteneur de type const. La classe template map fournit galement une surcharge pour loprateur daccs aux membres de tableau []. Cet oprateur renvoie la valeur de llment rfrenc par sa clef et permet dobtenir directement cette valeur sans passer par la mthode find et un drfrencement de litrateur ainsi obtenu. Cet oprateur insre automatiquement un nouvel lment construit avec la valeur par dfaut du type des lments stocks dans la map si aucun lment ne correspond la clef fournie en paramtre. Contrairement loprateur [] des classes vector et deque, cet oprateur ne renvoie donc jamais lexception out_of_range.

Les recherches dans les conteneurs associatifs sappuient sur le fait que les objets disposent dune relation dordre induite par le foncteur less appliqu sur le type des donnes quils manipulent. Ce comportement est gnralement celui qui est souhait, mais il existe des situations o ce foncteur ne convient pas. Par exemple, on peut dsirer que le classement des objets se fasse sur une de leur donne membre seulement, ou que la fonction de comparaison utilise pour classer les objets soit diffrente de celle induite par le foncteur less. La bibliothque standard fournit donc la possibilit de spcier un foncteur de comparaison pour chaque conteneur associatif, en tant que paramtre template complmentaire au type de donnes des objets contenus. Ce foncteur doit, sil est spci, tre prcis avant le type de lallocateur mmoire utiliser. Il pourra tre construit partir des facilits fournies par la bibliothque standard pour la cration et la manipulation des foncteurs. Exemple 17-12. Utilisation dun foncteur de comparaison personnalis
#include #include #include #include #include <iostream> <map> <string> <functional> <cstring>

using namespace std; // Fonction de comparaison de chanes de caractres // non sensible la casse des lettres : bool stringless_nocase(const string &s1, const string &s2) { return (strcasecmp(s1.c_str(), s2.c_str()) < 0); } int main(void) { // Dfinit le type des associations chanes -> entiers // dont la clef est indexe sans tenir compte // de la casse des lettres : typedef map<string, int, pointer_to_binary_function<const string &, const string &, bool> > String2Int; String2Int m(ptr_fun(stringless_nocase)); // Insre quelques lments dans la map : m.insert(String2Int::value_type("a. Un", 1)); m.insert(String2Int::value_type("B. Deux", 2)); m.insert(String2Int::value_type("c. Trois", 3)); // Affiche le contenu de la map : String2Int::iterator i = m.begin(); while (i != m.end())

449

Chapitre 17. Les conteneurs


{ cout << i->first << " : " << i->second << endl; ++i; } return 0; }

Dans cet exemple, le type du foncteur est spci en troisime paramtre de la classe template map. Ce type est une instance de la classe template pointer_to_binary_function pour les types string et bool. Comme on la vu dans la Section 13.5, cette classe permet dencapsuler toute fonction binaire dans un foncteur binaire. Il ne reste donc qu spcier linstance du foncteur que la classe template map doit utiliser, en la lui fournissant dans son constructeur. Lexemple prcdent utilise la fonction utilitaire ptr_fun de la bibliothque standard pour construire ce foncteur partir de la fonction stringless_nocase. En fait, il est possible de passer des foncteurs beaucoup plus volus la classe map, qui peuvent ventuellement tre paramtrs par dautres paramtres que la fonction de comparaison utiliser pour comparer deux clefs. Cependant, il est rare davoir crire de tels foncteurs et mme, en gnral, il est courant que la fonction binaire utilise soit toujours la mme. Dans ce cas, il est plus simple de dnir directement le foncteur et de laisser le constructeur de la classe map prendre sa valeur par dfaut. Ainsi, seul le paramtre template donnant le type du foncteur doit tre spci, et lutilisation des conteneurs associatif en est dautant facilite. Lexemple suivant montre comment la comparaison de chanes de caractres non sensible la casse peut tre implmente de manire simplie. Exemple 17-13. Dnition directe du foncteur de comparaison pour les recherches
#include #include #include #include #include <iostream> <string> <map> <functional> <cstring>

using namespace std; // Classe de comparaison de chanes de caractres : class StringLessNoCase : public binary_function<string, string, bool> { public: bool operator()(const string &s1, const string &s2) { return (strcasecmp(s1.c_str(), s2.c_str()) < 0); } }; int main(void) { // Dfinition du type des associations chanes -> entiers // en spcifiant directement le type de foncteur utiliser // pour les comparaisons de clefs : typedef map<string, int, StringLessNoCase> String2Int; // Instanciation dune association en utilisant // la valeur par dfaut du foncteur de comparaison : String2Int m; // Utilisation de la map : m.insert(String2Int::value_type("a. Un", 1)); m.insert(String2Int::value_type("B. Deux", 2));

450

Chapitre 17. Les conteneurs


m.insert(String2Int::value_type("c. Trois", 3)); String2Int::iterator i = m.begin(); while (i != m.end()) { cout << i->first << " : " << i->second << endl; ++i; } return 0; }

Note : Les deux exemples prcdents utilisent la fonction strcasecmp de la bibliothque C standard pour effectuer des comparaisons de chanes qui ne tiennent pas compte de la casse des caractres. Cette fonction sutilise comme la fonction strcmp, qui compare deux chanes et renvoie un entier dont le signe indique si la premire chane est plus petite ou plus grande que la deuxime. Ces fonctions renvoient 0 si les deux chanes sont strictement gales. Si vous dsirez en savoir plus sur les fonctions de manipulation de chanes de la bibliothque C, veuillez vous rfrer la bibliographie.

Pour nir, sachez que les conteneurs associatifs disposent dune mthode count qui renvoie le nombre dlments du conteneur dont la clef est gale la valeur passe en premier paramtre. Cette mthode retourne donc une valeur du type size_type du conteneur, valeur qui peut valoir 0 ou 1 pour les conteneurs clefs uniques et nimporte quelle valeur pour les conteneurs clefs multiples. La complexit de cette mthode est ln(N)+n, o N est le nombre dlments stocks dans le conteneur et n est le nombre dlments dont la clef est gale la valeur passe en paramtre. Le premier terme provient en effet de la recherche du premier lment disposant de cette proprit, et le deuxime des comparaisons qui suivent pour compter les lments dsigns par la clef.
Note : Les implmentations de la bibliothque standard utilisent gnralement la structure de donnes des arbres rouges et noirs pour implmenter les conteneurs associatifs. Cette structure algorithmique est une forme darbre binaire quilibr, dont la hauteur est au plus le logarithme binaire du nombre dlments contenus. Ceci explique les performances des conteneurs associatifs sur les oprations de recherche.

451

Chapitre 17. Les conteneurs

452

Chapitre 18. Les algorithmes


La plupart des oprations qui peuvent tre appliques aux structures de donnes ne sont pas spciques ces structures. Par exemple, il est possible de trier quasiment toutes les squences, que ce soient des listes, des vecteurs ou des deques. Les classes template des conteneurs de la bibliothque standard ne fournissent donc que des mthodes de base permettant de les manipuler, et rares sont les conteneurs qui dnissent des oprations dont le rle dpasse le simple cadre de lajout, de la suppression ou de la recherche dlments. Au lieu de cela, la bibliothque standard dnit tout un jeu de fonctions template extrieures aux conteneurs et dont le but est de raliser ces oprations de haut niveau. Ces fonctions sont appeles algorithmes en raison du fait quelles effectuent les traitements des algorithmes les plus connus et les plus utiliss en informatique. Les algorithmes ne drogent pas la rgle de gnricit que la bibliothque standard C++ simpose. Autrement dit, ils sont capables de travailler en faisant le moins dhypothses possibles sur la structure de donnes contenant les objets sur lesquels ils sappliquent. Ainsi, tous les algorithmes sont des fonctions template et ils travaillent sur les objets exclusivement par lintermdiaire ditrateurs et de foncteurs. Cela signie que les algorithmes peuvent, en pratique, tre utiliss sur nimporte quelle structure de donnes ou nimporte quel conteneur, pourvu que les prconditions imposes sur les itrateurs et le type des donnes manipules soient respectes. Comme pour les mthodes permettant de manipuler les conteneurs, les algorithmes sont dcrits par leur smantique et par leur complexit. Cela signie que les implmentations de la bibliothque standard sont libres quant la manire de raliser ces algorithmes, mais quelles doivent imprativement respecter les contraintes de performances imposes par la norme de la bibliothque standard. En pratique cependant, tout comme pour les structures de donnes des conteneurs, ces contraintes imposent souvent lalgorithme sous-jacent pour limplmentation de ces fonctionnalits et lalgorithme utilis est le meilleur algorithme connu ce jour. Autrement dit, les algorithmes de la bibliothque standard sont forcment les plus efcaces qui soient. La plupart des algorithmes de la bibliothque standard sont dclars dans len-tte algorithm. Certains algorithmes ont t toutefois dnis initialement pour les valarray et napparaissent donc pas dans cet en-tte. Au lieu de cela, ils sont dclars dans len-tte numeric. Ces algorithmes sont peu nombreux et cette particularit sera signale dans leur description. Le nombre des algorithmes dnis par la bibliothque standard est impressionnant et couvre sans doute tous les besoins courants des programmeurs. Il est donc difcile, en raison de cette grande diversit, de prsenter les algorithmes de manire structure. Cependant, les sections suivantes regroupent ces algorithmes en fonction de la nature des oprations quils sont supposs effectuer. Ces oprations comprennent les oprations de manipulation gnrales des donnes, les recherches dlments selon des critres particuliers, les oprations de tri et de comparaison, et enn les oprations de manipulation ensemblistes.

18.1. Oprations gnrales de manipulation des donnes


Les algorithmes gnraux de manipulation des donnes permettent de raliser toutes les oprations classiques de type cration, copie, suppression et remplacement, mais galement de modier lordre des squences dlments ainsi que dappliquer un traitement sur chacun des lments des conteneurs. Certains algorithmes peuvent modier soit les donnes contenues par les conteneurs sur lesquels ils travaillent, soit les conteneurs eux-mmes. En gnral ces algorithmes travaillent sur place, cest dire quils modient les donnes du conteneur directement. Cependant, pour certains algorithmes, il

453

Chapitre 18. Les algorithmes est possible de stocker les donnes modies dans un autre conteneur. Le conteneur source nest donc pas modi et les donnes, modies ou non, sont copies dans le conteneur destination. En gnral, les versions des algorithmes capables de faire cette copie la vole ne sont fournies que pour les algorithmes peu complexes car le cot de la copie peut dans ce cas tre aussi grand ou plus grand que le cot du traitement des algorithmes eux-mmes. Il est donc justi pour ces algorithmes de donner la possibilit de raliser la copie pendant leur traitement an de permettre aux programmes doptimiser les programmes selon les cas dutilisation. Le nom des algorithmes qui ralisent une copie la vole est le mme nom que leur algorithme de base, mais sufx par le mot _copy .

18.1.1. Oprations dinitialisation et de remplissage


Il existe deux mthodes permettant dinitialiser un conteneur ou de gnrer une srie dobjets pour initialiser un conteneur. La premire mthode ne permet que de gnrer plusieurs copies dun mme objet, que lon spcie par valeur, alors que la deuxime permet dappeler une fonction de gnration pour chaque objet crer. Les algorithmes de gnration et dinitialisation sont dclars de la manire suivante dans len-tte algorithm :
template <class ForwarIterator, class T> void fill(ForwardIterator premier, ForwardIterator dernier, const T &valeur); template <class OutputIterator, class T> void fill_n(OutputIterator premier, Size nombre, const T &valeur); tempalte <class ForwardIterator, class T, class Generator> void generate(ForwardIterator premier, ForwardIterator dernier, Generator g); template <class OutputIterator, class T, class Generator> void generate_n(OutputIterator premier, Size nombre, Generator g);

Chaque algorithme est disponible sous deux formes diffrentes. La premire utilise un couple ditrateurs rfrenant le premier et le dernier lment initialiser, et la deuxime nutilise quun itrateur sur le premier lment et le nombre dlments gnrer. Le dernier paramtre permet de prciser la valeur affecter aux lments du conteneur cible pour les algorithmes fill et fill_n, ou un foncteur permettant dobtenir une nouvelle valeur chaque invocation. Ce foncteur ne prend aucun paramtre et renvoie la nouvelle valeur de lobjet. Exemple 18-1. Algorithme de gnration dobjets et de remplissage dun conteneur
#include #include #include #include <iostream> <list> <iterator> <algorithm>

using namespace std; int compte() { static int i = 0; return i++; }

454

Chapitre 18. Les algorithmes


int main(void) { // Cre une liste de 20 entiers conscutifs : typedef list<int> li; li l; generate_n(back_inserter(l), 20, compte); // Affiche la liste : li::iterator i = l.begin(); while (i != l.end()) { cout << *i << endl; ++i; } return 0; }

Ces algorithmes effectuent exactement autant daffectations quil y a dlments crer ou initialiser. Leur complexit est donc linaire en fonction du nombre de ces lments.

18.1.2. Oprations de copie


La bibliothque standard dnit deux algorithmes fondamentaux pour raliser la copie des donnes des conteneurs. Ces algorithmes sont dclars comme suit dans len-tte algorithm :
template <class InputIterator, class OutputIterator> OutputIterator copy(InputIterator premier, InputIterator dernier, OutputIterator destination); template <class BidirectionalIterator1, class BidirectionalIterator2> BidirectionalIterator2 copy_backward( BidirectionalIterator1 premier, BidirectionalIterator1 dernier, BidirectionalIterator2 fin_destination);

Alors que copy ralise la copie des objets rfrencs par les itrateurs premier et dernier du premier vers le dernier, lalgorithme backward_copy travaille dans le sens contraire. On utilisera donc typiquement backward_copy chaque fois que la zone mmoire destination empite sur la n des donnes sources. Notez que dans ce cas, litrateur spciant la destination rfrence le dernier emplacement utilis aprs la copie et non le premier lment. Autrement dit, litrateur fin_destination est utilis de manire descendante, alors que litrateur destination fourni lalgorithme copy est utilis de manire ascendante. Exemple 18-2. Algorithme de copie inverse
#include <iostream> #include <algorithm> #include <cstring> using namespace std; int main(void) { char sBuffer[] = "abcdefg123"; // Dtermine litrateur de fin :

455

Chapitre 18. Les algorithmes


char *pFin = sBuffer + strlen(sBuffer); // crase la chane par elle-mme partir du d : copy_backward(sBuffer, pFin-3, pFin); // Affiche le rsultat : cout << sBuffer << endl; return 0; }

Note : La fonction strlen utilise dans cet exemple est une des fonctions de la bibliothque C standard, qui est dclare dans len-tte cstring. Elle permet de calculer la longueur dune chane de caractres C (sans compter le caratre nul terminal).

Ces algorithmes effectuent exactement autant daffectation quil y a dlments copier. Leur complexit est donc linaire en fonction du nombre de ces lments.
Note : Il existe galement des algorithmes capables de raliser une copie de leur rsultat la vole. Le nom de ces algorithmes est gnralement le nom de leur algorithme de base sufx par la chane _copy. Ces algorithmes seront dcrits avec leurs algorithmes de base.

18.1.3. Oprations dchange dlments


Il est possible dchanger le contenu de deux squences dlments grce un algorithme ddi cette tche, lalgorithme swap_ranges. Cet algorithme est dclar comme suit dans len-tte algorithm :
template <class ForwardIterator, class ForwardIterator2> ForwardIterator2 swap_ranges(ForwardIterator premier, ForwardIterator dernier, ForwardIterator2 destination);

Cet algorithme prend en paramtre les deux itrateurs dnissant la premire squence et un itrateur destination permettant dindiquer le premier lment de la deuxime squence avec les lments de laquelle lchange doit tre fait. La valeur retourne est litrateur de n de cette squence, une fois lopration termine. Exemple 18-3. Algorithme dchange
#include <iostream> #include <list> #include <algorithm> using namespace std; int main(void) { // Dfinit une liste dentiers : typedef list<int> li; li l; l.push_back(2); l.push_back(5); l.push_back(3);

456

Chapitre 18. Les algorithmes


l.push_back(7); // Dfinit un tableau de quatre lments : int t[4] = {10, 11, 12, 13}; // change le contenu du tableau et de la liste : swap_ranges(t, t+4, l.begin()); // Affiche le tableau : int i; for (i=0; i<4; ++i) cout << t[i] << " "; cout << endl; // Affiche la liste : li::iterator it = l.begin(); while (it != l.end()) { cout << *it << " "; ++it; } cout << endl; return 0; }

Cet algorithme nchange pas plus dlments que ncessaire, autrement dit, il a une complexit linaire en fonction de la taille de la squence initiale.

18.1.4. Oprations de suppression dlments


Les conteneurs de la bibliothque standard disposent tous de mthodes puissantes permettant deffectuer des suppressions dlments selon diffrents critres. Toutefois, la bibliothque standard dnit galement des algorithmes de suppression dlments dans des squences. En fait, ces algorithmes neffectuent pas proprement parler de suppression, mais une rcriture des squences au cours de laquelle les lments supprimer sont tout simplement ignors. Ces algorithmes renvoient donc litrateur du dernier lment copi, au-del duquel la squence initiale est inchange. La bibliothque standard fournit galement des versions de ces algorithmes capables de raliser une copie la vole des lments de la squence rsultat. Ces algorithmes peuvent donc typiquement tre utiliss pour effectuer un ltre sur des lments dont le but serait de supprimer les lments indsirables. Les fonctions de suppression des lments sont dclares comme suit dans len-tte algorithm :
template <class ForwardIterator, class T> ForwardIterator remove(ForwardIterator premier, ForwardIterator second, const T &valeur); template <class InputIterator, class OutputIterator, class T> OutputIterator remove_copy(InputIterator premier, InputIterator second, OutputIterator destination, const T &valeur); template <class ForwardIterator, class Predicate> ForwardIterator remove_if(ForwardIterator premier, ForwardIterator second, Predicate p); template <class InputIterator, class OutputIterator, class Predicate> OutputIterator remove_copy_if(InputIterator premier, InputIterator second, OutputIterator destination, Predicate p);

457

Chapitre 18. Les algorithmes

Toutes ces fonctions prennent en premier et en deuxime paramtre les itrateurs dnissant lintervalle dlments sur lequel elles doivent travailler. Pour les fonctions de suppression dun lment particulier, la valeur de cet lment doit galement tre fournie. Si vous prfrez utiliser les versions bases sur un prdicat, il vous faut spcier un foncteur unaire prenant en paramtre un lment et renvoyant un boolen indiquant si cet lment doit tre supprim ou non. Enn, les versions de ces algorithmes permettant de raliser une copie la vole ncessitent bien entendu un itrateur supplmentaire indiquant lemplacement destination o les lments non supprims devront tre stocks. Comme vous pouvez le constater daprs leurs dclarations, ces algorithmes renvoient tous un itrateur rfrenant llment suivant le dernier lment de la squence rsultat. Cet itrateur permet de dterminer la n de la squence dlments rsultat, que cette squence ait t modie sur place ou quune copie ait t ralise. Si lalgorithme utilis neffectue pas de copie, les lments suivant cet itrateur sont les lments de la squence initiale. Cest ce niveau que la diffrence entre les algorithmes de suppression et les mthodes erase des conteneurs (et les mthodes remove des listes) apparat : les algorithmes crasent les lments supprims par les lments qui les suivent, mais ne suppriment pas les lments source du conteneur situs au-del de litrateur renvoy, alors que les mthodes erase des conteneurs suppriment effectivement des conteneurs les lments liminer. Exemple 18-4. Algorithme de suppression
#include <iostream> #include <algorithm> using namespace std; int main(void) { // Construit un tableau de 10 entiers : int t[10] = { 1, 2, 2, 3, 5, 2, 4, 3, 6, 7 }; // Supprime les entiers valant 2 : int *fin = remove(t, t+10, 2); // Affiche le tableau rsultat : int *p = t; while (p != fin) { cout << *p << endl; ++p; } return 0; }

De manire similaire, la bibliothque standard dnit galement des algorithmes permettant de supprimer les doublons dans des squences dlments. Ces algorithmes sont dclars comme suit dans len-tte algorithm :
template<class ForwardIterator> ForwardIterator unique(ForwardIterator premier, ForwardIterator dernier); template<class ForwardIterator, class OutputIterator> OutputIterator unique_copy(ForwardIterator premier, ForwardIterator dernier); template <class ForwardIterator, class BinaryPredicate> ForwardIterator unique(ForwardIterator premier, ForwardIterator dernier,

458

Chapitre 18. Les algorithmes


BinaryPredicate p); template <class ForwardIterator, class OutputIterator, class BinaryPredicate> OutputIterator unique_copy(ForwardIterator premier, ForwardIterator dernier, BinaryPredicate p);

Ces algorithmes fonctionnent de la mme manire que les algorithmes remove ceci prs quils nliminent que les doublons dans la squence source. Cela signie quil nest pas ncessaire de prciser la valeur des lments liminer dune part et, dautre part, que les prdicats utiliss sont des prdicats binaires puisquils doivent tre appliqus aux couples dlments successifs.
Note : Il nexiste pas dalgorithmes unique_if et unique_copy_if. La bibliothque standard utilise les possibilits de surcharge du C++ pour distinguer les versions avec et sans prdicat des algorithmes de suppression des doublons.

Exemple 18-5. Algorithme de suppression des doublons


#include <iostream> #include <algorithm> using namespace std; int main(void) { // Construit un tableau de 10 entiers : int t[10] = { 1, 2, 2, 3, 5, 2, 4, 3, 6, 7 }; // Supprime les doublons : int *fin = unique(t, t+10); // Affiche le tableau rsultat : int *p = t; while (p != fin) { cout << *p << endl; ++p; } return 0; }

Le test de suppression est appliqu par ces algorithmes autant de fois quil y a dlments dans la squence initiale, cest--dire que leur complexit est linaire en fonction du nombre dlments de cette squence.

18.1.5. Oprations de remplacement


Les algorithmes de remplacement permettent de remplacer tous les lments dun conteneur vriant une proprit particulire par un autre lment dont la valeur doit tre fournie en paramtre. Les lments devant tre remplacs peuvent tre identis soit par leur valeur, soit par un prdicat unaire prenant en paramtre un lment et renvoyant un boolen indiquant si cet lment doit tre remplac ou non. Les algorithmes de remplacement sont dclars comme suit dans len-tte algorithm :
template <class ForwardIterator, class T>

459

Chapitre 18. Les algorithmes


void replace(ForwardIterator premier, ForwardIterator dernier, const T &ancienne_valeur, const T &nouvelle_valeur); template <class InputIterator, class OutputIterator, class T> void replace_copy(InputIterator premier, InputIterator dernier, OutputIterator destination, const T &ancienne_valeur, const T &nouvelle_valeur); template <class ForwardIterator, class Predicate, class T> void replace_if(ForwardIterator premier, ForwardIterator dernier, Predicate p, const T &nouvelle_valeur); template <class InputIterator, class OutputIterator, class Predicate, class T> void replace_copy_if(InputIterator premier, InputIterator dernier, OutputIterator destination, Predicate p, const T &nouvelle_valeur);

Les algorithmes de remplacement peuvent travailler sur place ou effectuer une copie la vole des lments sur lesquels ils travaillent. Les versions capables de raliser ces copies sont identies par le sufxe _copy de leur nom. Ces algorithmes prennent un paramtre supplmentaire permettant de spcier lemplacement destination o les lments copis devront tre stocks. Ce paramtre est un itrateur, tout comme les paramtres qui indiquent lintervalle dlments dans lequel la recherche et le remplacement doivent tre raliss. Exemple 18-6. Algorithme de recherche et de remplacement
#include <iostream> #include <algorithm> using namespace std; int main(void) { int t[10] = {1, 2, 5, 3, 2, 7, 6, 4, 2, 1}; // Remplace tous les 2 par des 9 : replace(t, t+10, 2, 9); // Affiche le rsultat : int i; for (i=0; i<10; ++i) cout << t[i] << endl; return 0; }

Le test de remplacement est appliqu par ces algorithmes autant de fois quil y a des lments dans la squence initiale, cest--dire que leur complexit est linaire en fonction du nombre dlments de cette squence.

18.1.6. Rorganisation de squences


Comme il la t expliqu dans la Section 17.2, lordre des lments dune squence est important. La plupart des squences conservent les lments dans lordre dans lequel ils ont t insrs, dautre se rorganisent automatiquement lorsque lon travaille dessus pour assurer un ordre bien dni.

460

Chapitre 18. Les algorithmes La bibliothque standard fournit plusieurs algorithmes permettant de rorganiser la squence des lments dans un conteneur qui ne prend pas en charge lui-mme lordre de ses lments. Ces algorithmes permettent de raliser des rotations et des permutations des lments, des symtries et des inversions, ainsi que de les mlanger de manire alatoire.
Note : Il existe galement des algorithmes de tri extrmement efcaces, mais ces algorithmes seront dcrits plus loin dans une section qui leur est consacre.

18.1.6.1. Oprations de rotation et de permutation


Les algorithmes de rotation permettent de faire tourner les diffrents lments dune squence dans un sens ou dans lautre. Par exemple, dans une rotation vers la gauche dune place, le deuxime lment peut prendre la place du premier, le troisime celle du deuxime, etc., le premier lment revenant la place du dernier. Ces algorithmes sont dclars de la manire suivante dans len-tte algorithm :
template <class ForwardIterator> void rotate(ForwardIterator premier, ForwardIterator pivot, ForwardIterator dernier); template <class ForwardIterator, class OutputIterator> void rotate_copy(ForwardIterator premier, ForwardIterator pivot, ForwardIterator dernier, OutputIterator destination);

Les algorithmes de rotation prennent en paramtre un itrateur indiquant le premier lment de la squence devant subir la rotation, un itrateur rfrenant llment qui se trouvera en premire position aprs la rotation, et un itrateur rfrenant llment suivant le dernier lment de la squence. Ainsi, pour effectuer une rotation dune position vers la gauche, il suft dutiliser pour litrateur pivot la valeur de litrateur suivant litrateur premier et, pour effectuer une rotation dune position vers la droite, il faut prendre pour litrateur pivot la valeur prcdant celle de litrateur dernier. Exemple 18-7. Algorithme de rotation
#include <iostream> #include <algorithm> using namespace std; int main(void) { int t[10] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}; // Effectue une rotation pour amener le quatrime // lment en premire position : rotate(t, t+3, t+10); // Affiche le rsultat : int i; for (i=0; i<10; ++i) cout << t[i] << endl; return 0; }

La bibliothque fournit galement des algorithmes permettant dobtenir lensemble des permutations dune squence dlments. Rappelons quune permutation est une des combinaisons possibles des

461

Chapitre 18. Les algorithmes valeurs des diffrents lments dun ensemble, en considrant les lments dgale valeur comme identiques. Par exemple, si un ensemble contient deux lments de mme valeur, il ny a quune seule permutation possible : les deux valeurs. Si en revanche ces deux lments ont deux valeurs distinctes, on peut raliser deux permutations selon la valeur que lon place en premier. Les algorithmes de permutation de la bibliothque ne permettent pas dobtenir les permutations directement. Au lieu de cela, ils permettent de passer dune permutation la permutation suivante ou la prcdente. Cela suppose quune relation dordre soit dnie sur lensemble des permutations de la squence. La bibliothque standard utilise lordre lexicographique pour classer ces permutations. Autrement dit, les premires permutations sont celles pour lesquelles les premiers lments ont les valeurs les plus faibles. Les algorithmes de calcul des permutations suivante et prcdente sont dclars comme suit dans len-tte algorithm :
template <class BidirectionalIterator> bool next_permutation(BidirectionalIterator premier, BidirectionalIterator dernier); template <class BidirectionalIterator> bool prev_permutation(BidirectionalIterator premier, BidirectionalIterator dernier);

Ces algorithmes prennent tous les deux deux itrateurs indiquant les lments devant subir la permutation et renvoient un boolen indiquant si la permutation suivante ou prcdente existe ou non. Si ces permutations nexistent pas, les algorithmes next_permutation et prev_permutation bouclent et calculent respectivement la plus petite et la plus grande permutation de lensemble des permutations. Exemple 18-8. Algorithme de permutation
#include <iostream> #include <algorithm> using namespace std; int main(void) { int t[3] = {1, 1, 2}; // Affiche lensemble des permutations de (1, 1, 2) : do { int i; for (i=0; i<3; ++i) cout << t[i] << " "; cout << endl; } while (next_permutation(t, t+3)); return 0; }

Les algorithmes de rotation effectuent autant dchange quil y a dlments dans la squence initiale, et les algorithmes de calcul de permutation en font exactement la moiti. La complexit de ces algorithmes est donc linaire en fonction du nombre dlments de lintervalle qui doit subir lopration.

462

Chapitre 18. Les algorithmes

18.1.6.2. Oprations dinversion


Il est possible dinverser lordre des lments dune squence laide des algorithmes reverse et reverse_copy. Ces algorithmes sont dclars de la manire suivante dans len-tte algorithm :
template <class BidirectionalIterator> void reverse(BidirectionalIterator premier, BidirectionalIterator dernier); template <class BidirectionalIterator, class OutputIterator> OutputIterator reverse_copy(BidirectionalIterator premier, BidirectionalIterator dernier, OutputIterator destination);

Ces algorithmes prennent en paramtre les itrateurs permettant de spcier lintervalle des lments qui doit tre invers. La version de cet algorithme qui permet de raliser une copie prend un paramtre supplmentaire qui doit recevoir litrateur rfrenant lemplacement destination dans lequel le rsultat de linversion doit tre stock. Cet itrateur retourne la valeur de litrateur destination pass le dernier lment crit. Exemple 18-9. Algorithme dinversion
#include <iostream> #include <algorithm> using namespace std; int main(void) { int t[10] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}; // Inverse le tableau : reverse(t, t+10); // Affiche le rsultat : int i; for (i=0; i<10; ++i) cout << t[i] << endl; return 0; }

Les algorithmes dinversion effectuent autant dchange dlments quil y en a dans la squence initiale. Autrement dit, leur complexit est linaire en fonction de la taille de cette squence.

18.1.6.3. Oprations de mlange


Il est possible de redistribuer alatoirement les lments dune squence laide de lalgorithme random_shuffle. Cet algorithme est fourni sous la forme de deux surcharges dclares comme suit dans len-tte algorithm :
template <class RandomAccessIterator> void random_shuffle(RandomAccessIterator premier, RandomAccessIterator dernier); template <class RandomAccessIterator, class RandomNumberGenerator> void random_shuffle(RandomAccessIterator premier, RandomAccessIterator dernier, RandomNumberGenerator g);

463

Chapitre 18. Les algorithmes Ces algorithmes prennent en paramtre les itrateurs de dbut et de n de la squence dont les lments doivent tre mlangs. La deuxime version de cet algorithme peut prendre en dernier paramtre un foncteur qui sera utilis pour calculer les positions des lments pendant le mlange. Ainsi, cette surcharge permet de spcier soi-mme la fonction de distribution utiliser pour effectuer cette nouvelle rpartition. Ce foncteur doit prendre en paramtre une valeur du type difference_type des itrateurs utiliss pour rfrencer les lments de la squence, et renvoyer une valeur comprise entre 0 et la valeur reue en paramtre. Il doit donc se comporter comme la fonction rand de la bibliothque standard C (dclare dans le chier den-tte cstdlib). Exemple 18-10. Algorithme de mlange
#include <iostream> #include <algorithm> using namespace std; int main(void) { int t[10] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}; // Mlange le tableau t : random_shuffle(t, t+10); // Affiche le rsultat : int i; for (i=0; i<10; ++i) cout << t[i] << endl; return 0; }

Ces algorithmes effectuent exactement le nombre dlments de la squence mlanger moins un changes de valeurs. Leur complexit est donc linaire en fonction du nombre dlments de ces squences.

18.1.7. Algorithmes ditration et de transformation


Les algorithmes de transformation et ditration de la bibliothque standard font partie des plus utiles puisquils permettent deffectuer un traitement sur lensemble des lments dun conteneur. Ces traitements peuvent modier ou non ces lments ou tout simplement calculer une valeur partir de ces lments. Les deux principaux algorithmes fournis par la bibliothque standard sont sans doute les algorithmes for_each et transform, qui permettent deffectuer une action sur chaque lment dun conteneur. Ces algorithmes sont dclars comme suit dans len-tte algorithm :
template <class InputIterator, class Function> Function for_each(InputIterator premier, InputIterator dernier, Function f); template <class InputIterator, class OutputIterator, class UnaryOperation> OutputIterator transform(InputIterator premier, InputIterator dernier, OutputIterator destination, UnaryOperation op); template <class InputIterator1, class InputIterator2, class OutputIterator, class BinaryOperation> OutputIterator transform(InputIterator1 premier1, InputIterator1 dernier1,

464

Chapitre 18. Les algorithmes


InputIterator2 premier2, OutputIterator destination, BinaryOperation op);

Lalgorithme for_each permet ditrer les lments dun conteneur et dappeler une fonction pour chacun de ces lments. Il prend donc en paramtre deux itrateurs permettant de spcier les lments itrer et un foncteur qui sera appel chaque itration. Pendant litration, ce foncteur reoit en paramtre la valeur de llment de litration courante de la part de for_each. Cette valeur ne doit en aucun cas tre modie et la valeur retourne par ce foncteur est ignore. La valeur retourne par lalgorithme for_each est le foncteur qui lui a t communiqu en paramtre. Contrairement for_each, qui ne permet pas de modier les lments quil itre, lalgorithme transform autorise la modication des lments du conteneur sur lequel il travaille. Il est fourni sous deux versions, la premire permettant dappliquer un foncteur unaire sur chaque lment dun conteneur et la deuxime un foncteur binaire sur deux lments de deux conteneurs sur lesquels lalgorithme itre simultanment. Les deux versions prennent en premiers paramtres les itrateurs permettant de spcier les lments itrer du premier conteneur. Ils prennent galement en paramtre un foncteur permettant de calculer une nouvelle valeur partir des lments itrs et un itrateur dans lequel les rsultats de ce foncteur seront stocks. La version permettant de travailler avec un foncteur binaire prend un paramtre complmentaire, qui doit recevoir la valeur de litrateur de dbut du conteneur devant fournir les lments utiliss en tant que second oprande du foncteur. La valeur retourne par les algorithmes transform est la valeur de n de litrateur destination. Exemple 18-11. Algorithmes ditration
#include <iostream> #include <functional> #include <algorithm> using namespace std; void aff_entier(int i) { cout << i << endl; } int main(void) { int t[10] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}; // Inverse tous les lments du tableau : transform(t, t+10, t, negate<int>()); // Affiche le rsultat : for_each(t, t+10, ptr_fun(&aff_entier)); return 0; }

Comme vous pouvez le constater daprs cet exemple, il est tout fait possible dutiliser la mme valeur pour litrateur premier et litrateur destination. Cela signie que les lments itrs peuvent tre remplacs par les nouvelles valeurs calcules par le foncteur fourni lalgorithme transform. Un cas particulier des algorithmes ditration est celui des algorithmes count et count_if puisque le traitement effectu est alors simplement le dcompte des lments vriant une certaine condition. Ces deux algorithmes permettent en effet de compter le nombre dlments dun conteneur dont la

465

Chapitre 18. Les algorithmes valeur est gale une valeur donne ou vriant un critre spci par lintermdiaire dun prdicat unaire. Ces deux algorithmes sont dclars de la manire suivante dans len-tte algorithm :
template <class InputIterator, class T> iterator_traits<InputIterator>::difference_type count(InputIterator premier, InputIterator dernier, const T &valeur); template <class InputIterator, class Predicate> iterator_traits<InputIterator>::difference_type count_if(InputIterator premier, InputIterator dernier, Predicate p);

Comme vous pouvez le constater, ces algorithmes prennent en paramtre deux itrateurs spciant lintervalle des lments sur lesquels le test doit tre effectu, et la valeur avec laquelle ces lments doivent tre compars ou un prdicat unaire. Dans ce cas, le rsultat de ce prdicat indique si llment quil reoit en paramtre doit tre compt ou non. Exemple 18-12. Algorithme de dcompte dlments
#include <iostream> #include <functional> #include <algorithm> using namespace std; bool parity_even(int i) { return (i & 1) == 0; } int main(void) { int t[10] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}; // Compte le nombre dlments pairs : cout << count_if(t, t+10, ptr_fun(&parity_even)) << endl; return 0; }

Tous les algorithmes ditration ne font quun seul passage sur chaque lment itr. Autrement dit, la complexit de ces algorithmes est linaire en fonction du nombre dlments compris entre les deux itrateurs spciant lintervalle dlments sur lequel ils sont appliqus. Enn, la bibliothque standard fournit des algorithmes de calcul plus volus, capables de travailler sur les lments des conteneurs. Ces algorithmes sont gnralement utiliss en calcul numrique et ont t conus spcialement pour les tableaux de valeurs. Cependant, ils restent tout fait utilisables sur dautres conteneurs que les valarray, la seule distinction quils ont avec les autres algorithmes de la bibliothque standard est quils sont dclars dans len-tte numeric au lieu de len-tte algorithm. Ces algorithmes sont les suivants :
template <class InputIterator, class T> T accumulate(InputIterator premier, InputIterator dernier, T init); template <class InputIterator, class T, class BinaryOperation> T accumulate(InputIterator premier, InputIterator dernier, T init, BinaryOperation op);

466

Chapitre 18. Les algorithmes


template <class InputIterator1, class InputIterator2, class T> T inner_product(InputIterator1 premier1, InputIterator1 dernier1, InputIterator2 premier2, T init); template <class InputIterator1, class InputIterator2, class T, class BinaryOperation1, class BinaryOperation2> T inner_product(InputIterator1 premier1, InputIterator1 dernier1, InputIterator2 premier2, T init, BinaryOperation1 op1, BinaryOperation op2); template <class InputIterator, class OutputIterator> OutputIterator partial_sum(InputIterator premier, InputIterator dernier, OutputIterator destination); template <class InputIterator, class OutputIterator, class BinaryOperation> OutputIterator partial_sum(InputIterator premier, InputIterator dernier, OutputIterator destination, BinaryOperation op); template <class InputIterator, class OutputIterator> OutputIterator adjacent_difference(InputIterator premier, InputIterator dernier, OutputIterator destination); template <class InputIterator, class OutputIterator, class BinaryOperation> OutputIterator adjacent_difference(InputIterator premier, InputIterator dernier, OutputIterator destination, BinaryOperation op);

Ces algorithmes correspondent des oprations courantes, que lon fait gnralement sur les tableaux de nombres de type valarray. Lalgorithme accumulate permet gnralement de raliser la somme des valeurs qui sont stockes dans un conteneur. Lalgorithme inner_product est utilis quant lui pour raliser le produit scalaire de deux squences de nombres, opration mathmatique gnralement effectue dans le calcul vectoriel. Enn, les algorithmes partial_sum et adjacent_difference ralisent respectivement le calcul des sommes partielles et des diffrences deux deux des lments dun conteneur. Pout tous ces algorithmes, il est possible dutiliser dautres oprations que les oprations gnralement utilises. Par exemple, accumulate peut utiliser une autre opration que laddition pour accumuler les valeurs des lments. Pour cela, la bibliothque standard fournit des surcharges de ces algorithmes capables de travailler avec des foncteurs binaires. Ces foncteurs doivent accepter deux paramtres du type des lments du conteneur sur lequel les algorithmes sont appliqus et renvoyer une valeur du mme type, calcule partir de ces paramtres. Lalgorithme accumulate prend donc en premiers paramtres les itrateurs dnissant lintervalle des valeurs qui doivent tre accumules. Il initialise la valeur dune variable accumulateur avec la valeur fournie en troisime paramtre, et parcours lensemble des lments. Pour chaque lment trait, accumulate remplace la valeur courante de laccumulateur par le rsultat de lopration daccumulation applique laccumulateur lui-mme et la valeur de llment courant. Par dfaut, lopration daccumulation utilise est laddition, mais il est possible de changer ce comportement en fournissant un foncteur binaire en dernier paramtre. Lorsque lensemble des lments a t parcouru, la valeur de laccumulateur est retourne. Exemple 18-13. Algorithme daccumulation
#include <list> #include <numeric>

467

Chapitre 18. Les algorithmes


#include <functional> #include <iostream> using namespace std; int main(void) { // Construit une liste dentiers : typedef list<int> li; li l; l.push_back(5); l.push_back(2); l.push_back(9); l.push_back(1); // Calcule le produit de ces entiers : int res = accumulate(l.begin(), l.end(), 1, multiplies<int>()); cout << res << endl; return 0; }

Lalgorithme inner_product travaille sur deux conteneurs simultanment et ralise leur produit scalaire. Le produit scalaire est lopration qui consiste multiplier les lments de deux sries de nombres deux deux, et de faire la somme des rsultats. Lalgorithme inner_product prend donc en paramtre les itrateurs de dbut et de n spciant la premire srie de nombres, litrateur de dbut de la deuxime srie de nombres, et la valeur initiale de laccumulateur utilis pour raliser la somme des produits des lments de ces deux conteneurs. Bien entendu, tout comme pour lalgorithme accumulate, il est possible de remplacer les oprations de multiplication et daddition de lalgorithme standard par deux foncteurs en fournissant ceux-ci en derniers paramtres. Exemple 18-14. Algorithme de produit scalaire
#include <iostream> #include <numeric> using namespace std; int main(void) { // Dfinit deux vecteurs orthogonaux : int t1[3] = {0, 1, 0}; int t2[3] = {0, 0, 1}; // Calcule leur produit scalaire : int res = inner_product(t1, t1+3, t2, 0); // Le produit scalaire de deux vecteurs orthogonaux // est toujours nul : cout << res << endl; return 0; }

Lalgorithme partial_sum permet de calculer la srie des sommes partielles de la suite de valeurs spcie par les deux itrateurs fournis en premiers paramtres. Cette srie de sommes contiendra dabord la valeur du premier lment, puis la valeur de la somme des deux premiers lments, puis la valeur de la somme des trois premiers lments, etc., et enn la somme de lensemble des lments de la suite de valeurs sur laquelle lalgorithme travaille. Toutes ces valeurs sont stockes successivement aux emplacements indiqus par litrateur destination. Comme pour les autres algorithmes, il est

468

Chapitre 18. Les algorithmes possible de spcier une autre opration que laddition laide dun foncteur binaire que lon passera en dernier paramtre. Enn, lalgorithme adjacent_difference est lalgorithme inverse de lalgorithme parial_sum. En effet, il permet de calculer la srie des diffrences des valeurs des lments successifs dune suite de valeurs, pris deux deux. Cet algorithme prend en paramtre les itrateurs dcrivant la suite de valeurs sur laquelle il doit travailler, litrateur de lemplacement destination o les rsultats devront tre stocks et ventuellement le foncteur appliquer aux couples dlments successifs traits par lalgorithme. La premire diffrence est calcule en supposant que llment prcdent le premier lment a pour valeur la valeur nulle. Ainsi, le premier lment de lemplacement destination est toujours gal au premier lment de la suite de valeurs sur laquelle lalgorithme travaille. Exemple 18-15. Algorithmes de sommes partielles et de diffrences adjacentes
#include <iostream> #include <numeric> using namespace std; int main(void) { int t[4] = {1, 1, 1, 1}; // Calcule les sommes partielles des lments // du tableau : partial_sum(t, t+4, t); // Affiche le rsultat : int i; for (i=0; i<4; ++i) cout << t[i] << " "; cout << endl; // Calcule les diffrences adjacentes : adjacent_difference(t, t+4, t); // Cest le tableau initial : for (i=0; i<4; ++i) cout << t[i] << " "; cout << endl; return 0; }

Tous ces algorithmes travaillent en une seule passe sur les lments des conteneurs sur lesquels ils sappliquent. Leur complexit est donc linaire en fonction du nombre dlments spcis par les itrateurs fournis en premier paramtre.

18.2. Oprations de recherche


En gnral, la plupart des oprations de recherche de motifs que les programmes sont susceptibles deffectuer se font sur des chanes de caractres ou sur les conteneurs associatifs. Cependant, il peut tre ncessaire de rechercher un lment dans un conteneur selon un critre particulier ou de rechercher une squence dlments constituant un motif retrouver dans la suite des lments dun conteneur. Enn, il est relativement courant davoir rechercher les groupes dlments conscutifs disposant de la mme valeur dans un conteneur. Toutes ces oprations peuvent tre ralises laide des algorithmes de recherche que la bibliothque standard met la disposition des programmeurs.

469

Chapitre 18. Les algorithmes

18.2.1. Opration de recherche dlments


Le premier groupe doprations de recherche contient tous les algorithmes permettant de retrouver un lment dans un conteneur, en lidentiant soit par sa valeur, soit par une proprit particulire. Toutefois, cet lment peut ne pas tre le seul lment vriant ce critre. La bibliothque standard dnit donc plusieurs algorithmes permettant de rechercher ces lments de diffrentes manires, facilitant ainsi les oprations de recherche dans diffrents contextes. Les algorithmes de recherche dlments sont les algorithmes find et find_if, qui permettent de retrouver le premier lment dun conteneur vriant une proprit particulire, et lalgorithme find_first_of, qui permet de retrouver le premier lment vriant une relation avec une valeur parmi un ensemble de valeurs donnes. Tous ces algorithmes sont dclars dans len-tte algorithm :
template <class InputIterator, class T> InputIterator find(InputIterator premier, InputIterator dernier, const T &valeur); template <class InputIterator, class Predicate> InputIterator find_if(InputIterator premier, InputIterator dernier, Predicate p); template <class InputIterator, class ForwardIterator> InputIterator find_first_of(InputIterator premier1, InputIterator dernier1, ForwardIterator premier2, ForwardIterator dernier2); template <class InputIterator, class ForwardIterator, class BinaryPredicate> InputIterator find_first_of(InputIterator premier1, InputIterator dernier1, ForwardIterator premier2, ForwardIterator dernier2, BinaryPredicate p);

Lalgorithme find prend en paramtre les deux itrateurs classiques dnissant la squence dlments dans laquelle la recherche doit tre effectue. Il prend galement en paramtre la valeur de llment recherch, et renvoie un itrateur sur le premier lment qui dispose de cette valeur. Si vous dsirez effectuer une recherche sur un autre critre que lgalit des valeurs, vous devez utiliser lalgorithme find_if. Celui-ci prend un prdicat en paramtre la place de la valeur. Cest la valeur de ce prdicat, appliqu llment courant dans le parcours des lments de la squence dnie par les itrateurs premier et dernier, qui permettra de dterminer si cet lment est celui recherch ou non. La valeur retourne est litrateur dernier si aucun lment ne correspond au critre de recherche. La complexit de cet algorithme est linaire en fonction du nombre dlments de la squence dlments dans laquelle la recherche se fait. Lalgorithme find_first_of prend deux couples ditrateurs en paramtre. Le premier dnit lintervalle dlments dans lequel la recherche doit tre effectue et le deuxime un ensemble de valeur dont les lments doivent tre recherchs. Lalgorithme renvoie un itrateur sur le premier lment qui est gal lune des valeurs de lensemble de valeurs spci par le deuxime couple ditrateurs, ou litrateur dernier1 si cet lment nexiste pas. Il est galement possible dutiliser un autre critre que lgalit avec lun des lments de cet ensemble en utilisant un prdicat binaire dont la valeur indiquera si llment courant vrie le critre de recherche. Lorsquil est appel par lalgorithme, ce prdicat reoit en paramtre llment courant de la recherche et lune des valeurs de lensemble de valeurs spci par les itrateurs premier2 et dernier2. La complexit de cet algorithme est nm, o n est le nombre dlments de la squence dans laquelle la recherche est effectue et m est le nombre de valeurs avec lesquelles ces lments doivent tre compars. Exemple 18-16. Algorithme de recherche dlments
#include <iostream>

470

Chapitre 18. Les algorithmes


#include <algorithm> using namespace std; int main(void) { int t[10] = {0, 5, 3, 4, 255, 7, 0, 5, 255, 9}; // Recherche les lments valant 0 ou 255 : int sep[2] = {0, 255}; int *debut = t; int *fin = t+10; int *courant; while ((courant=find_first_of(debut, fin, sep, sep+2)) != fin) { // Affiche la position de llment trouv : cout << *courant << " en position " << courant-t << endl; debut = courant+1; } return 0; }

18.2.2. Oprations de recherche de motifs


Les oprations de recherche de motifs permettent de trouver les premires et les dernires occurrences dun motif donn dans une suite de valeurs. Ces oprations sont ralises respectivement par les algorithmes search et find_end, dont la dclaration dans len-tte algorithm est la suivante :
template <class ForwardIterator1, class ForwardIterator2> ForwardIterator1 search(ForwardIterator1 premier1, ForwardIterator1 dernier1, ForwardIterator2 premier2, ForwardIterator2 dernier2); template <class ForwardIterator1, class ForwardIterator2, class BinaryPredicate> ForwardIterator1 search(ForwardIterator1 premier1, ForwardIterator1 dernier1, ForwardIterator2 premier2, ForwardIterator2 dernier2, BinaryPredicate p); template <class ForwardIterator1, class ForwardIterator2> ForwardIterator1 find_end(ForwardIterator1 premier1, ForwardIterator1 dernier1, ForwardIterator2 premier2, ForwardIterator2 dernier2); template <class ForwardIterator1, class ForwardIterator2, class BinaryPredicate> ForwardIterator1 find_end(ForwardIterator1 premier1, ForwardIterator1 dernier1, ForwardIterator2 premier2, ForwardIterator2 dernier2, BinaryPredicate p);

Tous ces algorithmes prennent en paramtre deux couples ditrateurs, le premier permettant didentier la squence des valeurs dans laquelle la recherche du motif doit tre effectue et le deuxime permettant didentier le motif lui-mme. Chaque algorithme est fourni sous la forme de deux surcharges. La premire recherche le motif en comparant les lments laide de loprateur

471

Chapitre 18. Les algorithmes dgalit du type des lments compars. La deuxime permet deffectuer cette comparaison laide dun prdicat binaire, que lon fournit dans ce cas en dernier paramtre. La valeur retourne par lalgorithme search est un itrateur sur la premire occurrence du motif dans la squence de valeurs spcies par les itrateurs premier1 et dernier1 ou litrateur dernier1 lui-mme si ce motif ny apparat pas. De mme, la valeur retourne par lalgorithme find_end est un itrateur rfrenant la dernire occurrence du motif dans la squence des valeurs spcie par les itrateurs premier1 et dernier1, ou litrateur dernier1 lui-mme si le motif na pas pu tre trouv. Exemple 18-17. Algorithmes de recherche de motif
#include <iostream> #include <algorithm> using namespace std; int main(void) { int t[10] = {1, 2, 4, 5, 3, 1, 2, 3, 5, 9}; // Recherche le motif {1, 2, 3} dans le tableau : int motif[3] = {1, 2, 3}; int *p = search(t, t+10, motif, motif+3); cout << "{1, 2, 3} en position " << p - t << endl; // Recherche la dernire occurrence de {1, 2} : p = find_end(t, t+10, motif, motif+2); cout << "Dernier {1, 2} en position " << p - t << endl; return 0; }

La complexit de lalgorithme search est nm, o n est le nombre dlments de la squence spcie par le premier couple ditrateurs et m est la longueur du motif rechercher. La complexit de lalgorithme find_end est n(n-m). Lorsque tous les lments du motif sont gaux, il est possible dutiliser lalgorithme search_n. Cet algorithme permet en effet de rechercher une srie de valeurs identiques dans une squence. Il est dclar comme suit dans len-tte algorithm :
template <class ForwardIterator, class Size, class T> ForwardIterator search_n(ForwardIterator premier, ForwardIterator dernier, Size nombre, const T &valeur); template <class ForwardIterator, class Size, class T, class BinaryPredicate> ForwardIterator search_n(ForwardIterator premier, ForwardIterator dernier, Size nombre, const T &valeur, BinaryPredicate p);

Les deux surcharges de cet algorithme prennent en paramtre les itrateurs dnissant la squence de valeurs dans laquelle la recherche doit tre effectue, la longueur du motif rechercher, et la valeur des lments de ce motif. La deuxime version de cet algorithme accepte galement un prdicat binaire, qui sera utilis pour effectuer la comparaison des lments de la squence dans laquelle la recherche se fait avec la valeur passe en paramtre. La valeur retourne est un itrateur rfrenant la premire occurrence du motif recherch ou litrateur dernier si ce motif nexiste pas dans la squence de

472

Chapitre 18. Les algorithmes valeurs analyse. La complexit de lalgorithme search_n est nm, o n est la taille de la squence dans laquelle la recherche est effectue et m est la longueur du motif recherch. Un cas particulier de la recherche de valeurs successives est lidentication de doublons de valeurs. Cette identication peut tre ralise grce lalgorithme adjacent_find. Contrairement lalgorithme search_n, adjacent_find localise tous les couples de valeurs dune srie de valeurs, quelle que soit la valeur des lments de ces couples. Il est donc inutile de prciser cette valeur, et les surcharges de cet algorithme sont dclares comme suit dans len-tte algorithm :
template <class ForwardIterator> ForwardIterator adjacent_find(ForwardIterator premier, ForwardIterator dernier); template <class ForwardIterator, class BinaryPredicate> ForwardIterator adjacent_find(ForwardIterator premier, ForwardIterator dernier, BinaryPredicate p);

Les itrateurs fournis en paramtre permettent comme laccoutume de dnir la squence dlments dans laquelle la recherche seffectue. La deuxime surcharge prend galement en paramtre un prdicat binaire dnissant la relation de comparaison utilise par lalgorithme. La valeur retourne par ces algorithmes est litrateur rfrenant le premier doublon dans la squence de valeurs analyse, ou litrateur dernier si aucun doublon nexiste. Exemple 18-18. Algorithme de recherche de doublons
#include <iostream> #include <algorithm> using namespace std; int main(void) { int t[10] = {0, 1, 2, 2, 3, 4, 4, 5, 6, 2}; // Recherche les doublons dans le tableau : int *debut = t; int *fin = t+10; int *p; while ((p = adjacent_find(debut, fin)) != fin) { cout << "Doublon en position " << p-t << endl; debut = p+1; } return 0; }

La complexit de cet algorithme est linaire en fonction de la taille de la squence de valeurs dans laquelle la recherche se fait.

18.3. Oprations dordonnancement


La bibliothque standard fournit plusieurs algorithmes relatifs lordonnancement des lments dans les conteneurs. Grce ces algorithmes, il est possible de rorganiser la squence de ces lments

473

Chapitre 18. Les algorithmes de manire obtenir certaines proprits bases sur la relation dordre. Ces rorganisations ont gnralement pour but soit de trier compltement ces squences, soit deffectuer des tris partiels partir desquels il est possible dobtenir des informations relatives lordre des lments de manire trs efcace. La plupart des algorithmes de tri et dordonnancement se basent sur une structure de donnes trs performante : les tas . Les algorithmes de manipulation de ces structures de donnes seront donc dcrits en premier. Les sections qui suivront traiteront ensuite des algorithmes de tri et de recherches binaires dans un ensemble dlments dj tri.

18.3.1. Oprations de gestion des tas


Un tas ( heap en anglais) est une structure de donnes rcursive dans laquelle le premier lment est toujours le plus grand lment et qui dispose dune opration de suppression du premier lment ainsi que dune opration dajout dun nouvel lment extrmement performantes. Plus prcisment, les proprits fondamentales des tas sont les suivantes :

le premier lment du tas est toujours le plus grand de tous les lments contenus ; il est possible de supprimer ce premier lment et cette opration de suppression a une complexit logarithmique en fonction du nombre dlments dans le tas ; il est possible dajouter un nouvel lment dans le tas avec une complexit galement logarithmique en fonction du nombre dlments dj prsents.

Les tas sont donc particulirement adapts pour raliser les les de priorit puisque la dtermination du plus grand lment est immdiate et que la suppression de cet lment se fait avec une complexit logarithmique. Les tas sont galement trs utiles dans limplmentation des algorithmes de tri car ils permettent datteindre une complexit algorithmique en nln(n), ce qui est loptimum.
Note : En pratique, un tas est une forme darbre binaire quilibr dont la proprit rcursive est que la racine de larbre est llment de plus grande valeur et que les deux branches de larbre sont eux-mme des tas. La suppression de la racine, ainsi que lajout dun nouvel lment, ncessite une rorganisation de larbre binaire, ce qui ne peut dpasser ln(n) oprations en raison de son aspect quilibr. Notez que les tas ne garantissent pas, contrairement aux B-arbres et aux arbres rouges et noirs, que tous les lments situs la gauche dun noeud sont plus grands que le noeud lui-mme et que tous les lments situs la droite sont plus petits. Cest pour cette raison quun tas nest justement pas compltement tri, et que les algorithmes de gestion des tas ne font que conserver cet ordre partiel. La reprsentation des tas en mmoire peut tre relativement difcile comprendre. En gnral, il est dusage de les stocker dans des tableaux, car les oprations de gestion des tas requirent des itrateurs accs alatoires sur le conteneur sur lequel elles travaillent. Dans ce cas, les premiers lments du tableau stockent les noeuds de larbre binaire du tas, et les feuilles sont places dans la seconde moiti du tableau. Ainsi, un lment dindice i a comme feuilles les lments dindice 2i et 2i+1 (pour tout i < n/2). Reportez-vous la bibliographie pour plus de renseignements sur les structures de donnes et les notions algorithmiques associes.

Les algorithmes de manipulation des tas sont dclars comme suit dans len-tte algorithm :
template <class RandomAccessIterator> void make_heap(RandomAccessIterator premier, RandomAccessIterator dernier);

474

Chapitre 18. Les algorithmes


template <class RandomAccessIterator, class Compare> void make_heap(RandomAccessIterator premier, RandomAccessIterator dernier, Compare c); template <class RandomAccessIterator> void pop_heap(RandomAccessIterator premier, RandomAccessIterator dernier); template <class RandomAccessIterator, class Compare> void pop_heap(RandomAccessIterator premier, RandomAccessIterator dernier, Compare c); template <class RandomAccessIterator> void push_heap(RandomAccessIterator premier, RandomAccessIterator dernier); template <class RandomAccessIterator, class Compare> void push_heap(RandomAccessIterator premier, RandomAccessIterator dernier, Compare c); template <class RandomAccessIterator> void sort_heap(RandomAccessIterator premier, RandomAccessIterator dernier); template <class RandomAccessIterator, class Compare> void sort_heap(RandomAccessIterator premier, RandomAccessIterator dernier, Compare c);

Lalgorithme make_heap permet de construire un nouveau tas partir dune squence dlments quelconque. Il prend simplement en paramtre les itrateurs de dbut et de n de cette squence, et ne retourne rien. Sa complexit est une fonction linaire du nombre dlments rfrencs par ces deux itrateurs. Les algorithmes pop_heap et push_heap permettent respectivement de supprimer la tte dun tas existant et dajouter un nouvel lment dans un tas. pop_heap prend en paramtre deux itrateurs rfrenant le premier et le dernier lment du tas. Il place le premier lment du tas en dernire position et rorganise les lments restants de telle sorte que les dernier-premier-1 lments constituent un nouveau tas. Lalgorithme push_heap en revanche effectue le travaille inverse : il prend en paramtre deux itrateurs rfrenant une squence dont les premiers lments sauf le dernier constituent un tas et y ajoute llment rfrenc par litrateur dernier-1. Ces deux oprations effectuent leur travail avec une complexit logarithmique. Enn, lalgorithme sort_heap permet simplement de trier une squence ayant la structure de tas. Sa complexit est nln(n), o n est le nombre dlments de la squence. Exemple 18-19. Algorithmes de manipulation des tas
#include <iostream> #include <algorithm> using namespace std; int main(void) { int t[10] = {5, 8, 1, 6, 7, 9, 4, 3, 0, 2}; // Construit un tas partir de ce tableau : make_heap(t, t+10); // Affiche le tas :

475

Chapitre 18. Les algorithmes


int i; for (i=0; i<10; ++i) cout << t[i] << " "; cout << endl; // Supprime llment de tte : pop_heap(t, t+10); // Llment de tte est en position 9 : cout << "Max = " << t[9] << endl; // Affiche le nouveau tas : for (i=0; i<9; ++i) cout << t[i] << " "; cout << endl; // Ajoute un lment : t[9] = 6; push_heap(t, t+10); // Affiche le nouveau tas : for (i=0; i<10; ++i) cout << t[i] << " "; cout << endl; // Tri le tas : sort_heap(t, t+10); // Affiche le tableau ainsi tri : for (i=0; i<10; ++i) cout << t[i] << " "; cout << endl; return 0; }

18.3.2. Oprations de tri


Les oprations de tri de la bibliothque standard sappuient sur les algorithmes de manipulation des tas que lon vient de voir. Ces mthodes permettent deffectuer un tri total des lments dune squence, un tri stable, lgrement moins performant que le prcdent mais permettant de conserver lordre relatif des lments quivalents, et un tri partiel. Les algorithmes de tri sont dclars comme suit dans len-tte algorithm :
template <class RandomAccessIterator> void sort(RandomAccessIterator premier, RandomAccessIterator dernier); template <class RandomAccessIterator, class Compare> void sort(RandomAccessIterator premier, RandomAccessIterator dernier, Compare c); template <class RandomAccessIterator> void stable_sort(RandomAccessIterator premier, RandomAccessIterator dernier); template <class RandomAccessIterator, class Compare> void stable_sort(RandomAccessIterator premier, RandomAccessIterator dernier, Compare c);

Les algorithmes sort et stable_sort sutilisent de la mme manire et permettent de trier compltement la squence qui leur est spcie laide des deux itrateurs premier et dernier. Ces deux algorithmes effectuent un tri par ordre croissant en utilisant loprateur dinfriorit du type des

476

Chapitre 18. Les algorithmes lments de la squence trier. Cependant, il est galement possible dutiliser un autre critre, en spciant un foncteur binaire en troisime paramtre. Ce foncteur doit tre capable de comparer deux lments de la squence trier et dindiquer si le premier est ou non le plus petit au sens de la relation dordre quil utilise. Exemple 18-20. Algorithme de tri
#include <iostream> #include <algorithm> using namespace std; int main(void) { int t[10] = {2, 3, 7, 5, 4, 1, 8, 0, 9, 6}; // Trie le tableau : sort(t, t+10); // Affiche le rsultat : int i; for (i=0; i<10; ++i) cout << t[i] << " "; cout << endl; return 0; }

Il se peut que plusieurs lments de la squence soient considrs comme quivalents par la relation dordre utilise. Par exemple, il est possible de trier des structures selon lun de leurs champs, et plusieurs lments peuvent avoir la mme valeur dans ce champ sans pour autant tre strictement gaux. Dans ce cas, il peut tre ncessaire de conserver lordre relatif initial de ces lments dans la squence trier. Lalgorithme sort ne permet pas de le faire, cependant, lalgorithme stable_sort garantit la conservation de cet ordre relatif, au prix dune complexit algorithmique lgrement suprieure. En effet, la complexit de stable_sort est nln2 (n) (o n est le nombre dlments trier), alors que celle de lalgorithme sort nest que de nln(n). Hormis cette petite diffrence, les deux algorithmes sont strictement quivalents. Dans certaines situations, il nest pas ncessaire deffectuer un tri total des lments. En effet, le tri des premiers lments dune squence seulement ou bien seule la dtermination du nime lment dun ensemble peuvent tre dsirs. cet effet, la bibliothque standard fournit les algorithmes suivants :
template <class RandomAccessIterator> void partial_sort(RandomAccessIterator premier, RandomAccessIterator pivot, RandomAccessIterator dernier); template <class InputIterator, class RandomAccessIterator> RandomAccessIterator partial_sort_copy( InputIterator premier, InputIterator dernier, RandomAccessIterator debut_resultat, RandomAccessIterator fin_resultat); template <class RandomAccessIterator, class Compare> void partial_sort( RandomAccessIterator premier, RandomAccessIterator fin_tri, RandomAccessIterator dernier, Compare c); template <class InputIterator, class RandomAccessIterator, class Compare> RandomAccessIterator partial_sort_copy(

477

Chapitre 18. Les algorithmes


InputIterator premier, InputIterator dernier, RandomAccessIterator debut_resultat, RandomAccessIterator fin_resultat, Compare c); template <class RandomAccessIterator> void nth_element(RandomAccessIterator premier, RandomAccessIterator position, RandomAccessIterator dernier); template <class RandomAccessIterator, class Compare> void nth_element(RandomAccessIterator premier, RandomAccessIterator position, RandomAccessIterator dernier, Compare c);

Lalgorithme partial_sort permet de neffectuer quun tri partiel dune squence. Cet algorithme peut tre utilis lorsquon dsire nobtenir que les premiers lments de la squence trie. Cet algorithme existe en deux versions. La premire version prend en paramtre litrateur de dbut de la squence, litrateur de la position du dernier lment de la squence qui sera trie la n de lexcution de lalgorithme, et litrateur de n de la squence. La deuxime version, nomme partial_sort_copy, permet de copier le rsultat du tri partiel un autre emplacement que celui de la squence initiale. Cette version de lalgorithme de tri partiel prend alors deux couples ditrateurs en paramtre, le premier spciant la squence sur laquelle lalgorithme doit travailler et le deuxime lemplacement destination dans lequel le rsultat doit tre stock. Enn, comme pour tous les autres algorithmes, il est possible de spcier un autre oprateur de comparaison que loprateur dinfriorit utilis par dfaut en fournissant un foncteur binaire en dernier paramtre. Exemple 18-21. Algorithme de tri partiel
#include <iostream> #include <algorithm> using namespace std; int main(void) { int t[10] = {2, 3, 7, 5, 4, 1, 8, 0, 9, 6}; // Trie les 5 premiers lments du tableau : partial_sort(t, t+5, t+10); // Affiche le rsultat : int i; for (i=0; i<10; ++i) cout << t[i] << " "; cout << endl; return 0; }

La complexit de lalgorithme partial_sort est nln(m), o n est la taille de la squence sur laquelle lalgorithme travaille et m est le nombre dlments tris obtenir. Lalgorithme nth_element permet quant lui de calculer la valeur dun lment de rang donn dans le conteneur si celui-ci tait compltement tri. Cet algorithme prend en paramtre litrateur de dbut de la squence traiter, litrateur rfrenant lemplacement qui recevra llment qui sera plac sa position la n de lopration de tri partiel et litrateur de n de la squence. Il est galement possible, comme pour les autres algorithmes, de spcier un foncteur utiliser pour tester linfriorit des lments de la squence. lissue de lappel, le n-ime lment de la squence sera le mme lment que celui qui se trouverait cette position si la squence tait compltement trie

478

Chapitre 18. Les algorithmes selon la relation dordre induite par loprateur dinfriorit ou par le foncteur fourni en paramtre. La complexit de lalgorithme nth_element est linaire en fonction du nombre dlments de la squence traiter. Exemple 18-22. Algorithme de positionnement du nime lment
#include <iostream> #include <algorithm> using namespace std; int main(void) { int t[10] = {2, 3, 9, 6, 7, 5, 4, 0, 1, 8}; // Trie tous les lments un un : int i; for (i=0; i<10; ++i) { nth_element(t, t+i, t+10); cout << "Llment " << i << " a pour valeur " << t[i] << endl; } return 0; }

Enn, et bien que ces algorithmes ne fassent pas proprement parler des oprations de tri, la bibliothque standard fournit deux algorithmes permettant dobtenir le plus petit et le plus grand des lments dune squence. Ces algorithmes sont dclars de la manire suivante dans len-tte algorithm :
template <class ForwardIterator> ForwardIterator min_element(ForwardIterator premier, ForwardIterator dernier); template <class ForwardIterator, class Compare> ForwardIterator min_element(ForwardIterator premier, ForwardIterator dernier, Compare c); template <class ForwardIterator> ForwardIterator max_element(ForwardIterator premier, ForwardIterator dernier); template <class ForwardIterator, class Compare> ForwardIterator max_element(ForwardIterator premier, ForwardIterator dernier, Compare c);

Ces deux algorithmes prennent en paramtre deux itrateurs permettant de dnir la squence des lments dont le minimum et le maximum doivent tre dtermins. Ils retournent un itrateur rfrenant respectivement le plus petit et le plus grand des lments de cette squence. La complexit de ces algorithmes est proportionnelle la taille de la squence fournie en paramtre. Exemple 18-23. Algorithmes de dtermination du maximum et du minimum
#include <iostream> #include <algorithm> using namespace std;

479

Chapitre 18. Les algorithmes

int main(void) { int t[10] = {5, 2, 4, 6, 3, 7, 9, 1, 0, 8}; // Affiche le minimum et le maximum : cout << *min_element(t, t+10) << endl; cout << *max_element(t, t+10) << endl; return 0; }

18.3.3. Oprations de recherche binaire


Les oprations de recherche binaire de la bibliothque standard sont des oprations qui permettent de manipuler des squences dlments dj tries en se basant sur cet ordre. Les principales fonctionnalits de ces algorithmes sont de rechercher les positions des lments dans ces squences en fonction de leur valeur. Les principaux algorithmes de recherche binaire sont les algorithmes lower_bound et upper_bound. Ces algorithmes sont dclars comme suit dans len-tte algorithm :
template <class ForwardIterator, class T> ForwardIterator lower_bound(ForwardIterator premier, ForwardIterator dernier, const T &valeur); template <class ForwardIterator, class T, class Compare> ForwardIterator lower_bound(ForwardIterator premier, ForwardIterator dernier, const T &valeur, Compare c); template <class ForwardIterator, class T> ForwardIterator upper_bound(ForwardIterator premier, ForwardIterator dernier, const T &valeur); template <class ForwardIterator, class T, class Compare> ForwardIterator upper_bound(ForwardIterator premier, ForwardIterator dernier, const T &valeur, Compare c);

Lalgorithme lower_bound dtermine la premire position laquelle la valeur valeur peut tre insre dans la squence ordonne spcie par les itrateurs premier et dernier sans en briser lordre. De mme, lalgorithme upper_bound dtermine la dernire position laquelle la valeur valeur peut tre insre sans casser lordre de la squence sur laquelle il travaille. Il est suppos ici que linsertion se ferait avant les lments indiqus par ces itrateurs, comme cest gnralement le cas pour tous les conteneurs. Si le programmeur veut dterminer simultanment les deux itrateurs renvoys par les algorithmes lower_bound et upper_bound, il peut utiliser lalgorithme equal_range suivant :
template <class ForwardIterator, class T> pair<ForwardIterator, ForwardIterator> equal_range(ForwardIterator premier, ForwardIterator dernier, const T &valeur); template <class ForwardIterator, class T, class Compare> pair<ForwardIterator, ForwardIterator> equal_range(ForwardIterator premier, ForwardIterator dernier,

480

Chapitre 18. Les algorithmes


const T &valeur, Compare comp);

Cet algorithme renvoie une paire ditrateurs contenant respectivement la premire et la dernire des positions auxquelles la valeur valeur peut tre insre sans perturber lordre de la squence identie par les itrateurs premier et dernier. Exemple 18-24. Algorithmes de dtermination des bornes infrieures et suprieures
#include <iostream> #include <algorithm> using namespace std; int main(void) { int t[10] = {1, 2, 4, 4, 4, 5, 8, 9, 15, 20}; // Dtermine les positions possibles dinsertion // dun 4 : cout << "4 peut tre insr de " << lower_bound(t, t+10, 4) - t << " " << upper_bound(t, t+10, 4) - t << endl; // Rcupre ces positions directement // avec equal_range : pair<int *, int *> p = equal_range(t, t+10, 4); cout << "Equal range donne lintervalle [" << p.first-t << ", " << p.second-t << "]"; cout << endl; return 0; }

Comme pour la plupart des algorithmes de la bibliothque standard, il est possible de spcier un foncteur qui devra tre utilis par les algorithmes de recherche binaire dans les comparaisons dinfriorit des lments de la squence. Enn, lalgorithme binary_search permet de dterminer si un lment dun conteneur au moins est quivalent une valeur donne au sens de loprateur dinfriorit ou au sens dun foncteur fourni en paramtre. Cet algorithme est dclar de la manire suivante dans len-tte algorithm :
template <class ForwardIterator, class T> bool binary_search(ForwardIterator premier, ForwardIterator dernier, const T &valeur); template <class ForwardIterator, class T, class Compare> bool binary_search(ForwardIterator premier, ForwardIterator dernier, const T &valeur, Compare c);

Cet algorithme prend en paramtre les deux itrateurs dnissant la squence dlments tester, la valeur avec laquelle ses lments doivent tre tests, et ventuellement un foncteur permettant de raliser une opration de comparaison autre que celle de loprateur dinfriorit. Il renvoie un boolen indiquant si un des lments au moins du conteneur est quivalent la valeur fournie en paramtre.

481

Chapitre 18. Les algorithmes


Note : La relation dquivalence utilise par cet algorithme nest pas celle induite par loprateur dgalit des lments. En ralit, deux lments x et y sont considrs comme quivalents si et seulement si les deux inquations x<y et y<x sont fausses. Cest la raison pour laquelle le foncteur fourni en paramtre ne doit pas dnir la relation dgalit, mais la relation dinfriorit. Cette distinction a son importance si certains lments de la squence ne sont pas comparables ou si loprateur dgalit dnit une autre relation que loprateur dinfriorit. Bien entendu, en pratique, ces deux inquations signie souvent que les valeurs x et y sont gales.

Exemple 18-25. Algorithme de recherche binaire


#include <iostream> #include <string> #include <algorithm> using namespace std; struct A { int numero; string nom;

// Numro unique de llment // Nom de llment

A(const char *s) : nom(s) { // Affecte un nouveau numro : static int i=0; numero = ++i; } // Oprateur de classement : bool operator<(const A &a) const { return (numero < a.numero); } // Oprateur dgalit (jamais utilis) : bool operator==(const A &a) const { return (nom == a.nom); } }; int main(void) { // Construit un tableau dlments tris // (par construction, puisque le numro est incrment // chaque nouvel objet) : A t[5] = {"Jean", "Marc", "Alain", "Ariane", "Sophie"}; // Cette instance a le mme nom que t[1] // mais ne sera pas trouv car son numro est diffrent : A test("Marc"); // Effectue la recherche de test dans le tableau : if (binary_search(t, t+5, test)) { cout << "(" << test.numero << ", " <<

482

Chapitre 18. Les algorithmes


test.nom << ") a t trouv" << endl; } else { cout << "(" << test.numero << ", " << test.nom << ") na pas t trouv" << endl; } return 0; }

La complexit algorithmique de tous ces algorithmes est logarithmique en fonction du nombre dlments de la squence sur laquelle ils travaillent. Ils sappuient sur le fait que cette squence est dj trie pour atteindre cet objectif.

18.4. Oprations de comparaison


An de faciliter la comparaison de conteneurs de natures diffrentes pour lesquels, de surcrot, il nexiste pas forcment doprateurs de comparaison, la bibliothque standard fournit plusieurs algorithmes de comparaison. Ces algorithmes sont capables deffectuer une comparaison lment lment des diffrents conteneurs pour vrier leur galit en terme dlments contenus, ou de dterminer une relation dordre au sens lexicographique. Enn, il est possible de dterminer les lments par lesquels deux conteneurs se diffrencient. Lalgorithme gnral de comparaison des conteneurs est lalgorithme equal. Cet algorithme est dclar comme suit dans len-tte algorithm :
template <class InputIterator1, class InputIterator2> bool equal(InputIterator1 premier1, InputIterator1 dernier1, InputIterator2 premier2); template <class InputIterator1, class InputIterator2, class BinaryPredicate> bool equal(InputIterator1 premier1, InputIterator1 dernier1, InputIterator2 premier2, BinaryPredicate p);

Comme vous pouvez le constater daprs cette dclaration, lalgorithme equal prend en paramtre un couple ditrateurs dcrivant la squence dlments qui doivent tre pris en compte dans la comparaison ainsi quun itrateur sur le premier lment du deuxime conteneur. Les lments rfrencs successivement par les itrateurs premier1 et premier2 sont ainsi compars, jusqu ce quune diffrence soit dtecte ou que litrateur dernier1 du premier conteneur soit atteint. La valeur retourne est true si les deux squences dlments des deux conteneurs sont gales lment lment, et false sinon. Bien entendu, il est possible de spcier un foncteur binaire que lalgorithme devra utiliser pour raliser les comparaisons entre les lments des deux conteneurs. Sil est spci, ce foncteur est utilis pour dterminer si les lments compars sont gaux ou non.
Note : Notez bien ici que le foncteur fourni permet de tester lgalit de deux lments et non linfriorit, comme cest le cas avec la plupart des autres algorithmes.

Sil savre que les deux conteneurs ne sont pas gaux membre membre, il peut tre utile de dterminer les itrateurs des deux lments qui ont fait chouer le test dgalit. Cela peut tre ralis laide de lalgorithme mismatch dont on trouvera la dclaration dans len-tte algorithm :

483

Chapitre 18. Les algorithmes


template <class InputIterator1, class InputIterator2> pair<InputIterator1, InputIterator2> mismatch(InputIterator1 premier1, InputIterator1 dernier1, InputIterator2 premier2); template <class InputIterator1, class InputIterator2, class BinaryPredicate> pair<InputIterator1, InputIterator2> mismatch(InputIterator1 premier1, InputIterator1 dernier1, InputIterator2 premier2, BinaryPredicate p);

Cet algorithme fonctionne exactement de la mme manire que lalgorithme equal. Cependant, contrairement ce dernier, sa valeur de retour est une paire ditrateurs des deux conteneurs rfrenant les lments respectifs qui ne sont pas gaux au sens de lopration de comparaison utilise par lalgorithme (que ce soit loprateur dgalit ou le foncteur fourni en paramtre). Si les deux conteneurs sont effectivement gaux, la valeur retourne est la paire contenant litrateur dernier1 et litrateur correspondant dans le deuxime conteneur. Exemple 18-26. Algorithme de comparaison de conteneurs
#include <iostream> #include <algorithm> using namespace std; int main(void) { int t1[10] = {5, 6, 4, 7, 8, 9, 2, 1, 3, 0}; int t2[10] = {5, 6, 4, 7, 9, 2, 1, 8, 3, 0}; // Compare les deux tableaux : if (!equal(t1, t1+10, t2)) { // Dtermine les lments diffrents : pair<int *, int *> p = mismatch(t1, t1+10, t2); cout << *p.first << " est diffrent de " << *p.second << endl; } return 0; }

Enn, la bibliothque standard fournit un algorithme de comparaison gnral permettant de dterminer si un conteneur est infrieur un autre conteneur selon la relation dordre lexicographique induite par loprateur dinfriorit du type de leurs lments. Rappelons que lordre lexicographique est celui utilis par le dictionnaire : les lments sont examins un un et dans leur ordre dapparition et la comparaison sarrte ds que deux lments diffrents sont trouvs. En cas dgalit totale, le plus petit des conteneurs est celui qui contient le moins dlments. Lalgorithme de comparaison lexicographique est lalgorithme lexicographical_compare. Il est dclar comme suit dans len-tte algorithm :
template <class InputIterator1, class InputIterator2> bool lexicographical_compare(InputIterator1 premier1, InputIterator1 dernier1, InputIterator2 premier2, InputIterator2 dernier2); template <class InputIterator1, class InputIterator2, class Compare>

484

Chapitre 18. Les algorithmes


bool lexicographical_compare(InputIterator1 premier1, InputIterator1 dernier1, InputIterator2 premier2, InputIterator2 dernier2, Compare c);

Cet algorithme prend en paramtre deux couples ditrateurs grce auxquels le programmeur peut spcier les deux squences dlments comparer selon lordre lexicographique. Comme laccoutume, il est galement possible de fournir un foncteur utiliser pour les tests dinfriorit entre les lments des deux conteneurs. La valeur retourne par lalgorithme lexicographical_compare est true si le premier conteneur est strictement plus petit que le deuxime et false sinon. Exemple 18-27. Algorithme de comparaison lexicographique
#include <iostream> #include <algorithm> using namespace std; int main(void) { int t1[10] = {5, 6, 4, 7, 8, 9, 2, 1, 3, 0}; int t2[10] = {5, 6, 4, 7, 9, 2, 1, 8, 3, 0}; // Compare les deux tableaux : if (lexicographical_compare(t1, t1+10, t2, t2+10)) { cout << "t1 est plus petit que t2" << endl; } return 0; }

Tous ces algorithmes de comparaison sexcutent avec une complexit linaire en fonction du nombre dlments comparer.

18.5. Oprations ensemblistes


En mathmatiques, il est possible deffectuer diffrents types doprations sur les ensembles. Ces oprations comprennent la dtermination de linclusion dun ensemble dans un autre, leur union (cest-dire le regroupement de tous leurs lments), leur intersection (la slection de leurs lments communs), leur diffrence (la suppression des lments dun ensemble qui appartiennent aussi un autre ensemble) et leur partitionnement (le dcoupage dun ensemble en sous-ensemble dont les lments vrient une proprit discriminante). La bibliothque standard fournit tout un ensemble dalgorithmes qui permettent deffectuer les oprations ensemblistes classiques sur les conteneurs tris. Tous ces algorithmes sont dcrits ci-dessous et sont classs selon la nature des oprations quils ralisent.
Note : Remarquez ici que la notion de tri est importante : les algorithmes sappuient sur cette proprit des conteneurs pour effectuer leur travail. En contrepartie de cette contrainte, les performances de ces algorithmes sont excellentes.

485

Chapitre 18. Les algorithmes

18.5.1. Oprations dinclusion


Linclusion dun ensemble dans un autre peut tre ralise laide de lalgorithme includes. Cet algorithme est dclar comme suit dans len-tte algorithm :
template <class InputIterator1, class InputIterator2> bool includes(InputIterator1 premier1, InputIterator1 dernier1, InputIterator2 premier2, InputIterator2 dernier2); template <class InputIterator1, class InputIterator2, class Compare> bool includes(InputIterator1 premier1, InputIterator1 dernier1, InputIterator2 premier2, InputIterator2 dernier2, Compare c);

Lalgorithme includes prend en paramtre deux couples ditrateurs permettant de dnir les squences dlments des deux ensembles sur lesquels il doit travailler. La valeur retourne par cet algorithme est true si tous les lments de la squence identie par les itrateurs premier2 et dernier2 sont galement prsents dans la squence identie par les itrateurs premier1 et dernier1. Lalgorithme considre quun lment est prsent dans un ensemble sil existe au moins un lment de cet ensemble qui lui est identique. Chaque lment utilis de lensemble ne lest quune seule fois, ainsi, si lensemble dont on teste linclusion dispose de plusieurs copies du mme lment, il faut quil y en ait autant dans lensemble conteneur pour que le test dinclusion soit valide. Bien entendu, il est possible dutiliser une autre relation que lgalit pour dterminer lappartenance dun lment un ensemble, pour cela, il suft de fournir un foncteur binaire en dernier paramtre. Ce prdicat doit prendre deux lments en paramtre et renvoyer true si le premier lment est infrieur au second, et false dans le cas contraire.
Note : Il est important que le foncteur dinfriorit spci soit compatible avec la relation dordre utilise pour le tri des lments des conteneurs. Si ce nest pas le cas, lalgorithme peut ne pas fonctionner correctement.

Exemple 18-28. Algorithme de dtermination dinclusion


#include <iostream> #include <algorithm> using namespace std; int main(void) { int t1[10] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}; int t2[3] = {4, 5, 6}; if (includes(t1, t1+10, t2, t2+3)) cout << "t1 contient t2" << endl; return 0; }

La complexit de lalgorithme includes est n+m, o n et m sont respectivement les tailles des deux conteneurs qui lui sont fournis en paramtre.

486

Chapitre 18. Les algorithmes

18.5.2. Oprations dintersection


Lintersection de deux ensembles peut tre ralise laide de lalgorithme set_intersection. Cet algorithme est dclar de la manire suivante dans len-tte algorithm :
template <class InputIterator1, class InputIterator2, class OutputIterator> OutputIterator set_intersection(InputIterator1 premier1, InputIterator1 dernier1, InputIterator2 premier2, InputIterator2 dernier2, OutputIterator destination); template <class InputIterator1, class InputIterator2, class OutputIterator, class Compare> OutputIterator set_intersection(InputIterator1 premier1, InputIterator1 dernier1, InputIterator2 premier2, InputIterator2 dernier2, OutputIterator destination, Compare c);

Cet algorithme prend en paramtre les itrateurs de dbut et de n des deux conteneurs dont lintersection doit tre dtermine, ainsi quun itrateur rfrenant lemplacement destination o les lments de lintersection doivent tre stocks. Pour ceux qui le dsirent, il est galement possible de spcier un foncteur que lalgorithme utilisera pour effectuer les comparaisons dinfriorit entre les lments des deux conteneurs fournis en paramtre. Ce foncteur devra bien entendu tre compatible avec la relation dordre selon laquelle les conteneurs passs en paramtre sont tris. Lalgorithme copie lemplacement destination tous les lments du premier conteneur qui font galement partie du deuxime. Le critre dappartenance un ensemble est, comme pour lalgorithme includes, le fait quil existe au moins un lment dans le deuxime ensemble gal llment considr. De mme, si plusieurs copies dun mme lment se trouvent dans chaque ensemble, le nombre de copies de lintersection sera le plus petit nombre de copies de llment dans les deux ensembles sources. Exemple 18-29. Algorithme dintersection densembles
#include <iostream> #include <algorithm> using namespace std; int main(void) { int t1[10] = {2, 4, 6, 8, 9, 10, 15, 15, 15, 17}; int t2[10] = {1, 4, 5, 8, 11, 15, 15, 16, 18, 19}; int t[10]; // Effectue lintersection de t1 et de t2 : int *fin = set_intersection(t1, t1+10, t2, t2+10, t); // Affiche le rsultat : int *p = t; while (p != fin) { cout << *p << " "; ++p; } cout << endl; return 0; }

487

Chapitre 18. Les algorithmes La complexit de lalgorithme est n+m, o n et m sont respectivement les tailles des deux conteneurs qui lui sont fournis en paramtre.

18.5.3. Oprations dunion et de fusion


La bibliothque standard fournit plusieurs algorithmes permettant de raliser lunion de deux ensembles. Ces variantes se distinguent par la manire quelles ont de traiter le cas des lments en multiples exemplaires. Lalgorithme set_union considre que les lments quivalents des deux ensembles sont les mmes entits et ne les place quune seule fois dans lensemble rsultat de lunion. Toutefois, si ces lments sont en plusieurs exemplaires dans un des ensembles source, ils apparatront galement en plusieurs exemplaires dans le rsultat. Autrement dit, le nombre dlments prsents dans lensemble destination est le nombre maximum du compte de ses occurrences dans chacun des deux ensembles source. Inversement, lalgorithme merge effectue une union au sens large et ajoute les lments de chaque ensemble dans lensemble rsultat sans considrer leurs valeurs. Ainsi, le nombre dlments du rsultat est strictement gal la somme des nombres des lments de chaque conteneur source. An de distinguer ces deux comportements, on peut dire que lalgorithme set_union ralise lunion des deux ensembles, alors que lalgorithme merge ralise leur fusion. Tous ces algorithmes sont dclars comme suit dans len-tte algorithm :
template <class InputIterator1, class InputIterator2, class OutputIterator> OutputIterator set_union(InputIterator1 premier1, InputIterator1 dernier1, InputIterator2 premier2, InputIterator2 dernier2, OutputIterator destination); template <class InputIterator1, class InputIterator2, class OutputIterator, class Compare> OutputIterator set_union(InputIterator1 premier1, InputIterator1 dernier1, InputIterator2 premier2, InputIterator2 dernier2, OutputIterator destination, Compare c); template <class InputIterator1, class InputIterator2, class OutputIterator> OutputIterator merge(InputIterator1 premier1, InputIterator1 dernier1, InputIterator2 premier2, InputIterator2 dernier2, OutputIterator destination); template <class InputIterator1, class InputIterator2, class OutputIterator, class Compare> OutputIterator merge(InputIterator1 premier1, InputIterator1 dernier1, InputIterator2 dernier2, InputIterator2 premier2, OutputIterator destination, Compare c);

Comme vous pouvez le constater, ils prennent tous en paramtre les itrateurs permettant de spcier les deux ensembles ainsi quun itrateur destination indiquant lemplacement o les lments de lunion ou de la fusion doivent tre stocks. Enn, si le programmeur le dsire, il peut galement donner le foncteur dnissant la relation dordre selon laquelle les ensembles sont tris.

488

Chapitre 18. Les algorithmes Exemple 18-30. Algorithmes dunion et de fusion densembles
#include <iostream> #include <algorithm> using namespace std; int main(void) { int t1[4] = {1, 2, 5, 5}; int t2[6] = {3, 4, 5, 5, 5, 7}; int t[10]; // Effectue lunion de t1 et de t2 : int *fin = set_union(t1, t1+4, t2, t2+6, t); // Affiche le rsultat : int *p = t; while (p != fin) { cout << *p << " "; ++p; } cout << endl; // Effectue la fusion de t1 et de t2 : fin = merge(t1, t1+4, t2, t2+6, t); // Affiche le rsultat : p = t; while (p != fin) { cout << *p << " "; ++p; } cout << endl; return 0; }

La bibliothque standard fournit galement une version modie de lalgorithme merge dont le but est de fusionner deux parties dune mme squence dlments tries indpendamment lune de lautre. Cet algorithme permet deffectuer la fusion sur place, et ne travaille donc que sur un seul conteneur. Il sagit de lalgorithme inplace_merge, qui est dclar comme suit :
template <class BidirectionalIterator> void inplace_merge(BidirectionalIterator premier, BidirectionalIterator separation, BidirectionalIterator dernier); template <class BidirectionalIterator, class Compare> void inplace_merge(BidirectionalIterator premier, BidirectionalIterator separation, BidirectionalIterator dernier, Compare c);

Cet algorithme effectue la fusion des deux ensembles identis respectivement par les itrateurs premier et separation dune part, et par les itrateurs separation et dernier dautre part. Enn, si besoin est, il est possible de spcier le foncteur selon lequel ces deux ensembles sont tris.

489

Chapitre 18. Les algorithmes Exemple 18-31. Algorithme de runication de deux sous-ensembles
#include <iostream> #include <algorithm> using namespace std; int main(void) { int t[10] = {1, 5, 9, 0, 2, 3, 4, 6, 7, 8}; // Fusionne les deux sous-ensembles de t // (la sparation est au troisime lment) : inplace_merge(t, t+3, t+10); // Affiche le rsultat : int i; for (i=0; i<10; ++i) { cout << t[i] << " "; } cout << endl; return 0; }

Tous les algorithmes dunion et de fusion ont une complexit n+m, o n et m sont les tailles des deux ensembles fusionner ou runir.

18.5.4. Oprations de diffrence


La diffrence entre deux ensembles peut tre ralise avec lalgorithme set_difference. Cet algorithme supprime du premier ensemble tous les lments du second, si ncessaire. Chaque lment nest supprim quune seule fois, ainsi, si le premier ensemble contient plusieurs lments identiques et que le deuxime ensemble en contient moins, les lments rsiduels aprs suppression seront prsents dans la diffrence. La bibliothque standard fournit galement un algorithme de suppression symtrique, lalgorithme set_symmetric_difference, qui construit un nouvel ensemble contenant tous les lments des deux ensembles qui ne se trouvent pas dans lautre. Il sagit en fait de lunion des deux diffrences des deux ensembles.
Note : Remarquez que le mot symmetric scrit avec deux m en anglais. Ne vous tonnez donc pas dobtenir des erreurs de compilation si vous crivez set_symmetric_difference la franaise !

Les algorithmes set_difference et set_symmetric_difference sont dclars comme suit dans len-tte algorithm :
template <class InputIterator1, class InputIterator2, class OutputIterator> OutputIterator set_difference( InputIterator1 premier1, InputIterator1 dernier1, InputIterator2 premier2, InputIterator2 dernier2, OutputIterator destination); template <class InputIterator1, class InputIterator2,

490

Chapitre 18. Les algorithmes


class OutputIterator, class Compare> OutputIterator set_difference( InputIterator1 premier1, InputIterator1 dernier1, InputIterator2 premier2, InputIterator2 dernier2, OutputIterator destination, Compare c); template <class InputIterator1, class InputIterator2, class OutputIterator> OutputIterator set_symmetric_difference( InputIterator1 premier, InputIterator1 dernier, InputIterator2 premier, InputIterator2 dernier2, OutputIterator destination); template <class InputIterator1, class InputIterator2, class OutputIterator, class Compare> OutputIterator set_symmetric_difference( InputIterator1 premier1, InputIterator1 dernier1, InputIterator2 premier2, InputIterator2 dernier2, OutputIterator destination, Compare c);

Ils prennent tous deux paires ditrateurs identiant les deux ensembles dont la diffrence doit tre calcule ainsi quun itrateur rfrenant lemplacement destination dans lequel le rsultat doit tre plac. Comme laccoutume, il est possible dindiquer le foncteur permettant lalgorithme de raliser les tests dinfriorit entre deux lments et selon lequel les ensembles sont tris. La complexit de ces algorithmes est n+m, o n et m sont les nombres dlments des deux ensembles sur lesquels les algorithmes oprent. Exemple 18-32. Algorithmes de diffrence densembles
#include <iostream> #include <algorithm> using namespace std; int main(void) { int t1[10] = {0, 1, 5, 7, 7, 7, 8, 8, 9, 10}; int t2[10] = {0, 2, 3, 7, 9, 11, 12, 12, 13, 14}; int t[20]; // Calcule la diffrence de t1 et de t2 : int *fin = set_difference(t1, t1+10, t2, t2+10, t); // Affiche le rsultat : int *p = t; while (p != fin) { cout << *p << " "; ++p; } cout << endl; // Calcule la diffrence symtrique de t1 et t2 : fin = set_symmetric_difference(t1, t1+10, t2, t2+10, t); // Affiche le rsultat : int *p = t; while (p != fin) { cout << *p << " ";

491

Chapitre 18. Les algorithmes


++p; } cout << endl; // Calcule la diffrence symtrique de t1 et t2 : fin = set_symmetric_difference(t1, t1+10, t2, t2+10, t); // Affiche le rsultat : p = t; while (p != fin) { cout << *p << " "; ++p; } cout << endl; return 0; }

18.5.5. Oprations de partitionnement


Lalgorithme partition de la bibliothque standard permet de sparer les lments dun ensemble en deux sous-ensembles selon un critre donn. Les lments vriant ce critre sont placs en tte de lensemble, et les lments qui ne le vrient pas sont placs la n. Cet algorithme est dclar comme suit dans len-tte algorithm :
template <class ForwardIterator, class Predicate> ForwardIterator partition(ForwardIterator premier, ForwardIterator dernier, Predicate p);

Les paramtres qui doivent tre fournis cet algorithme sont les itrateurs rfrenant le premier et le dernier lment de lensemble partitionner, ainsi quun foncteur unaire permettant de dterminer si un lment vrie le critre de partitionnement ou non. La valeur retourne est la position de la sparation entre les deux sous-ensembles gnrs par lopration de partition. Exemple 18-33. Algorithme de partitionnement
#include <iostream> #include <functional> #include <algorithm> using namespace std; bool parity_even(int i) { return (i & 1) == 0; } int main(void) { int t[10] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}; // Partitionne le tableau en nombre pairs // et nombre impairs : partition(t, t+10, ptr_fun(&parity_even)); // Affiche le rsultat : int i;

492

Chapitre 18. Les algorithmes


for (i=0; i<10; ++i) cout << t[i] << " "; cout << endl; return 0; }

La complexit de lalgorithme partition est linaire en fonction du nombre dlments de lensemble partitionner. Cependant, lopration de partitionnement nest pas stable, cest--dire que lordre relatif des lments de mme valeur et sur lesquels le prdicat du critre de partitionnement donne le mme rsultat nest pas conserv. La bibliothque standard fournit donc un autre algorithme, stable celui-l, mais qui sexcute avec une complexit lgrement suprieure. Il sagit de lalgorithme stable_partition, qui est dclar comme suit dans len-tte algorithm :
template <class ForwardIterator, class Predicate> ForwardIterator stable_partition(ForwardIterator premier, ForwardIterator dernier, Predicate p);

Comme vous pouvez le constater, cet algorithme sutilise exactement de la mme manire que lalgorithme partition. Toutefois, il garantit lordre relatif des lments au sein des sous-ensembles gnrs par lopration de partitionnement. La complexit de cet algorithme est n sil dispose de sufsamment de mmoire, et nln(n) dans le cas contraire (n tant la taille de lensemble partitionner).

493

Chapitre 18. Les algorithmes

494

Chapitre 19. Conclusion


Pour terminer, je rappellerai les principales rgles pour raliser de bons programmes. Sans organisation, aucun langage, aussi puissant soit-il, ne peut garantir le succs dun projet. Voici donc quelques conseils :

commentez votre code, mais ne tuez pas le commentaire en en mettant l o les oprations sont vraiment trs simples ou dcrites dans un document externe. Marquez les rfrences aux documents externes dans les commentaires ; analysez le problme avant de commencer la programmation. Cela comprend plusieurs tapes. La premire est de rchir aux structures de donnes utiliser et aux oprations quon va leur appliquer (il faut donc identier les classes). Il faut ensuite tablir les relations entre les classes ainsi identies et leurs communications. Pour cela, on pourra faire des diagrammes dvnements qui identient les diffrentes tapes du processus permettant de traiter une donne. Enn, on dcrira chacune des mthodes des classes fonctionnellement, an de savoir exactement quelles sont leurs entres et les domaines de validit de celles-ci, leurs sorties, leurs effets de bords et les oprations effectues. Enn seulement on passera au codage. Si le codage implique de corriger les rsultats des tapes prcdentes, cest que la conception a t incorrecte ou incomplte : il vaut mieux retourner en phase de conception un peu pour voir limpact des modications faire. Cela permet de ne pas passer cot dun effet de bord inattendu, et donc dviter de perdre du temps dans la phase de mise au point ; ne considrez aucun projet, mme un petit projet ou un projet personnel, comme un projet qui chappe ces rgles. Si vous devez interrompre le dveloppement dun projet pour une raison quelconque, vous serez content de retrouver le maximum dinformations sur lui. Il en est de mme si vous dsirez amliorer un ancien projet. Et si la conception a t bien faite, cette amlioration ne sera pas une verrue sur lancienne version du logiciel, contrairement ce qui se passe trop souvent.

Voil. Vous connaissez prsent la plupart des fonctionnalits du C++. Jespre que la lecture de ce cours vous aura t utile et agrable. Si vous voulez en savoir plus, consultez les Draft Papers, mais sachez quils sont rellement difciles lire. Ils ne peuvent vraiment pas tre pris pour un support de cours. Vous trouverez en annexe la description de lorganisation gnrale de ce document, plus quelques renseignements pour faciliter leur lecture. Bonne continuation...

495

Chapitre 19. Conclusion

496

Annexe A. Liste des mots cls du C/C++


La liste des mots cls du C/C++ est donne dans le tableau ci-dessous. Tableau A-1. Mots cls du langange Mots cls
and bitor char continue dynamic_cast extern goto mutable operator public signed switch try unsigned wchar_t and_eq bool class default else false if namespace or register sizeof template typedef using while asm break compl delete enum float inline new or_eq auto case const do explicit for int not private bitand catch const_cast double export friend long not_eq protected short struct true union volatile

reinterpret_cast return static this typeid virtual xor static_cast throw typename void xor_eq

497

Annexe A. Liste des mots cls du C/C++

498

Annexe B. Priorits des oprateurs


Cette annexe donne la priorit des oprateurs du langage C++, dans lordre dcroissant. Cette priorit intervient dans lanalyse de toute expression et dans la dtermination de son sens. Cependant, lanalyse des expressions peut tre modie en changeant les priorits laide de parenthses. Tableau B-1. Oprateurs du langage Oprateur
:: [] () type() . -> ++ -new new[] delete delete[] ++ -* & + ! ~ sizeof sizeof typeid (type) const_cast dynamic_cast reinterpret_cast static_cast .* ->* * / %

Nom ou signication Oprateur de rsolution de porte Oprateur daccs aux lments de tableau Oprateur dappel de fonction Oprateur de transtypage explicite Oprateur de slection de membre Oprateur de slection de membre par drfrencement Oprateur dincrmentation post-xe Oprateur de dcrmentation post-xe Oprateur de cration dynamique dobjets Oprateur de cration dynamique de tableaux Oprateur de destruction des objets crs dynamiquement Oprateur de destruction des tableaux crs dynamiquement Oprateur dincrmentation prxe Oprateur de dcrmentation prxe Oprateur de drfrencement Oprateur dadresse Oprateur plus unaire Oprateur ngation unaire Oprateur de ngation logique Oprateur de complment un Oprateur de taille dobjet Oprateur de taille de type Oprateur didentication de type Oprateur de transtypage Oprateur de transtypage de constance Oprateur de transtypage dynamique Oprateur de rinterprtation Oprateur de transtypage statique Oprateur de slection de membre par pointeur sur membre Oprateur de slection de membre par pointeur sur membre par drfrencement Oprateur de multiplication Oprateur de division Oprateur de reste de la division entire

499

Annexe B. Priorits des oprateurs Oprateur


+ << >> < > <= >= == != & ^ | && || ?: = *= /= %= += -= <<= >>= &= |= ^= ,

Nom ou signication Oprateur daddition Oprateur de soustraction Oprateur de dcalage gauche Oprateur de dcalage droite Oprateur dinfriorit Oprateur de supriorit Oprateur dinfriorit ou dgalit Oprateur de supriorit ou dgalit Oprateur dgalit Oprateur dingalit Oprateur et binaire Oprateur ou exclusif binaire Oprateur ou inclusif binaire Oprateur et logique Oprateur ou logique Oprateur ternaire Oprateur daffectation Oprateur de multiplication et daffectation Oprateur de division et daffectation Oprateur de modulo et daffectation Oprateur daddition et daffectation Oprateur de soustraction et daffectation Oprateur de dcalage gauche et daffectation Oprateur de dcalage droite et daffectation Oprateur de et binaire et daffectation Oprateur de ou inclusif binaire et daffectation Oprateur de ou exclusif binaire et daffectation Oprateur virgule

500

Annexe C. Draft Papers


Les Draft Papers sont vraiment une source dinformations trs prcise, mais ils ne sont pas vraiment structurs. En fait, ils ne sont destins quaux diteurs de logiciels dsirant raliser un compilateur, et la structure du document ressemble un texte de loi (fortement technique en prime). Les exemples y sont rares, et quand il y en a, on ne sait pas quel paragraphe ils se rfrent. Enn, nombre de termes non dnis sont utiliss, et il faut lire le document pendant quelques 40 pages avant de commencer le comprendre. An de faciliter leur lecture, je donne ici quelques dnitions, ainsi que la structure des Draft Papers. Les Draft Papers sont constitus de deux grandes parties. La premire traite du langage, de sa syntaxe et de sa smantique. La deuxime partie dcrit la bibliothque standard C++. La syntaxe est dcrite dans la premire partie de la manire BNF. Il vaut mieux tre familiaris avec cette forme de description pour la comprendre. Cela ne causera pas de problme cependant si lon matrise dj la syntaxe du C++. Lors de la lecture de la deuxime partie, on ne sattardera pas trop sur les fonctionnalits de gestion des langues et des jeux de caractres (locales). Elles ne sont pas ncessaires la comprhension de la bibliothque standard. Une fois les grands principes de la bibliothque assimils, les notions de locale pourront tre approfondies. Les termes suivants sont souvent utiliss et non dnis (ou dnis au milieu du document dune manire peu claire). Leurs dnitions pourront tre dun grand secours lors de lecture de la premire partie des Draft Papers :

cv, cv qualied : labrviation cv signie ici const ou volatile. Ce sont donc les proprits de constance et de volatilit ; un agrgat est un tableau ou une classe qui na pas de constructeurs, pas de fonctions virtuelles, et pas de donne non statique private ou protected ; POD : cette abrviation signie plain ol data, ce qui nest pas comprhensible a priori. En fait, un type POD est un type relativement simple, pour lequel aucun traitement particulier nest ncessaire (pas de constructeur, pas de virtualit, etc.). La dnition des types POD est rcursive : une structure ou une union est un type POD si cest un agrgat qui ne contient pas de pointeur sur un membre non statique, pas de rfrence, pas de type non POD, pas de constructeur de copie et pas de destructeur.

Les autres termes sont dnis lorsquils apparaissent pour la premire fois dans le document.

501

Annexe C. Draft Papers

502

BIBLIOGRAPHIE
Langage C
C as a Second Language For Native Speakers of Pascal, Mldner and Steele, Addison-Wesley. The C Programming Language, Brian W. Kernigham and Dennis M. Ritchie, Prentice Hall.

Langage C++
Lessentiel du C++, Stanley B. Lippman, Addison-Wesley. The C++ Programming Language, Bjarne Stroustrup, Addison-Wesley. Working Paper for Draft Proposed International Standard for Information Systems -- Programming Language C++ (http://casteyde.christian.free.fr/cpp/cours/drafts/index.html), ISO.

Bibliothque C / appels systmes POSIX et algorithmique


Programmation systme en C sous Linux, Christophe Blaess, Eyrolles. The Single UNIX Specication, Version 3 (http://www.unix.org/single_unix_specication/), The Open Group. Introduction lalgorithmique, Thomas Cormen, Charles Leiserson, et Ronald Rivest, Dunod.

503

BIBLIOGRAPHIE

504

También podría gustarte