Académique Documents
Professionnel Documents
Culture Documents
Cours - C++ & C
Cours - C++ & C
Christian Casteyde
Copyright (c) 2001 Christian Casteyde Permission is granted to copy, distribute and/or modify this document under the terms of the GNU Free Documentation License, Version 1.1 or any later version published by the Free Software Foundation; with no Invariant Sections, with no Front-Cover Texts, and with no Back-Cover Texts. A copy of the license is included in the section entitled "GNU Free Documentation License".
Copyright (c) 2001 Christian Casteyde Permission vous est donne de copier, distribuer et modier ce document selon les termes de la licence GNU pour les documentations libres, version 1.1 ou toute autre version ultrieure publie par la Free Software Foundation. Une copie de cette licence est incluse dans lannexe intitule "GNU Free Documentation License".
Version 1.39.105/03/2001 Revised by: CC Description des types de donnes complmentaires de la librairie standard C++. Correction du comportement du bloc catch des con Version 1.39.004/02/2001 Revised by: CC Mise en conformit des en-ttes C++ des exemples avec la norme. Correction des exemples utilisant des noms rservs par la librai Version 1.38.114/10/2000 Revised by: CC Prcisions sur les classes de base virtuelles. Corrections orthographiques. Version 1.38.001/10/2000 Revised by: CC Corrections typographiques. Prcisions sur les oprateurs & et *. Version 1.37 23/08/2000 Revised by: CC Passage au format de chier SGML. Ajout des liens hypertextes. Corrections mineures. Version 1.36 27/07/2000 Revised by: CC Complment sur les parenthses dans les dnitions de macros. Corrections sur la numrotation des paragraphes. Version 1.35 10/07/2000 Revised by: CC Corrections sur les dclarations using. Version 1.34 09/07/2000 Revised by: CC Passage en licence FDL. Ajout de la table des matires. Version 1.33 22/60/2000 Revised by: CC Correction dune erreur dans le paragraphe sur les paramtres template template. Corrections orthographiques diverses. Version 1.32 17/06/2000/Revised by: 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 Revised by: CC Corrections mineurs. Ajout du paragraphe sur la spcialisation dune fonction membre dune classe template. Version 1.30 05/12/1999 Revised by: CC Ajout de la licence. Modications mineures du formatage. Version <1.30<1998 Revised by: CC Version initiale.
3.3. Drfrencement, indirection ................................................................................... 56 3.4. Notion de rfrence .................................................................................................. 57 3.5. Lien entre les pointeurs et les rfrences.................................................................. 58 3.6. Passage de paramtres par variable ou par valeur .................................................... 59 3.6.1. Passage par valeur........................................................................................ 59 3.6.2. Passage par variable ..................................................................................... 60 3.6.3. Avantages et inconvnients des deux mthodes........................................... 60 3.6.4. Comment passer les paramtres par variable en C ?.................................... 61 3.6.5. Passage de paramtres par rfrence............................................................ 61 3.7. Arithmtique des pointeurs....................................................................................... 62 3.8. Utilisation des pointeurs avec les tableaux............................................................... 63 3.8.1. Conversions des tableaux en pointeurs ........................................................ 64 3.8.2. Paramtres de fonction de type tableau ....................................................... 65 3.9. Rfrences et pointeurs constants et volatiles .......................................................... 66 3.10. Les chanes de caractres : pointeurs et tableaux la fois ! ................................... 70 3.11. Allocation dynamique de mmoire ........................................................................ 70 3.11.1. Allocation dynamique de mmoire en C ................................................... 71 3.11.2. Allocation dynamique en C++................................................................... 72 3.12. Pointeurs et rfrences de fonctions ....................................................................... 74 3.12.1. Pointeurs de fonctions................................................................................ 74 3.12.2. Rfrences de fonctions ............................................................................. 77 3.13. Paramtres de la fonction main - ligne de commande............................................ 77 3.14. DANGER................................................................................................................ 78 4. Comment faire du code illisible ? ......................................................................................... 80 4.1. De nouveaux oprateurs ........................................................................................... 80 4.2. Quelques conseils ..................................................................................................... 81 5. Le prprocesseur C................................................................................................................ 83 5.1. Dnition.................................................................................................................. 83 5.2. Les commandes du prprocesseur ............................................................................ 83 5.2.1. Inclusion de chier....................................................................................... 83 5.2.2. Remplacement de texte ................................................................................ 84 5.2.3. Dnition dun identicateur....................................................................... 85 5.2.4. Suppression de texte .................................................................................... 85 5.2.5. Autres commandes....................................................................................... 86 5.3. Les macros................................................................................................................ 86 5.4. Manipulation de chanes de caractres dans les macros........................................... 89 5.5. Les trigraphes ........................................................................................................... 90 6. Modularit ............................................................................................................................. 91 6.1. Pourquoi faire une programmation modulaire ?....................................................... 91 6.2. tapes impliques dans la gnration dun excutable ............................................ 91 6.3. Compilation spare en C/C++ ................................................................................ 92 6.4. Syntaxe des outils de compilation ............................................................................ 93 6.4.1. Syntaxe des compilateurs............................................................................. 93 6.4.2. Syntaxe de make .......................................................................................... 94
6.5. Problmes syntaxiques relatifs la compilation spare ......................................... 95 6.5.1. Dclaration des types ................................................................................... 96 6.5.2. Dclaration des variables ............................................................................. 96 6.5.3. Dclaration des fonctions............................................................................. 96 6.5.4. Directive ddition de liens .......................................................................... 97 7. C++ : la couche objet ............................................................................................................ 98 7.1. Gnralits................................................................................................................ 98 7.2. Extension de la notion de type du C......................................................................... 99 7.3. Dclaration de classes en C++.................................................................................. 99 7.4. Encapsulation des donnes ..................................................................................... 104 7.5. Hritage .................................................................................................................. 105 7.6. Classes virtuelles .................................................................................................... 108 7.7. Fonctions et classes amies ...................................................................................... 110 7.7.1. Fonctions amies ......................................................................................... 110 7.7.2. Classes amies ............................................................................................. 111 7.8. Constructeurs et destructeurs.................................................................................. 112 7.8.1. Dclaration des constructeurs et des destructeurs...................................... 112 7.8.2. Constructeurs de copie............................................................................... 116 7.8.3. Utilisation des constructeurs dans les transtypages ................................... 117 7.9. Pointeur this............................................................................................................ 118 7.10. Donnes et fonctions membres statiques.............................................................. 120 7.10.1. Donnes membres statiques..................................................................... 120 7.10.2. Fonctions membres statiques ................................................................... 121 7.11. Rednition des oprateurs .................................................................................. 122 7.11.1. Dnition des oprateurs interne ............................................................. 123 7.11.2. Surcharge des oprateurs externes ........................................................... 125 7.11.3. Oprateurs dincrmentation et de dcrmentation ................................. 127 7.11.4. Oprateurs dallocation dynamique de mmoire ..................................... 128 7.11.5. Oprateurs de transtypage........................................................................ 135 7.11.6. Oprateurs de comparaison...................................................................... 136 7.11.7. Oprateur fonctionnel .............................................................................. 136 7.11.8. Oprateurs dindirection et de drfrencement ...................................... 139 7.12. Des entres - sorties simplies ........................................................................... 140 7.13. Mthodes virtuelles .............................................................................................. 141 7.14. Drivation ............................................................................................................. 143 7.15. Mthodes virtuelles pures - Classes abstraites ..................................................... 146 7.16. Pointeurs sur les membres dune classe ............................................................... 152 8. Les exceptions en C++ ........................................................................................................ 155 8.1. Lancement et rcupration dune exception........................................................... 156 8.2. Remonte des exceptions........................................................................................ 159 8.3. Liste des exceptions autorises pour une fonction ................................................. 160 8.4. Hirarchie des exceptions....................................................................................... 162 8.5. Exceptions dans les constructeurs .......................................................................... 164 9. Identication dynamique des types..................................................................................... 168
9.1. Identication dynamique des types ........................................................................ 168 9.1.1. Loprateur typeid ...................................................................................... 168 9.1.2. La classe type_info .................................................................................... 170 9.2. Transtypages C++................................................................................................... 171 9.2.1. Transtypage dynamique ............................................................................. 171 9.2.2. Transtypage statique .................................................................................. 174 9.2.3. Transtypage de constance et de volatilit................................................... 174 9.2.4. Rinterprtation des donnes ..................................................................... 175 10. Espaces de nommage ........................................................................................................ 176 10.1. Dnition des espaces de nommage..................................................................... 176 10.1.1. Espaces de nommage nommes............................................................... 176 10.1.2. Espaces de nommage anonymes .............................................................. 178 10.1.3. Alias despaces de nommage ................................................................... 179 10.2. Dclaration using.................................................................................................. 179 10.2.1. Syntaxe des dclarations using ................................................................ 180 10.2.2. Utilisation des dclarations using dans les classes .................................. 182 10.3. Directive using...................................................................................................... 183 11. Les template ...................................................................................................................... 186 11.1. Gnralits............................................................................................................ 186 11.2. Dclaration des paramtres template.................................................................... 186 11.2.1. Dclaration des types template ................................................................ 186 11.2.2. Dclaration des constantes template ........................................................ 188 11.3. Fonctions et classes template................................................................................ 189 11.3.1. Fonctions template ................................................................................... 189 11.3.2. Les classes template................................................................................. 190 11.3.3. Fonctions membres template ................................................................... 193 11.4. Instanciation des template .................................................................................... 196 11.4.1. Instanciation implicite.............................................................................. 196 11.4.2. Instanciation explicite .............................................................................. 198 11.4.3. Problmes soulevs par linstanciation des template............................... 199 11.5. Spcialisation des template................................................................................... 200 11.5.1. Spcialisation totale ................................................................................. 200 11.5.2. Spcialisation partielle ............................................................................. 201 11.5.3. Spcialisation dune mthode dune classe template............................... 203 11.6. Mot-cl typename................................................................................................. 204 11.7. Fonctions exportes .............................................................................................. 205 II. La librairie standard C++......................................................................................................... 207 12. Services et notions de base de la librairie standard........................................................... 209 12.1. Encapsulation de la librairie C standard............................................................... 209 12.2. Dnition des exceptions standard ....................................................................... 212 12.3. Abstraction des types de donnes : les traits ........................................................ 214 12.4. Abstraction des pointeurs : les itrateurs.............................................................. 217 12.4.1. Notions de base et dnition.................................................................... 217 12.4.2. Classication des itrateurs...................................................................... 218
12.4.3. Itrateurs adaptateurs ............................................................................... 221 12.4.3.1. Adaptateurs pour les ux dentre / sortie standard .................... 221 12.4.3.2. Adaptateurs pour linsertion dlments dans les conteneurs ..... 224 12.4.3.3. Itrateur inverse pour les itrateurs bidirectionnels..................... 227 12.5. Abstraction des fonctions : les foncteurs.............................................................. 229 12.5.1. Foncteurs prdnis ................................................................................. 230 12.5.2. Prdicats et foncteurs doprateurs logiques............................................ 235 12.5.3. Foncteurs rducteurs ................................................................................ 236 12.6. Gestion personnalise de la mmoire : les allocateurs ......................................... 238 13. Les types complmentaires ............................................................................................... 244 13.1. Les chanes de caractres...................................................................................... 244 13.1.1. Construction et initialisation dune chane .............................................. 249 13.1.2. Accs aux proprits dune chane .......................................................... 250 13.1.3. Modication de la taille des chanes........................................................ 251 13.1.4. Accs aux donnes de la chane de caractres ......................................... 252 13.1.5. Oprations sur les chanes........................................................................ 255 13.1.5.1. Affectation et concatnation de chanes de caractres ................ 255 13.1.5.2. Extraction de donnes dune chane de caractres ...................... 257 13.1.5.3. Insertion et suppression de caractres dans une chane............... 258 13.1.5.4. Remplacements de caractres dune chane ................................ 260 13.1.6. Comparaison de chanes de caractres..................................................... 261 13.1.7. Recherche dans les chanes...................................................................... 262 13.1.8. Fonctions dentre / sortie des chanes de caractres............................... 265 13.2. Les pointeurs auto................................................................................................. 266 13.3. Les complexes ...................................................................................................... 270 13.3.1. Dnition et principales proprits des nombres complexes................... 270 13.3.2. La classe complex .................................................................................... 272 13.4. Les tableaux de valeurs......................................................................................... 275 13.4.1. Fonctionnalits de base des valarray........................................................ 277 13.4.2. Slection multiple des lments dun valarray ........................................ 282 13.4.2.1. Slection par un masque.............................................................. 282 13.4.2.2. Slection par indexation explicite ............................................... 283 13.4.2.3. Slection par indexation implicite............................................... 284 13.4.2.4. Oprations ralisables sur les slections multiples ..................... 287 14. Les ux dentre / sortie.................................................................................................... 289 14.1. Notions de base..................................................................................................... 289 14.2. Les tampons.......................................................................................................... 289 14.3. Les classes de base : ios_base et basic_ios........................................................... 289 14.4. Flux dentre......................................................................................................... 289 14.5. Flux de sortie ........................................................................................................ 289 14.6. Flux dentre / sortie............................................................................................. 289 15. Les locales......................................................................................................................... 290 16. Les conteneurs................................................................................................................... 291 17. Les algorithmes ................................................................................................................. 292
18. Conclusion ................................................................................................................................. 293 A. Priorits des oprateurs............................................................................................................. 294 B. Draft Papers................................................................................................................................ 297 C. GNU Free Documentation License........................................................................................... 298 BIBLIOGRAPHIE ......................................................................................................................... 304
2-14. Dnition de type simple .......................................................................................................... 49 2-15. Dnition de type tableau.......................................................................................................... 50 2-16. Dnition de type structure ....................................................................................................... 50 2-17. Transtypage en C....................................................................................................................... 51 2-18. Dclaration dune variable locale statique ................................................................................ 52 2-19. Dclaration dune variable constante ........................................................................................ 53 2-20. Dclaration de constante externes ............................................................................................. 53 2-21. Utilisation du mot-cl mutable .................................................................................................. 54 3-1. Dclaration de pointeurs.............................................................................................................. 57 3-2. Utilisation de pointeurs de structures .......................................................................................... 57 3-3. Dclaration de rfrences ............................................................................................................ 58 3-4. Passage de paramtre par valeur.................................................................................................. 59 3-5. Passage de paramtre par variable en Pascal............................................................................... 60 3-6. Passage de paramtre par variable en C ...................................................................................... 61 3-7. Passage de paramtre par rfrence en C++................................................................................ 62 3-8. Arithmtique des pointeurs ......................................................................................................... 63 3-9. Accs aux lments dun tableau par pointeurs .......................................................................... 64 3-10. Passage de tableau en paramtre ............................................................................................... 65 3-11. Passage de paramtres constant par rfrence........................................................................... 69 3-12. Cration dun objet temporaire lors dun passage par rfrence............................................... 69 3-13. Allocation dynamique de mmoire en C................................................................................... 72 3-14. Dclaration de pointeur de fonction .......................................................................................... 74 3-15. Drfrencement de pointeur de fonction ................................................................................. 75 3-16. Application des pointeurs de fonctions ..................................................................................... 76 3-17. Rcupration de la ligne de commande..................................................................................... 78 4-1. Programme parfaitement illisible ................................................................................................ 81 5-1. Dnition de constantes .............................................................................................................. 84 5-2. Macros MIN et MAX .................................................................................................................. 87 6-1. Compilation dun chier et dition de liens ................................................................................ 94 6-2. Fichier makele sans dpendances.............................................................................................. 95 6-3. Fichier makele avec dpendances ............................................................................................. 95 6-4. Dclarations utilisables en C et en C++ ...................................................................................... 97 7-1. Dclaration de mthode de classe ............................................................................................. 100 7-3. Utilisation des champs dune classe dans une de ses mthodes................................................ 102 7-4. Utilisation du mot-cl class ....................................................................................................... 105 7-5. Hritage public, priv et protg ............................................................................................... 106 7-6. Oprateur de rsolution de porte et membre de classes de base.............................................. 108 7-7. Classes virtuelles ....................................................................................................................... 109 7-8. Fonctions amies ......................................................................................................................... 110 7-9. Classe amie................................................................................................................................ 111 7-10. Constructeurs et destructeurs................................................................................................... 113 7-11. Appel du constructeur des classes de base .............................................................................. 114 7-12. Mot-cl explicit ....................................................................................................................... 118 7-13. Donne membre statique ......................................................................................................... 120
10
7-14. Fonction membre statique ....................................................................................................... 121 7-15. Appel de fonction membre statique......................................................................................... 122 7-16. Rednition des oprateurs ..................................................................................................... 123 7-17. Surcharge doprateur externe................................................................................................. 126 7-18. Oprateurs dincrmentation et de dcrmentation................................................................. 127 7-19. Dtermination de la taille de len-tte des tableaux ................................................................ 129 7-20. Oprateurs new avec placement .............................................................................................. 130 7-21. Utilisation de new sans exception ........................................................................................... 135 7-22. Implmentation de la classe matrice ....................................................................................... 136 7-23. Oprateur de drfrencement et dindirection ....................................................................... 139 7-24. Flux dentre / sortie cin et cout.............................................................................................. 140 7-25. Surcharge de mthode de classe de base ................................................................................. 142 7-26. Conteneur dobjets polymorphiques ....................................................................................... 147 7-27. Pointeurs sur membres statiques ............................................................................................. 153 8-1. Utilisation des exceptions.......................................................................................................... 157 8-2. Installation dun gestionnaire dexception avec set_terminate.................................................. 160 8-3. Gestion de la liste des exceptions autorises............................................................................. 161 8-4. Classication des exceptions..................................................................................................... 163 8-5. Exceptions dans les constructeurs ............................................................................................. 165 9-1. Oprateur typeid ........................................................................................................................ 168 9-2. Oprateur dynamic_cast ............................................................................................................ 173 10-1. Extension de namespace.......................................................................................................... 176 10-2. Accs aux membres dun namespace...................................................................................... 177 10-3. Dnition externe dune fonction de namespace .................................................................... 177 10-4. Dnition de namespace dans un namespace.......................................................................... 178 10-5. Dnition de namespace anonyme.......................................................................................... 178 10-6. Ambiguts entre namespaces ................................................................................................. 178 10-7. Dclaration using..................................................................................................................... 180 10-8. Dclarations using multiples ................................................................................................... 180 10-9. Extension de namespace aprs une dclaration using ............................................................. 181 10-10. Conit entre dclarations using et identicateurs locaux...................................................... 181 10-11. Dclaration using dans une classe ......................................................................................... 182 10-12. Rtablissement de droits daccs laide dune directive using ........................................... 183 10-13. Directive using....................................................................................................................... 184 10-14. Extension de namespace aprs une directive using ............................................................... 184 10-15. Conit entre directive using et identicateurs locaux ........................................................... 185 11-1. Dclaration de paramtres template ........................................................................................ 187 11-2. Dclaration de paramtre template template ........................................................................... 187 11-3. Dclaration de paramtres template de type constante ........................................................... 188 11-4. Dnition de fonction template............................................................................................... 189 11-5. Dnition dune pile template................................................................................................. 190 11-6. Fonction membre template ...................................................................................................... 193 11-7. Fonction membre template dune classe template .................................................................. 194 11-8. Fonction membre template et fonction membre virtuelle ....................................................... 195
11
11-9. Surcharge de fonction membre par une fonction membre template........................................ 195 11-10. Instanciation implicite de fonction template ......................................................................... 197 11-11. Instanciation explicite de classe template ............................................................................. 199 11-12. Spcialisation totale............................................................................................................... 201 11-13. Spcialisation partielle .......................................................................................................... 201 11-14. Spcialisation de fonction membre de classe template ......................................................... 203 11-15. Mot-cl typename.................................................................................................................. 205 11-16. Mot-cl export ....................................................................................................................... 206 12-1. Dtermination des limites dun type ....................................................................................... 211 12-2. Itrateurs de ux dentre........................................................................................................ 222 12-3. Itrateur de ux de sortie......................................................................................................... 223 12-4. Itrateur dinsertion ................................................................................................................. 226 12-5. Utilisation dun itrateur inverse ............................................................................................. 229 12-6. Utilisation des foncteurs prdnis ......................................................................................... 232 12-7. Adaptateurs de fonctions ......................................................................................................... 233 12-8. Rduction de foncteurs binaires .............................................................................................. 238 12-9. Utilisation de lallocateur standard.......................................................................................... 241 13-1. Redimensionnement dune chane........................................................................................... 251 13-2. Rservation de mmoire dans une chane ............................................................................... 252 13-3. Accs direct aux donnes dune chane................................................................................... 254 13-4. Affectation de chane de caractres......................................................................................... 255 13-5. Concatnation de chanes de carctres .................................................................................... 256 13-6. Copie de travail des donnes dune basic_string..................................................................... 257 13-7. Extraction de sous-chane........................................................................................................ 258 13-8. Insertion de caractres dans une chane .................................................................................. 259 13-9. Suppression de caractres dans une chane ............................................................................. 259 13-10. Remplacement dune sous-chane dans une chane .............................................................. 260 13-11. change du contenu de deux chanes de caractres .............................................................. 260 13-12. Comparaisons de chanes de caractres................................................................................. 261 13-13. Recherches dans les chanes de caractres ............................................................................ 264 13-14. Lecture de lignes sur le ux dentre .................................................................................... 266 13-15. Utilisation des pointeurs automatiques.................................................................................. 267 13-16. Sortie dun pointeur dun auto_ptr ........................................................................................ 269 13-17. Manipulation des nombres complexes .................................................................................. 275 13-18. Modication de la taille dun valarray .................................................................................. 279 13-19. Oprations sur les valarray .................................................................................................... 280 13-20. Dcalages et rotations de valeurs .......................................................................................... 281 13-21. Slection des lments dun valarray par un masque............................................................ 283 13-22. Slection des lments dun valarray par indexation ............................................................ 284 13-23. Slection par indexation implicite ......................................................................................... 285
12
Avant-propos
Le prsent document 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 cours est structur en deux grandes parties, traitant chacune un des aspects du C++. La premire partie, contenant les chapitres 1 11, traite du langage C++ lui-mme, de sa syntaxe et de ses principales fonctionnalits. La deuxime partie quant elle se concentre sur la librairie standard C++, qui fournit un ensemble de fonctionnalits cohrentes et rutilisables par tous les programmeurs. La librairie 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 librairie standard stend du chapitre 12 au chapitre 17. Si la librairie standard C++ est dcrite en dtail, il nen va pas de mme pour les fonctions de la librairie C. Vous ne trouverez donc pas dans ce cours la description des fonctions classiques du C, ni celle les fonctions les plus courantes de la norme POSIX (telles que les fonctions de manipulation des chiers par exemple). 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 librairie C seront donc prsentes ici. Si vous dsirez plus de renseignements, reportez-vous la documentation des environnements de dveloppement, laide des kits de dveloppement des systmes dexploitation (SDK), et la bibliographie. Ce document a pour but de prsenter le langage C++ tel quil est dcrit par la norme ISO 14882 du langage C++. Cependant, bien que cette norme ait t publie en 1999, le texte ofciel nest pas librement disponible. Comme je ne veux pas cautionner le fait quun texte de norme internationnal ne soit pas accessible tous, je me suis rabattu sur le document du projet de normalisation du langage, datant du 2 dcembre 1996 et intitul Working Paper for Draft Proposed International Standard for Information Systems Programming Language C++ (http ://www.cygnus.com/misc/wp/dec96pub/). Je serai reconnaissant quiconque pourrait me procurer le texte ofciel de cette norme, an que je puisse mettre en conformit ce cours. Ceci ne rglerait toutefois pas le problme de la rtention dinformation pratique par les groupes de travail de lISO. 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 exactes 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 ce document dans une note. Le fait que les exemples de ce cours ne fonctionnent pas avec de tels compilateurs ne peut donc pas tre considr comme une erreur de ce document, mais plutt comme une non-conformit des outils utiliss, qui sera sans doute leve dans les versions ultrieures de ces produits. Aprs avoir tent de faire une prsentation rigoureuse du sujet, jai dcid darranger ce document dans un ordre plus pdagogique. Il est mon avis impossible de parler dun sujet un tant soit peu
13
Avant-propos
vaste dans un ordre purement mathmatique, cest dire un ordre o les notions sont introduites une une, partir des notions dj connues (chaque fonction, oprateur, etc. . . napparat pas avant sa dnition dans le document). Un tel plan ncessiterait de couper le texte en morceaux qui ne sont plus thmatiques. Jai donc pris la dcision de prsenter les choses par ordre logique, et non par ordre de ncessit syntaxique. Les consquences de ce choix sont les suivantes :
il faut admettre certaines choses, quitte les comprendre plus tard ; il faut lire deux fois ce document. Lors de la premire lecture, on voit lessentiel, et lors de la deuxime lecture, on comprend les dtails (de toutes manires, je flicite celui qui comprend toutes les subtilits du C++ du premier coup).
Enn, ce document nest pas une rfrence et contient certainement des erreurs. Toute remarque est donc la bienvenue. Je tcherai de corriger les erreurs que lon me signalera dans la mesure du possible, et dapporter les modications ncessaires si un point est obscur. En revanche, il est possible que les rclamations concernant la forme de ce document ne soient pas prises en compte, parce que jai des contraintes matrielles et logicielles que je ne peux pas viter. En particulier, je maintiens ce document sous un unique format, et je mefforce dassurer la portabilit du document sur diffrents traitements de texte. An de reconnatre les diffrentes ditions de ce document, un historique des rvisions a t inclus en premire page. La dernire version de ce document peut tre trouve sur mon site Web (http ://casteyde.christian.free.fr).
14
I. Le langage C++
Le C++ est 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 et illisible. Cette rputation est en partie justie. La complexit du langage est invitable lorsque lon cherche avoir beaucoup de fonctionnalits. En revanche, en ce qui concerne la lisibilit des programmes, tout dpend de la bonne volont du programmeur. Les caractristiques du C++ en font un langage idal pour certains types de projets. Il est incontournable dans la ralisation des grands programmes. Les optimisations des compilateurs actuels en font galement un langage de prdilection pour ceux qui recherchent les performances. Enn, ce langage est, avec le C, idal pour ceux qui doivent assurer la portabilit de leurs programmes au niveau des chiers sources (pas des excutables). Les principaux avantages du C++ sont les suivants :
grand nombre de fonctionnalits ; performances du C ; facilit dutilisation des langages objets ; portabilit des chiers sources ; facilit de conversion des programmes C en C++, et, en particulier, possibilit dutiliser toutes les fonctionnalits du langage C ; contrle derreurs accru.
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 avertissement en C soient des erreurs en C++. Quelques adaptations sont donc 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++. Tout le dbut de cette partie (chapitres 1 7) 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 compilable en C++. Ceci 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 considres comme des pratiques de programmation valables. Les chapitres suivants (chapitres 7 11) ne traitent que du C++. Le Chapitre 7 traite de la programmation oriente objet et de toutes les extensions qui ont t apportes au langage C pour grer les
objets. Le Chapitre 8 prsente le mcanisme dexceptions du langage, qui permet de grer les erreurs plus facilement. Lidentication dynamique des types sera dcrite dans le Chapitre 9. Le Chapitre 10 prsente la notion despace de nommage, que lon utilise an dviter les conits de noms entre les diffrentes parties dune grand projet. Enn, le Chapitre 11 dcrit les mcanisme des template, qui permettent dcrire des portions de code paramtres par des types de donnes ou par des valeurs constantes. Ces dernires notions sont utilises intensivement dans la librairie standard C++, aussi la lecture complte de la premire partie est-elle indispensable avant de sattaquer la deuxime. 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[...]])] ;
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 nale.
Rassurez-vous, il ny aura pratiquement jamais de syntaxe aussi complique. Je suis sincrement dsol de la complexit de cet exemple.
Ces diverses tapes peuvent tre disperses dans le programme. Par exemple, les entres peuvent se trouver dans le programme mme (lutilisateur na dans ce cas pas besoin de les saisir). Notez que ce processus peut tre rpt autant de fois que ncessaire pendant lexcution dun programme. Par exemple, les programmes graphiques traitent les vnements systme et graphiques au fur et mesure quils apparaissent. Les donnes quils reoivent sont fournies par le systme sont couramment appels des messages, et la boucle de traitement de ces donnes la boucle des messages. La sortie des donnes correspond dans ce cas au comportement que le programme graphique adopte en rponse aux messages quil reoit : ce peut tre afcher les donnes saisies, ou plus gnralement appliquer une commande aux donnes en cours de manipulation. Les donnes sont stockes dans des variables, cest dire des zones de la mmoire. Comme leur nom lindique, les variables peuvent tre modies (par le traitement des donnes). Des oprations peuvent tre effectues sur les variables, mais pas nimporte lesquelles. Par exemple, on ne peut pas ajouter des pommes et des bananes, sauf dnir cette opration bien prcisment. Les oprations dpendent donc de la nature des variables. An de rduire les risques derreurs de programmation, les langages comme le C/C++ donnent un type chaque variable (par exemple : pomme et banane). Lors de la compilation (phase de traduction du texte source du programme en excutable), ces types sont utiliss pour vrier si les oprations effectues sont autorises. Le programmeur peut videmment dnir ses propres types. Le langage fournit des types de base et des oprations prdnies sur ces types. Les oprations qui peuvent tre faites sont soit lapplication dun oprateur, soit lapplication dune fonction sur les variables. Logiquement parlant, il ny a pas de diffrence. Seule la syntaxe change :
a=2+3
17
videmment, des fonctions utilisateur peuvent tre dnies. Les oprateurs ne peuvent tre que rednis : il est impossible den dnir de nouveaux (de plus, la rednition des oprateurs nest faisable quen C++). Cette premire partie est donc consacre la dnition des types, la dclaration des variables, la construction et lappel de fonctions, et aux entres-sorties de base (clavier et cran).
Ces commentaires peuvent stendre sur plusieurs lignes. En revanche, les commentaires de n de lignes sarrtent la n de la ligne courante, et pas avant. Ils permettent de commenter plus facilement les actions effectues sur la ligne courante, avant le commentaire. Les commentaires de n de ligne commencent par la squence constitue de deux barres obliques (ils nont pas de squence de terminaison, puisquils ne se terminent qu la n de la ligne courante). Par exemple : Exemple 1-2. Commentaire C++
action quelconque action suivante // Ceci est un commentaire C++
le type vide : void. Ce type est utilis pour spcier le fait quil ny a pas de type. Ceci a une utilit pour faire des procdures (fonctions ne renvoyant rien) et les pointeurs sur des donnes non types (voir plus loin) ;
18
les boolens : bool, qui peuvent prendre les valeurs true et false (en C++ uniquement, ils nexistent pas en C) ; les caractres : char ; les caractres longs : wchar_t (en C++ seulement, ils nexistent pas en C) ; les entiers : int ; les rels : oat ; les rels en double prcision : double ; les tableaux une dimension, dont les indices sont spcis par des crochets ([ et ]). Pour les tableaux de dimension suprieure ou gale 2, on utilisera des tableaux de tableaux ; les structures, unions et numrations (voir plus loin).
Les types entiers (int) peuvent tre caractriss dun des mots-cls long ou short. Ces mots-cls permettent de modier la taille du type, cest dire la plage de valeurs quils peuvent couvrir. De mme, les rels en double prcision peuvent tre qualis du mot-cl long, ce qui augmente leur plage de valeurs. On ne peut pas utiliser le mot-cl short avec les double. On dispose donc de types additionnels :
les entiers longs : long int, ou long (int est facultatif) ; les entiers courts : short int, ou short ; les rels en quadruple prcision : long double.
La taille des types nest spcie dans aucune norme, sauf pour le type char. En revanche, les ingalits suivantes sont toujours vries :
char short int int long int float double long double
o loprateur <= signie ici a une plage de valeur plus petite ou gale que . La taille des caractres de type char est toujours de un octet. Les types char et int peuvent tre signs ou non. Un nombre sign peut tre ngatif, pas un nombre non sign. Lorsquun nombre est sign, la valeur absolue du plus grand nombre reprsentable est plus petite. Par dfaut, un nombre est sign (sauf les type char et wchar_t, qui peuvent tre soit signs, soit non signs, selon le compilateur utilis). Pour prciser quun nombre nest pas sign, il faut utiliser le mot-cl unsigned. Pour prciser quun nombre est sign, on peut utiliser le mot-cl signed. Ces mots-cls peuvent tre intervertis librement avec les mots-cls long et short. Exemple 1-3. Types signs et non signs
unsigned char signed char unsigned int signed int unsigned long int
19
Les valeurs accessibles avec les nombres signs ne sont pas les mmes que celles accessibles avec les nombres non signs. En effet, un bit est utilis pour le signe dans les nombres signs. Par exemple, puisque le type char est cod sur 8 bits, on peut coder les nombres allant de 0 256 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.). Le type int doit tre capable de reprsenter les entiers utiliss par la machine sur laquelle le programme tournera. Par exemple, sur les machines 16 bits ils sont cods sur 16 bits (les valeurs accessibles vont donc de -32768 32768, 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 95 ou NT, DOS Extender, Linux), sur la plupart des machines UNIX et sur les Macintosh. Sur les machines 64 bits, le type int est 64 bits (DEC Alpha par exemple). On constate donc que la portabilit des types de base est trs alatoire. En pratique cependant, ils ont souvent la mme taille pour toutes les machines 32 bits (la majorit). Sur ces machines, les entiers longs sont cods la plupart du temps sur 32 bits et les entiers courts sur 16 bits. Les caractres sont souvent cods sur 8 bits. Le type wchar_t est quivalent lun des types entiers, il est souvent cod sur 16 bits. Enn, le type oat est gnralement cod sur 4 octets et les types double et long double sont identiques et cods sur 8 octets. Le C++ (et le C++ uniquement) considre le type char comme le type de base des caractres. Les caractres nont pas de notion de signe associe. Cependant, les caractres peuvent tre considrs comme des entiers tout instant, mais il nest pas prcis si ce type est sign ou non. Ceci dpend du compilateur. Linterprtation du type char en tant que type intgral nest pas le comportement de base du C++, par consquent, le langage distingue les versions signes et non signes de ce type de la version dont le signe nest pas spci. Ceci signie que le compilateur traite les types char, unsigned char et signed char comme des types diffrents. Cette distinction na pas lieu dtre au niveau des plages de valeurs si lon connat le signe du type char, mais elle est trs importante dans la dtermination de la signature des fonctions (la signature des fonctions sera dnie plus loin dans ce cours). Si lon veut faire du code portable (cest dire qui compilera et fonctionnera sans modication du programme sur tous les ordinateurs), il faut utiliser des types de donnes qui donneront les mmes intervalles de valeurs sur tous les ordinateurs. Il est donc recommand de dnir ses propres types (par exemple int8, int16, int32) dont la taille et le signe seront xe. Lorsque le programme devra tre port, seule la dnition de ces types sera changer, pas le programme. En pratique, si lon veut faire du code portable entre les machines 16 bits et les machines 32 bits, on ne devra pas utiliser le type int seul : il faudra toujours indiquer la taille de lentier utilis : short (16 bits) ou long (32 bits).
20
base 10 (dcimale) : avec les chiffres de 0 9, et les signes + (facultatif) et -. Exemple 1-4. Notation des entiers en base 10
12354, -2564
base 16 (hexadcimale) : avec les chiffres 0 9 et A F ou a f (A=a=10, B=b=11, . . . F=f=15). Les entiers nots en hexadcimal devront toujours tre prcds de 0x (qui indique la base). On ne peut pas utiliser le signe - avec les nombres hexadcimaux. Exemple 1-5. Notation des entiers en base 16
0x1AE
base 8 (octale) : avec les chiffres de 0 7. Les nombres octaux doivent tre prcds dun 0 (qui indique la base). Le signe - ne peut pas tre utilis. Exemple 1-6. Notation des entiers en base 8
01, 0154
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. 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-7. Notation des rels
-123.56, 12e-12, 2
2 est entier, 2. est rel. Les caractres se notent entre guillemets simples :
A, c, (
21
On peut donner un caractre non accessible au clavier en donnant son code en octal, prcd du caractre \. Par exemple, le caractre A peut aussi tre not \0101. Attention ne pas oublier le 0 du nombre octal. Il est aussi possible dutiliser certains caractres spciaux, dont les principaux sont :
\a \b \f \r \n \t \v Bip sonore Backspace Dbut de page suivante Retour la ligne (sans saut de ligne) Passage la ligne Tabulation Tabulation verticale
Dautres squences dchappement sont disponibles, an de pouvoir reprsenter les caractres ayant une signication particulire en C :
\\ \ Le caractre \ Le caractre
Note: Attention ! Il ny a pas de chanes de caractres. Les chanes de caractres sont en fait des tableaux de caractres. Cependant, on pourra crer des tableaux de caractres constants en donnant la chane entre doubles guillemets :
"Exemple de chane de caractre..."
Les caractres spciaux peuvent tre utiliss directement dans les chanes de caractres :
"Ceci est un saut de ligne :\nCeci est la ligne suivante."
Si une chane de caractre 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."
Note: Attention : il ne faut pas mettre de caractre nul dans une chane de caractres. Ce caractre est en effet le caractre de terminaison de toute chane de caractres. Vous trouverez plus loin pour de plus amples informations sur les chanes de caractres et les tableaux.
Enn, les versions longues des diffrents types cits prcdemment (wchar_t, long int et long double) peuvent tre notes en faisant prcder ou suivre la valeur de la lettre L. Cette lettre doit prcder la
22
valeur dans le cas des caractres et des chanes de caractres, et la suivre quand il sagit des entiers et des ottants. Exemple :
L"Ceci est une chane de wchar_t." 2.3e5L
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][...]] ;
Les variables peuvent tre dclares quasiment nimporte o dans le programme. Ceci permet de ne dclarer une variable temporaire que l o lon en a besoin.
Note: Ceci nest vrai quen C++. En C pur, on est oblig de dclarer les variables au dbut des fonctions ou des instructions composes (voir plus loin). Il faut donc connatre les variables temporaires ncessaires lcriture du morceau de code qui suit leur dclaration.
La dclaration dun tableau se fait en faisant suivre le nom de lidenticateur dune paire de crochet, 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 1-9. Dclaration dun tableau
int MonTableau[100];
23
MonTableau est un entier de 100 entiers. On rfrence les lments des tableaux en donnant lindice
Les tableaux plus dune dimension sont en fait des tableaux de tableaux. On prendra garde au fait que dans la dclaration dun tableau plusieurs dimensions, la dernire dimension indique est la dimension du tableau dont on fait un tableau. Ainsi, dans lexemple suivant :
int Matrice[5][4] ; Matrice est un tableau de dimension 5 dont les lments sont des tableaux de dimension 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 :
int Matrice[2] ;
est donc le deuxime lment de ce tableau 5 dimensions, et est lui-mme un tableau 4 dimensions. On prendra garde au fait quen C/C++, 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 plus grand que leur taille. Un autre point auquel il faudra faire attention est la taille des tableaux utiliser pour les chanes de caractres. Une chane de caractres se termine obligatoirement par le caractre nul (\0), il faut donc rserver de la place pour lui. Par exemple, pour crer une chane de caractres de 100 caractres au plus, il faut un tableau pour 101 caractres (dclar avec char chaine[101] ; ).
affectation :
variable = valeur
24
Note : Laffectation nest pas une instruction. Cest une opration qui renvoie la valeur affecte. On peut donc effectuer des affectations multiples. Exemple 1-11. Affectation multiple
i=j=k=m=0 ; /* Annule les variables i, j, k et m. */
autres oprations :
valeur op valeur
% reprsente la congruence (modulo). | et & reprsentent respectivement le ou et le et binaire (cest dire bit bit : 1 et 1 = 1, 0 et x = 0, 1 ou x = 1 et 0 ou 0 = 0). ^ reprsente le ou exclusif (1 xor 1 = 0 xor 0 = 0 et 1 xor 0 = 1). ~ reprsente la ngation binaire (1 <-> 0). << et >> effectuent un dcalage binaire vers la gauche et la droite respectivement, dun nombre de bits gal la valeur du second oprande.
affections composes. Ces oprations permettent de raliser une opration normale et une affectation en une seule tape :
variable op_aff valeur
avec op_aff lun des oprateurs suivants : +=, -=, *=, etc. . . Cette syntaxe est strictement quivalente :
variable = variable op valeur
Il est possible de crer un bloc dinstructions, en entourant les instructions de ce bloc avec des accolades. Un bloc dinstructions est considr comme une instruction unique. Il est inutile de mettre un point virgule pour marquer linstruction, puisque le bloc lui-mme est une instruction. Exemple 1-13. Instruction compose
{ i=1; j=i+3*g; }
25
type est le type de la valeur renvoye, identificateur est le nom de la fonction, et paramtres est une liste de paramtres. 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 lors de lappel de la fonction si aucune autre valeur nest fournie.
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 la commande return, dont la syntaxe est :
return valeur ;
Si une fonction ne renvoie pas de valeur, on lui donnera le type void. Si elle nattend pas de paramtres, sa liste de paramtres sera void ou nexistera pas. Il nest pas ncessaire de mettre une instruction return la n dune fonction qui ne renvoie pas de valeur. Exemple 1-15. Dnition de procdure
void rien() { /* Fonction nattendant pas de paramtres */ /* et ne renvoyant pas de valeur. */
26
return; }
Si la dclaration comprend des valeurs par dfaut pour des paramtres (C++ seulement), ces valeurs sont utilises lorsque ces paramtres ne sont pas fournis lors de lappel. Si un paramtre est manquant, alors tous les paramtres qui le suivent doivent tre eux aussi manquants. 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 ; }
Lappel de la fonction test(8) est valide. Comme on ne prcise pas le dernier paramtre, j est initialis 2. Le rsultat obtenu 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.
27
type identificateur(paramtres) ;
o type est le type de la valeur renvoye par la fonction, identificateur est son nom et paramtres la liste des types des paramtres que la fonction admet, spars par des virgules. Exemple 1-17. 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; } /* Dfinition de la fonction min. */ int Min(int i, int j) { if (i<j) return i; else return j; }
En C++, il est possible de donner des valeurs par dfaut aux paramtres dans une dclaration, et ces valeurs peuvent tre diffrentes de celles que lon peut trouver dans une autre dclaration. Dans ce cas, les valeurs par dfaut utilises sont celles de la dclaration visible lors de lappel de la fonction.
28
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 surcharges 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 cours) qui lui permettent de dterminer la meilleure fonction tant donn un jeu de paramtres. Si, lors de la recherche de la fonction utiliser, le compilateur trouve des ambiguts, il gnrera une erreur.
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).
29
Enn, du fait que les fonctions inline sont insres telles quelles aux endroits o elles sont appeles, il est ncessaires quelles soient compltement dnies avant leur appel. Ceci 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. Ces rfrences ne seraient pas rsolues ldition de lien, car il ne gnre galement pas les fonctions inline, puisquelles sont supposes tre insres sur place lorsquon les utilise. Les notions de compilation dans des chiers spars et ddition de liens seront prsentes en dtail dans le Chapitre 6. Exemple 1-19. Fonction inline
inline int Max(int i, int j) { if (i>j) return i; else return j; }
Pour ce type de fonction, il est tout fait justi dutiliser le mot-cl inline.
30
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. Cependant, le C et le C++ disposent dun mcanisme qui permet au programmeur de raliser des fonctions dont le nombre et le type des paramtres est variable. Nous verrons plus loin que les fonctions dentre - sortie du C sont des fonctions dont la liste des arguments nest pas xe, ceci an de pouvoir raliser un nombre dentres - sorties arbitraire, et ce sur nimporte quel type prdni. En gnral, les fonctions dont la liste des paramtre 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 de la liste par exemple. On peut aussi dnir les paramtres qui suivent le premier paramtre laide dune chane de caractre. 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. Ces paramtres 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 et les chiers den-tte, consulter le Chapitre 5. Pour linstant, sachez seulement quil faut ajouter la ligne suivante :
#include <stdarg.h>
au dbut de votre programme. Ceci permet 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 lexpressions suivantes :
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
31
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 les tapes suivantes 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.
Note: Il existe une restriction sur les types des paramtres des listes variables darguments. Lors de lappel des fonctions, un certain nombre de traitements sur les paramtres a lieu. En particulier, des promotions implicites ont lieu, ce qui se traduit par le fait que les paramtres rellement passs aux fonctions ne sont pas du type dclar. Le compilateur continue de faire les vrications de type, mais en interne, un type plus grand peut tre utilis pour passer les valeurs des paramtres. En particulier, les types char et short ne sont pas utiliss : les paramtres sont toujours promus aux type int ou long int. Ceci implique que les seuls types que vous pouvez utiliser sont les types cibles des promotions et les types qui ne sont pas sujets aux promotions (pointeurs, structures et unions). Les types cibles dans les promotions sont dtermins comme suit :
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. Si int est insufsant, unsigned int est utilis ; les types des numrations (voir plus loin pour la dnition 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 (voir plus loin pour la dnition des champs de bits) ; les valeurs de type oat sont converties en double.
32
/* 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 do ... while, voir Section 2.1.4.
La fonction main doit renvoyer un code derreur dexcution du programme, le type de ce code est int. Elle peut aussi recevoir des paramtres du systme dexploitation. Ceci sera expliqu plus loin. Pour linstant, on se contentera dune fonction main ne prenant pas de paramtres.
Note: Il est spci dans la norme du C++ que la fonction main ne doit pas renvoyer le type void. En pratique cependant, tous les compilateurs lacceptent aussi. La valeur 0 retourne par la fonction main indique que tout sest droul correctement. En ralit, la valeur du code de retour peut tre interprte diffremment selon le systme dexploitation utilis. La librairie C dnit donc les constantes EXIT_SUCCESS et EXIT_FAILURE, qui permettent de supprimer lhypothse sur la valeur utiliser respectivement en cas de succs et en cas derreur.
33
Elle renvoie le nombre de caractres afchs. On peut passer autant de valeurs que lon veut, pour peu quelles soient toutes rfrences dans la chane de format. La chane de format peut contenir du texte, mais surtout elle doit contenir autant de formateurs que de variables afcher. Si ce nest pas le cas, le programme plantera. Les formateurs sont placs dans le texte l o les valeurs des variables doivent tre afches. La syntaxe des formateurs est la suivante :
%[[indicateur]...][largeur][.prcision][taille] type
Un formateur commence donc toujours par le caractre %. Pour afcher ce caractre sans faire un formateur, il faut le ddoubler (%%). Le type de la variable afcher est obligatoire lui aussi. Les types utilisables sont les suivants : Tableau 1-1. Types pour les chanes de format de printf Type de donnes afcher Numriques Entier dcimal sign Entier dcimal non sign Entier octal non sign Entier hxadcimal non sign Flottants Caractres Pointeurs Caractre isol Chane de caractres Pointeur Caractre de formatage d u ou i o x (avec les caractres a f) ou X (avec les caractres A F) f, e, g, E ou G c s p
Note: Voir le Chapitre 3 pour plus de dtails sur les pointeurs. Le format des pointeurs dpend de la machine. Les valeurs ottantes innies sont remplaces par les mentions +INF et -INF. Un non-nombre IEEE (Not-A-Number) donne +NAN ou -NAN.
Les autres paramtres sont facultatifs. Les valeurs disponibles pour le paramtre de taille sont les caractres suivants :
34
Tableau 1-2. Options pour les types des chanes de format Option F N h l L Type utilisable Pointeur Pointeur Entier Entier ou ottant Flottant Taille du type Pointeur FAR (DOS uniquement) Pointeur NEAR (DOS uniquement) short int long int ou double long double
Les paramtres indicateurs, largeur et prcisions sont moins utiliss. Il peut y avoir plusieurs paramtres indicateurs, ils permettent de modier lapparence de la sortie. Les principales options sont :
- : justication gauche de la sortie, avec remplissage droite par des 0 ou des espaces ; + : afchage du signe pour les nombres positifs ; espace : les nombres positifs commencent tous par un espace.
Le paramtre largeur permet de spcier la largeur minimum du champ de sortie, si la sortie est trop petite, on complte avec des 0 ou des espaces. Enn, le paramtre prcision spcie la prcision maximum de la sortie (nombre de chiffres afcher).
35
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 des chanes de caractres. Toutefois, si elle contient autre chose que des formateurs, le texte saisi par lutilisateur devra correspondre imprativement avec les chanes de caractres indiques dans la chane de format. scanf cherchera reconnatre ces chanes, et arrtera lanalyse la premire erreur. La syntaxe des formateurs pour scanf diffre un peu de celle de ceux de printf :
%[*][largeur][taille]type
Seul le paramtre largeur change par rapport printf. Il permet de spcier le nombre maximum 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. Cette variable doit quand mme tre prsente dans la liste des paramtres de scanf.
Sans entrer dans les dtails, disons simplement que cette ligne permet dinclure un chier contenant les dclarations des fonctions printf et scanf. Voir le chapitre sur le prprocesseur pour de plus amples dtails. Le programme suivant est donn titre dexemple. Il calcule la moyenne de deux nombres entrs au clavier et lafche : Exemple 1-24. Programme complet simple
#include <stdio.h> long double x, y; int main(void) { 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 %Lf et de %Lf est %Lf.\n", x, y, (x+y)/2); /* Autorise lemploi de printf et de scanf. */
36
return 0; }
Dans cet exemple, les chanes de format spcient des ottants (f) en quadruple prcision (L).
37
dinstructions. Une variante permet de spcier laction excuter en cas de test faux :
if (test) opration1 ; else opration2 ;
Les oprateurs de comparaison sont les suivants : Tableau 2-1. Oprateurs de comparaison == != < > <= >= galit ingalit infriorit supriorit infriorit ou galit supriorit ou galit
Les oprateurs logiques applicables aux expressions boolennes sont les suivants :
38
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,
39
Ceci signie que lon ne peut pas utiliser la variable i aprs linstruction for, puisquelle nest dnie que dans cette instruction. Ceci permet de raliser des variables muettes, qui ne servent qu linstruction for dans laquelle elles sont dnies.
Note: Cette rgle nest pas celle utilise par la plupart des compilateurs C++. La rgle quils utilisent spcie que la variable dclare dans la partie initialisation de linstruction for reste dclare aprs cette instruction. La diffrence est subtile, mais importante. Ceci pose assurment des problmes de compatibilit avec les programmes C++ crits pour ces compilateurs, puisque dans un cas la variable doit tre redclare et dans lautre cas elle ne le doit pas. Il est donc recommand de ne pas dclarer de variables dans la partie initialisation des instructions for pour assurer une portabilit maximale.
2.1.3. Le while
Syntaxe :
while (test) opration ; opration est effectue tant que test est vri. Comme pour le if, les parenthses autour du test
2.1.4. Le do
40
Syntaxe :
do opration ; while (test) ; opration est effectue jusqu ce que test ne soit plus vri. Lordre dexcution est : opration test
p * i; i +1; (i!=10);
41
} 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 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 isntructions 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-5. 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 deffectuer une dclaration de variable dans un des case dun switch.
2.1.6. Le saut
Syntaxe :
goto tiquette ;
et
tiquette: tiquette est le point darrive du saut goto. Elle peut avoir nimporte quel nom didenticateur, et est toujours suivi de deux points (:).
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
42
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.
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.
ou
break ;
ou
return [valeur] ;
ou
goto tiquette ;
part le goto, qui a dj t vu, il y a dautres commandes de rupture de squence (cest dire de changement de la suite des instructions excuter).
return permet de quitter immdiatement la fonction en cours. break permet de passer linstruction suivant linstruction while, do, for ou switch la plus imbrique (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.
43
Il nest pas ncessaire de donner un nom la structure. La structure contient plusieurs autres variables, appeles champs. Leur type est donn dans la dclaration de la structure. Ce type peut tre nimporte quel autre type, mme une structure. 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 2-7. Dclaration de variable de type structure
struct Client { unsigned char Age ; unsigned char Taille ; } Jean ;
44
dclarer la structure en lui donnant un nom, puis dclarer les variables avec la syntaxe suivante :
[struct] nom_structure identificateur ;
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 structure client, le motcl struct a t mis. Ceci 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.
Note: Le typage du C++ est plus fort que celui du C, parce quil considre que deux types ne sont identiques que sils ont le mme nom. Alors que le C considre que deux types qui ont la mme structure sont des types identiques, le C++ les distingue. Ceci peut tre un inconvnient, car des programmes qui pouvaient tre compils en C ne le seront pas forcment par un compilateur C++. Considrons lexemple suivant :
int main(void) { struct st1 { int a; } variable1 = {2}; struct { int a; } variable2; /* variable2 a exactement la mme structure que variable1, */ variable2 = variable1; /* mais ceci est ILLGAL en C++ ! */ return 0; }
Bien que les deux variables aient exactement la mme structure, elles sont de type diffrents ! En effet, variable1 est de type st1 , et variable2 de type (la structure qui a permis de la
45
construire na pas de nom). On ne peut donc pas faire laffectation. Pourtant, ce programme tait compilable en C pur. . .
Note: 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 :
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.
x.entier=2 ;
46
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 leur interprtation possible. Exemple 2-10. Union avec discriminant
struct SystemEvent { int iEventType;
union { struct { int iMouseX; int iMouseY; }; struct { char cCharacter; int iShiftState; }; /* etc... */ }; };
47
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 la valeur augmente de 1. Exemple 2-11. Dclaration dune numration
enum Nombre { un=1, deux, trois, cinq=5, six, sept };
Dans cet exemple, les numrs prennent respectivement leur 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++.
/* /* /* /*
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 ;
48
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} ;
o alias est le nom que doit avoir le synonyme du type et dfinition est sa dnition. Pour les tableaux, la syntaxe est particulire :
49
typedef type_tableau type[(taille)]([taille](...)) ; type_tableau est alors le type des lments du tableau.
mot est strictement quivalent unsigned int. Exemple 2-15. Dnition de type tableau
typedef int tab[10];
tab est le synonyme de tableau de 10 entiers . Exemple 2-16. Dnition de type structure
typedef struct client { unsigned int Age; unsigned int Taille; } Client;
Client reprsente la structure client. Attention ne pas confondre le nom de la structure ( struct client ) avec le nom de lalias ( Client ).
Note: Pour comprendre la syntaxe de typedef, il suft de raisonner de la manire suivante. Si on 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.
Une fois ces dnitions dalias effectues, on peut les utiliser comme nimporte quel type, puisquils reprsentent des types :
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 */
50
2.2.7. Transtypages
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 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
Dans cet exemple, i est transtyp en ottant avant la division. On obtient donc 2.5.
51
classes de stockage assez large et permet de spcier le type de variables que lon dsire utiliser :
auto : la 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. Ce mot-cl est facultatif, la classe de stockage auto tant la classe par dfaut ; static : cette classe de stockage permet 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 leur valeurs. Un chier peut tre considr comme un bloc. Ainsi, une variable statique dun chier ne peut pas tre accde partir dun autre chier. Ceci est utile en compilation spare (voir plus loin) ; register : cette classe de stockage permet de crer une variable dont lemplacement se trouve 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 ; volatile : cette classe de variable 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) ; const : cette classe est utilise 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 motcl 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 ;
mutable : cette classe de stockage, disponible uniquement en C++, ne sert que pour les membres des structures. Elle permet de passer outre la constance ventuelle dune structure pour ce membre. Ainsi, un champ de structure dclar mutable peut tre modi mme si la structure est dclare const ; extern : cette classe est utilise pour signaler que la variable peut tre dnie dans un autre
chier. Elle est utilise dans le cadre de la compilation spare (voir le Chapitre 6 pour plus de dtails). Pour dclarer une classe de stockage particulire, il suft de faire prcder la dclaration 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.
52
Cette fonction mmorise le nombre dappels qui lui ont t faits dans la variable n et renvoie ce nombre.
int appels(void) { int n = 0 ; return n =n + 1 ; }
Cette fonction renverra toujours 1. La variable n est cre, initialise, incrmente et dtruite chaque appel. Elle ne survit pas la n de linstruction return. Exemple 2-19. Dclaration dune variable constante
const 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 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 2-20. 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.
53
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 toute 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 motscls const et volatile avec les pointeurs et les rfrences dans le chapitre suivant. Enn, en C++ uniquement, le mot-cl mutable permet de rendre un champ de structure const accessible en criture : Exemple 2-21. 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; }
// i et j valent 1.
54
Note: Si plusieurs pointeurs doivent tre dclars, ltoile doit tre rpte :
55
Ici, pi1, pi2 et pi3 sont des pointeurs dentiers et j est un entier.
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 { nom *pointeur ; ... } MaStructure ;
Ce type de construction permet de crer des listes de structures, dans lesquelles chaque structure contient ladresse de la structure suivante dans la liste. Il est galement possible de crer des pointeurs sur des fonctions, et dutiliser ces pointeurs pour paramtrer un algorithme avec laction de la fonction pointe. Nous dtaillerons plus loin ce type dutilisation des pointeurs.
56
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).
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 3-2. Utilisation de pointeurs de structures
struct Client { int Age; }; Client structure1; Client *pstr = &structure1; pstr->Age = 35; /* On aurait pu crire (*pstr).Age=35; */
57
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 3-3. Dclaration de rfrences
int i=0; int &ri=i; ri=ri+i;
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.
58
// Ceci gnre une erreur de syntaxe mais nous // lignorons pour les besoins de lexplication.
Nous constatons que la rfrence ri peut tre identie avec lexpression *pi, qui reprsente bel et bien la variable i. Ainsi, ri reprsente exactement i. Ceci permet de comprendre lorigine de la syntaxe de dclaration des rfrences.
59
/* Le contenu de i est copi dans j. i nest pas modifi. Il vaut toujours 2. */ /* La valeur 2 est copie dans j. */
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).
60
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).
int main(void) { int i=3; test(&i); /* On passe ladresse de i en paramtre. */ /* Ici, i vaut 2. */ return 0; }
prsent, il est facile de comprendre la signication de & dans lappel de scanf : les variables entrer sont passes par variable.
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).
61
Le C++ permet de rsoudre tous ces problme 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 [...]]
et :
p1 - p2 = adresse contenue dans p1 - adresse contenue dans 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
62
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 3-8. Arithmtique des pointeurs
int i, j; ptrdiff_t delta = &i - &j; int error = &i - &j;
Il est possible de connatre la taille dun lment en octets 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 en octets du type, soit celle du type de lexpression. Dans le cas des tableaux, il renvoie la taille totale du tableau. Si son 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 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. Ceci 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
63
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.
et :
*(identificateur + n)
si identificateur est soit un identicateur de tableau, soit celui dun pointeur. Exemple 3-9. 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 5me lment est initialis 1 */
Note: Le langage C++ impose que ladresse suivant le dernier lment dun tableau doit toujours tre valide. Ceci 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 tout 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 lorsque lon manipule les adresses des tableaux. En particulier, on a lgalit suivante :
64
&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
ce qui est faux (le type du premier lment nest en gnral pas convertible en type pointeur.).
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).
65
Le programme dexemple suivant illustre le passage des tableaux en paramtre : Exemple 3-10. Passage de tableau en paramtre
int tab[10][20]; void test(int t[][20]) { /* Utilisation de t[i][j] ... */ return; } int main(void) { test(tab); return 0; }
66
la premire dclaration se lit de la manire suivante : pi (pi) est un tableau ([]) de 12 (12) entiers (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 on 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. */
67
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 ceci 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 de 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 ;
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 :
68
Cependant, il a toujours t dusage de raliser linitialisation des chanes de caractres de la mme manire :
char *pc="Coucou !" ; /* Thoriquement illgal, mais tolr. */
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 caractre constante peut provoquer un plangage immdiat du programme. En C++, les rfrences constantes sont trs utiles pour raliser des passages de variables par paramtres sans pour autant donner la fonction le droit de modier les paramtres. Ce type de situation apparat souvent lorsque lon veut passer en paramtre une grosse structure ou toute autre variable dont la copie nest pas ncessaire (et donc dconseille si lon tient avoir de bonnes performances). Exemple 3-11. 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.). 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 3-12. Cration dun objet temporaire lors dun passage par rfrence
void test(const int &i) {
69
...
return ; } int main(void) { test(3); // Appel de test avec une constante. return 0; }
Au cours de cet appel, une variable locale est cre (la variable i de la fonction test), et 3 lui est affect.
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 librairie 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 la fontion strcpy, la comparaison de deux chanes de caractres pourra tre ralise laide de la fonction strcmp. Je
70
vous invite consulter la documentation de votre environnement de dveloppement pour dcouvrir toutes les fonctions de manipulation des chanes de caractres.
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 clients. On ne peut pas savoir lavance combien de clients seront entrs, 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 davec la dclaration de tableau prcdente, cest que le nombre de clients et donc la quantit de mmoire allouer, est variable. Il faut donc faire ce quon appelle une allocation dynamique de mmoire.
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>
71
Son rle est similaire celui de la ligne #include <stdio.h>. Vous verrez sa signication dans le chapitre concernant le prprocesseur. Exemple 3-13. Allocation dynamique de mmoire en C
#include <stdio.h> #include <stdlib.h> /* Autorise lutilisation de printf et de scanf. */ /* Autorise lutilisation de malloc et de free. */ /* Dclare un pointeur dentier, qui sera utilis comme un tableau de n matrices 10*10. */
int (*pi)[10][10];
/* Taille du tableau (non connue la compilation). */ printf("Entrez la taille du tableau : "); scanf("%u",&taille); pi = (int (*)[10][10]) malloc(taille * sizeof(int)*10*10); /* Ici se place la section utilisant le tableau. */ free(pi); return 0; /* Attention ne jamais oublier de restituer la mmoire alloue par vos programmes ! */
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 :
72
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 libr 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] ;
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. Les oprateur delete et delete[] ne doivent pas gnrer derreur lorsquon leur passe en paramtre un pointeur nul. 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 ;
73
soit il ne peut rien faire. Dans ce cas, il peut mettre fait lexcution du programme ou lancer une exception std::bad_alloc, qui remonte alors jusqu la fonction appelant loprateur new.
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 librairie 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 librairie 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 librairie standard dnit lobjet constant std::nothrow cet usage.
Les oprateurs C++ dallocation et de dsallocation de la mmoire devront tre utiliss de prfrence aux fonctions malloc et free car ils permettent un meilleur contrle des types. De plus, nous verrons quils permettent dinitialiser correctement les types dnis par lutilisateur dans le chapitre traitant de la couche objet du C++.
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
74
paramtres, spars par des virgules. Exemple 3-14. Dclaration de pointeur de fonction
int (*pf)(int, int); /* Dclare un pointeur de fonction. */
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 3-15. Drfrencement de pointeur de fonction
#include <stdio.h> int f(int i, int j) { return i+j; } int (*pf)(int, int); int main(void) { int l, m; pf = &f; /* Autorise lemploi de scanf et de printf. */ /* Dfinit une fonction. */
/* Dclare deux entiers. */ /* 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 0; }
Lintrt des pointeurs de fonction est de permettre lappel dune fonction parmi un ventail de fonctions au choix.
75
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 3-16. Application des pointeurs de fonctions
#include <stdio.h> /* Autorise lemploi de scanf et de printf. */
/* Dfinit plusieurs fonctions travaillant sur des entiers : */ int somme(int i, int j) { return i+j; } 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]="ient; 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. */ printf("\nRsultat : %u.\n", (*(ftab[n]))(i,j) ); return 0; }
76
// Fonction de comparaison de deux entiers : int compare(int i, int j) { 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 ; }
77
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 ; 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 3-17. Rcupration de la ligne de commande
#include <stdio.h> /* Autorise lutilisation des fonctions */ /* printf et scanf. */ /* 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 0; /* Tout sest bien pass : on renvoie 0 ! */ }
3.14. DANGER
78
Je vais rpter ici les principaux dangers que lon encourt lorsquon utilise les pointeurs. Bien que tous ces dangers existent, il faut vivre avec car il est impossible de programmer en C/C++ sans pointeurs. Il suft de faire attention pour les viter. 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. Ceci conduit dans la majorit des cas au plantage du programme, et parfois au plantage de lordinateur si le systme ne dispose pas de mcanisme de protection efcace.
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 si la demande dallocation mmoire na pas pu tre satisfaite. Cependant, la plupart des 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
79
1. a naccrot pas la vitesse du programme. Si lon veut aller plus vite, il faut revoir lalgorithme ou changer de compilateur (inutile de faire de lassembleur : les bons compilateurs se dbrouillent mieux que les tre humains sur ce terrain. Lavantage de lassembleur est que l, au moins, on est sr davoir un programme illisible.). 2. a augmente les chances davoir des bogues. Cependant, si vous voulez vous amuser, voici quelques conseils :
Dans la syntaxe donne ci-dessus, 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 :
min=(i<j) ?i:j ; /* Calcule le minimum de i et de j. */
a reste encore lisible. Cest pour cela que loprateur virgule (,) a t invent. Il remplace les blocs dinstructions. Sa syntaxe est :
expression1,expression2[,expression3[...]]
Les expressions sont values de gauche droite (expression1, expression2, etc. . .), puis le type et la valeur de la dernire expression sont utiliss pour renvoyer le rsultat. Par exemple :
double r = 5 ; int i = r*3,1 ;
80
Enn, il y a les oprateurs dincrmentation et de dcrmentation (++ et --). Ils sappliquent 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. */
abusez des oprateurs vus prcdemment ; placez-les dans les structures de contrles. Notamment, utilisez loprateur virgule pour faire des instructions composes dans les tests du while et dans tous les membres du for. Il est souvent possible de mettre le corps du for dans les parenthses ; si ncessaire, utiliser les expressions composes ({ et }) dans les structures de contrles ; placez toutes les dclarations de variables globales ensemble ; ne commentez rien, ou mieux, donnez des commentaires sans rapport avec le code ; choisissez des noms de variables alatoires (pensez une phrase, et prenez les premires ou les deuximes lettres des mots au hasard) ; faites des fonctions rallonge ; ne soignez pas lapparence de votre programme (pas dindentations, plusieurs lignes ensembles) ; regroupez-les toutes dans un mme chier, par ordre de non-appariement ; rajoutez des parenthses l o elles ne sont pas ncessaires ; rajoutez des transtypages l o ils ne sont pas ncessaires.
81
Vous laurez compris : il est plus simple de dire ici ce quil ne faut pas faire que de dire comment il faut faire. Je ne prtend pas imposer quiconque une mthodologie quelconque, car chacun est libre de programmer comme il lentend. En effet, certaines conventions de codages sont aussi absurdes quinutiles et elles ont linconvnient de ne plaire qu celui qui les a crites (et encore. . .). Cest pour cette raison que je me suis content de lister les sources potentielles dillisibilit des programmes. Sachez donc simplement que si vous utilisez une des techniques donnes dans ce paragraphe, vous devriez vous assurer que cest rellement justi et revoir votre code. Pour obtenir des programmes lisibles, il faut simplement que chacun y mettre un peu du sien, cest aussi une marque de politesse envers les autres programmeurs.
82
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.
ou :
#include <fichier> 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).
83
Chapitre 5. Le prprocesseur C
Le chier inclus est trait lui aussi par le prprocesseur. La signication de la ligne #include <stdio.h> au dbut de tous les programmes utilisant les fonctions scanf et printf devient alors claire. Si vous ouvrez le chier stdio.h, vous y verrez la dclaration de toutes les fonctions et de tous les types de la librairie dentre - sortie standard. De mme, les fonctions malloc et free sont dclares dans le chier den-tte stdlib.h et dnies dans la librairie standard. Linclusion de ces chiers permet donc de dclarer ces fonctions an de les utiliser.
Cette commande servira dnir les constantes. Exemple 5-1. Dnition de constantes
#define VRAI 1 #define FAUX 0 VRAI sera remplac systmatiquement par 1 et FAUX par 0 dans la suite du texte.
Le prprocesseur dnit un certain nombre de chanes de remplacement automatiquement. Ce sont les suivantes :
__LINE__ : donne le numro de la ligne courante ; __FILE__ : donne le nom du chier courant ; __DATE__ : renvoie la date du traitement du chier par le prprocesseur ; __TIME__ : renvoie lheure du trautement du chier par le prprocesseur ; __cplusplus : dnie uniquement dans le cas dune compilation C++. Sa valeur doit tre 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.
84
Chapitre 5. Le prprocesseur C
On fera par ailleurs une distinction bien nette entre les constantes littrales 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 ont malgr tout une place mmoire rserve. Il est possible, par un transtypage de pointeur, de modier leur valeur (opration trs dconseille et de surcrot certainement inutile), et elles peuvent tre modies par lenvironnement si elles sont de type volatile. Ce sont donc des variables accessibles en lecture seule plutt 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 vraie constante dclare avec la directive #define du prprocesseur conservera toujours sa valeur (pourvu quon ne la rednisse pas).
Lorsquun identicateur est dni, il est possible de faire des tests dessus (voir section suivante).
Le texte compris entre le #ifdef (cest dire if dened ) et le #endif est laiss tel quel si lidenticateur identicateur est connu du prprocesseur. Sinon, il est supprim. Lidenticateur peut tre dclar en utilisant simplement la commande #define vue ci-dessus. Il existe dautres commandes conditionnelles :
#ifndef #elif #if (if not defined ...) (sinon, si ... ) (si ... )
qui permettent de faire des chiers dont certaines parties ne seront pas compiles. Cest ce quon appelle la compilation conditionnelle. La directive #if attend en paramtre une expression constante.
85
Chapitre 5. Le prprocesseur C
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 la norme dcrite dans le projet de norme du 2 dcembre 1996. Une autre application courante des directives de compilation est la suivante :
#ifndef DejaLa #define DejaLa Texte ninclure quune seule fois au plus. #endif
qui permet dviter que le texte ne 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 est 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.
# : ne fait rien (directive nulle) ; #error message : permet de stopper la compilation en afchant le message derreur donn en
paramtre ;
#line numro [fichier] : permet de changer le numro de ligne courant et le nom du chier courant lors de la compilation ; #pragma texte : permet de donner des ordres dpendant de limplmentation au compilateur. Toute implmentation qui ne reconnat pas un ordre donn dans une directive #pragma doit lignorer pour viter des messages derreurs.
86
Chapitre 5. Le prprocesseur C
Le prprocesseur peut, lors du mcanisme de remplacement de texte, prendre des paramtres. Ces paramtres sont placs sans modications dans le texte de remplacement. Le texte de remplacement est alors appel macro. Syntaxe :
#define macro(identificateur[,identificateur[...]]) dfinition
Pour poursuivre une dnition sur la ligne suivante, terminez la ligne courante par le signe \. Le mcanisme des macros permet de faire lquivalent des fonctions gnrales, comme, qui fonctionnent pour tous les types ordonns. 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 parmamtres passs une macro sont valus par celle-ci chaque fois quils sont utiliss dans la dnition de la macro. Ceci 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)
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. 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 utilise dans la macro. Les parenthses forcent ce calcul. Si on ne les met pas, les rgles de priorits 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
87
Chapitre 5. Le prprocesseur C
ce qui vaut 26, et non pas 70 comme on laurait attendu. La bonne macro est :
#define mul(x,y) ((x)*(y))
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 :
88
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
la place de :
#ifdef identificateur . . . #endif
Cependant, defined permet lcriture dexpressions plus complexes que la directive #if.
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
89
Chapitre 5. Le prprocesseur C
NOMBRE(2,3)
est remplac par le nombre dcimal 23. Le rsultat de la concatnation est ensuite analys pour dventuels remplacements additionnels par le prprocesseur.
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
# \ ^ [ ] | { } ~
90
Chapitre 6. Modularit
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 enn linuence de ces tapes sur la syntaxe du langage.
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. Ils sappliquent aussi aux programmeurs individuels. En effet il est plus facile de dcomposer un problme en ses lments, forcment plus simples, que de le traiter dans sa totalit (dixit Descartes). 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, 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.
91
Chapitre 6. Modularit
les chiers C, qui ne contiennent que du code C ; les chiers C++, qui contiennent du code C++ et ventuellement du code C si ce dernier est sufsamment propre ; les chiers den-ttes, qui contiennent toutes les dclarations et dnitions communes plusieurs chiers sources.
92
Chapitre 6. Modularit
On utilise une extension diffrente pour les chiers C et les chiers C++ an de les diffrencier. Les conventions utilises dpendent du compilateur. Cependant, on peut en gnral tablir les rgles suivantes :
les chiers C ont lextension .c ; les chiers C++ prennent lextension .cc, ou .C (majuscule) sur UNIX, ou .cpp sur les PC sous DOS ou Windows (ces deux systmes ne faisant pas la diffrence entre les majuscules et les minuscules dans leur systmes de chiers) ; les chiers den-tte ont lextension .h, parfois .hpp (en-tte C++).
Note: 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. Ceci 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 denttes contenant du code inline peuvent tre compils sparment sans que ces fonctions ne soient dnies plusieurs fois. Par consquent, lditeur de lien ne gnrera pas derreur (alors quil laurait fait si on avait plac le code dune fonction non inline dans un chier den-tte inclus plusieurs fois). Certains programmeurs considrent quil nest pas bon de placer des dnitions de fonctions dans des chiers den-ttes, 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.
93
Chapitre 6. Modularit
tions sont disponibles (mais trs peu portable). La syntaxe (simplie) des compilateurs est souvent la suivante :
cc [fichier.o [...]] [[-c] fichier.c [...]] [-o excutable] [-Lchemin_librairies] [-llibrairie [...]] [-Ichemin_include] fichier.c est le nom du chier compiler. Si loption -c le prcde, le chier sera compil, mais lditeur de lien ne sera pas appel. Si cette option nest pas prsente, lditeur de lien 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 objet dj compil ( fichier.o ) pour que lditeur de liens les lie avec le programme compil.
Loption -L permet dindiquer le chemin du rpertoire des librairies 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 librairie spcie, si elle ne fait pas partie des librairies 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. 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.
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 dinformations et de lignes de commandes (de linterprteur de commande UNIX ou DOS). Les commandes doivent toujours tre prcdes dun caractre de tabulation horizontale. Les lignes dinformations donnent des renseignements sur les dpendances des chiers (en particulier : quels sont les chiers objet qui doivent tre utiliss pour crer lexcutable ?). Les lignes dinforma-
94
Chapitre 6. Modularit
tions demandent make de compiler les chiers dont dpend lexcutable avant de crer celui-ci. Les lignes de commande indiquent comment effectuer la compilation (et ventuellement dautres tches). La syntaxe des lignes dinformations est la suivante :
nom :dpendance
o nom est le nom du chier destination, et dpendance est la liste des noms des chiers dont dpend le chier nom, spars par des espaces. 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
95
Chapitre 6. Modularit
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.
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.
Il faudra bien faire la distinction entre les chiers compils sparment et les chiers inclus par le prprocesseur. Ces derniers ne sont en effet pas spars : ils sont compils avec les chiers dans lesquels ils sont inclus. Il est donc possible dinclure du code dans les chiers den-tte. Les programmes modulaires auront donc typiquement la structure suivante :
Fichier a.h #include Fichier b.c
96
Chapitre 6. Modularit
--------> (Utilisation des fonctions de a.c, dclares dans le fichier inclus a.h)
-------------- INDPENDANCE DES FICHIERS a.c ET b.c ----------------Fichier a.c (dfinition des fonctions dclares dans le fichier en-tte a.h) Compilation : a.c donne a.o, b.c donne b.o ; dition de liens : a.o et b.o donnent le programme excutable.
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 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.
97
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 et 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 donnant 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 dobjet. 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 quelles donnes elles doivent traiter, un pointeur sur les donnes de lobjet en question leur est pass en paramtre. Ce mcanisme est compltement invisible au programmeur en C++. Nous voyons donc que non seulement la programmation oriente objet est plus logique, mais en plus elle est 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 consti-
98
tuent 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 et les mmes paramtres. 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. Ceci permettra de comprendre ces gnralits.
99
[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 en-ttes. Si on ne donne que leurs en-ttes, on devra les dnir plus loin. Pour cela, il faudra spcier la classe laquelle elles appartiennent avec la syntaxe suivante :
type classe::nom(paramtres) { /* 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 mmes signatures pourvu quelles soient dans des classes diffrentes. Exemple 7-1. Dclaration de mthode de classe
struct Entier { int i;
// 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
100
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 dinstruction 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 { int i; void fixe(int a) { i=a; return; } int valeur(void) { return i; } // Mme signature que la fonction globale. // 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 :
int i=1; int main(void) { if (test()) { int i=3; int j=2*::i; /* Suite ... */ }
101
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; int dans_le_rouge(void) { return (Solde<0); } int bon_client(void) { return (Date_Entree<1993); // Date limite : 1993. } }; // Le bon client est // un ancien client.
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. */
102
Dans le cas o les fonctions membres dune classe sont dnies dans la dclaration de cette classe, elles seront implmentes en inline ( moins quelles ne soient rcursives ou quil existe un pointeur sur elles). Dans le cas o 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 ; int dans_le_rouge(void) ; int bon_client(void) ; }; /* 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 : */ int client::dans_le_rouge(void) { return (Solde<0) ; }
103
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 : 32767). Un programme qui ferait :
clientele[0].Solde = 32800 ;
esprerait obtenir un solde positif, or il obtiendrait un solde de -12 (valeur en nombre sign du nombre non sign 32800) ! 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, utiliser les mots-cls suivants :
public : les accs sont libres ; private : les accs sont autoriss dans les fonctions de la classe seulement ; protected : les accs sont autoriss dans les fonctions de la classe et de ses descendantes (voir le paragraphe suivant) seulement. Le mot-cl protected nest utilis que dans le cadre de lhritage
des classes. Le paragraphe suivant 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 mthodes prives.
104
public:
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 motcl 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.
Enn, il existe un dernier type de classe, que je me contenterai de mentionner : les classes union. Elles se dclarent avec le mot-cl union comme les classes struct et class. Les donnes sont, comme pour les unions du C, situes toutes au mme emplacement, ce qui fait qucrire dans lune dentre elle provoque la destruction des autres. Les unions sont trs souvent utilises en programmation systme, lorsquun polymorphisme physique des donnes est ncessaires (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.
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.
105
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 et union) 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 : Tableau 7-1. Droits daccs sur les membres hrits mot-cl utilis pour lhritage Accs aux donnes mot-cl utilis pour les champs et les mthodes
public protected private public public protected interdit protected protected protected interdit private private private interdit
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.
106
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 :
class Cercle : public Emplacement, public Forme { /* Dfinition de la classe Cercle. Cette classe hrite des donnes publiques et protges des classes Emplacement et Forme. */ };
107
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 quau membre de la rednition (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
108
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 :
A / \ B C \ / D
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, ceci 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 donne 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 { protected: int Donnee; };
// 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. */
109
};
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 le Chapitre 9. Deuximement, chaque classe drive directement ou indirectement dune classe virtuelle doit en appeler le constructeur explicitement dans son constructeur, si elle ne dsire pas que le constructeur par dfaut soit appel. 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 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 le constructeur 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 ceci, 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
110
// 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.
// Toutes les mthodes de Amie sont amies. // Donne prive de la classe Hote.
111
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 0; }
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).
112
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 faire une cration dynamique dobjets. 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 pointeurs. 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 (ceci signie quen pratique, 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). Exemple 7-10. Constructeurs et destructeurs
class chaine { char * s; // Implmente une chane de caractre. // Le pointeur sur la chane de caractre.
113
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; 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. Il faut spcier ces constructeurs, sinon le compilateur appellera, par dfaut, les constructeurs des classes mres qui prennent void pour paramtre (et si ceux-ci ne sont pas dnis, les constructeurs par dfaut, qui ne font rien). 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 mre que le constructeur ne prenant pas de paramtres, il suft de donner le nom de ce constructeur avec ses paramtres aprs le nom du constructeur de la classe lle, spars par 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. Exemple 7-11. Appel du constructeur des classes de base
/* Dclaration de la classe mre. */
114
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");
115
return; }
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.
Si lon navait pas prcis que le constructeur appeler pour la classe Mere tait le constructeur prenant un entier en paramtre, le constructeur par dfaut aurait t appel et seul le message de construction de la classe Fille aurait t afch. Par ailleurs, on notera que lordre dappel est important.
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). Les constructeurs des classes de base virtuelles 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 ceci supposerait que le constructeur de cette dernire classe serait appel plusieurs fois. Chaque classe doit donc prendre en charge la construction des sous-objets des classes de base virtuelles dont il hrite.
116
copie par dfaut, dont le seul but est de recopier les champs de lobjet recopier un un dans les champs de lobjet instancier. Le constructeur par dfaut ne sufra pas toujours, cest pour cela que le programmeur devra parfois en fournir un. Ce sera notamment le cas lorsque certaines donnes des objets auront t alloues. 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 entranera la modication pour les donnes de lautre objet. 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 pourra 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) { int i = 0 ; // Compteur de caractres. Taille = Source.Taille ; s = new char[Taille + 1] ; // Effectue lallocation. while ((s[i]=Source.s[i]) !=\0) i=i+1 ; // Recopie // la chane de caractre source return ; }
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.
117
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 :
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. Exemple 7-12. Mot-cl explicit
class Entier { int i; public: explicit Entier(int j) { i=j; return ; } };
prsent, lexpression donne ci-dessus nest plus valide. Si lon veut convertir lentier en objet de classe Entier, on est maintenant forc dutiliser un transtypage explicite (ce qui donne lorigine du mot-cl). Lexemple prcdent donne alors :
int j=2 ; Entier e1, e2=(Entier) j ; e1=(Entier) j ;
118
faire des oprations arithmtiques dessus). Ceci 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. Ceci 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. Si elle est appele sur un objet dclar const, une erreur de compilation se produit donc. Ce comportement est normal. Si la mthode incrimine ne modie pas rellement lobjet, on devra donc toujours la dclarer const pour pouvoir laisser le choix de dclarer const ou non un objet.
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 signa-
119
ture). 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.
La variable a::i sera partage par tous les objets de classe test, et sa valeur initiale est 3. 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 <stdio.h> class test {
120
public: int n(void) ; }; 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 0 ; }
// Affiche 0 // Affiche 1
afchera 0 et 1, parce que la variable statique compte est la mme pour les deux objets.
121
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. Exemple 7-15. Appel de fonction membre statique
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 0; }
122
surcharger. Le C++ permet donc de surcharger les oprateurs du langage comme on surcharge les fonctions, mme en dehors dune classe. Nous allons voir ces deux syntaxes dans les sections suivantes.
lcriture
A Op B
se traduisant par :
A.operatorOp(B)
Avec cette syntaxe, le premier oprande est toujours lobjet auquel cette fonction sapplique. Ainsi, les paramtres de la fonction oprateur sont le deuxime oprande et les suivants. Les fonctions oprateurs devront souvent renvoyer une valeur du type de la classe des oprandes (ce nest pas une ncessit cependant). Il faudra donc soit renvoyer un objet qui aura t cr temporairement dans la fonction oprateur, soit lobjet caractris par le premier oprande de loprateur (cest dire lobjet lui-mme). Ceci est faisable grce au pointeur this. Dans le cas o la fonction renvoie un objet cr temporairement (cest dire avec la classe de stockage auto), cet objet est dtruit la sortie de la fonction. Cependant, lobjet retourn par la fonction existe : il a t recopi dans la valeur de retour de la fonction. Cet objet recopi sera dtruit par le compilateur une fois quil aura t utilis par linstruction qui a appel la fonction. Le programmeur na pas sen occuper. Les bons compilateurs sont mme capables dviter cette copie, ce qui accrot srieusement les performances avec les objets volumineux. Par exemple, la classe suivante implmente les nombres complexes avec leurs oprations de base : Exemple 7-16. Rednition des oprateurs
class complexe { float x, y; public:
123
float re(void) const; float im(void) const; // Les oprations de base : complexe operator+(const complexe complexe operator-(const complexe complexe operator*(const complexe complexe operator/(const complexe }; void complexe::fait(float a, float b) { x = a; y = b; return; } float complexe::re(void) const { return x; } float complexe::im(void) const { return y; }
// // // //
Fonction permettant de crer un complexe. Fonctions permettant de lire les parties relles et imaginaires. &) &) &) &) const; const; const; const;
complexe complexe::operator+(const complexe &c) const { complexe tmp=c; // Construit un complexe temporaire. // Le constructeur de copie par dfaut // est appel. tmp.x = tmp.x + x; // Ajoute les parties relles et imaginaires tmp.y = tmp.y + y; // du complexe en cours de traitement // celles du complexe tmp. return tmp; } complexe complexe::operator-(const complexe &c) const { complexe tmp=c; tmp.x = tmp.x - x; tmp.y = tmp.y - y; return tmp; } complexe complexe::operator*(const complexe &c) const {
124
complexe tmp=c; tmp.x = tmp.x * x - tmp.y * y; tmp.y = tmp.x * y + tmp.y *x; return tmp; } complexe complexe::operator/(const complexe &c) const { complexe tmp=c; float inv = tmp.x * tmp.x + tmp.y * tmp.y; tmp.x = tmp.x / inv; tmp.y = - tmp.y / inv; // tmp contient linverse de c. return (*this) * tmp; // Calcule x * (1/y). }
Le dernier oprateur fournit un exemple dutilisation du pointeur this. Il sera galement employ lors de la rednition des oprateurs =, +=, -=, etc. . . qui renvoient toujours lobjet en cours. Ils devront donc tous se terminer par :
return *this ;
125
Cette syntaxe permet dimplmenter les oprateurs pour lesquels loprande de gauche 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 dans ce cas, puisque la classe du premier oprande est dj dnie. Par exemple, si lon veut implmenter la multiplication par un scalaire gauche pour la classe complexe, on devra procder comme suit :
complexe operator*(float k, const complexe &c) { complexe tmp ; tmp.fait(c.re()*k,c.im()*k) ; return tmp ; }
La premire syntaxe ne permettait pas de le faire, car il aurait fallu surcharger loprateur de multiplication de la classe oat, mais celle ci est dnie par le langage. La deuxime syntaxe est utilisable partout o la premire lest, mais elle est diffrente. En effet, les oprateurs ainsi dnis ne font pas partie de la classe que lon est en train de faire. Ceci a une consquence majeure : on ne peut pas accder aux donnes non publiques de la classe dans la dnition de ces oprateurs. La solution consiste fournir toutes les fonctions membres permettant laccs ces donnes, ou bien dclarer loprateur comme une fonction amie de la classe. Exemple 7-17. Surcharge doprateur externe
// Oprateur appartenant la classe complexe : complexe complexe::operator*(float k) const { complexe tmp=*this; tmp.x=tmp.x*k; tmp.y=tmp.y*k; return tmp; } // Oprateur externe, il nappartient pas la classe complexe : complexe operator*(const complexe &c, float k) { complexe tmp; tmp.fait(c.re()*k,c.im()*k); return tmp; }
126
On dispose donc de deux possibilits pour rednir les oprateurs classiques. Les seuls oprateurs qui ne peuvent pas tre rednis sont les suivants :
:: . .* ?: sizeof typeid static_cast dynamic_cast const_cast reinterpret_cast
Tous les autres oprateurs sont rednissables. En gnral, ils suivent les rgles nonces dans les paragraphes prcdents. Cependant, un certain nombre dentre eux demande des explications complmentaires.
127
Entier &operator++(void) // Oprateur prfixe : incrmente { // la variable et la retourne. i++; return *this; } };
128
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 0; }
Il est noter quaucun des oprateurs new, delete, new[] et delete[] ne reoit le pointeur this en paramtre : ce sont des oprateurs statiques. Ceci 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 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 :
129
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). Ceci 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. Ceci 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-20. Oprateurs new avec placement
#include <stdlib.h> class A { public: A(void) { return ; } ~A(void) { return ; }
// Constructeur.
// Destructeur.
// Loprateur new suivant utilise le placement. // Il reoit en paramtre le pointeur sur le bloc // utiliser pour la requte dallocation dynamique
130
// 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(&A) A; delete pB; return 0; }
// // // // //
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 librairie standard C++ effectue 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. Ceci 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 quun
131
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, ceci 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
132
// 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 ; } // 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 0 ; }
133
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 dans le cas o 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[] 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() ; };
134
Note: 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 derreur ne prennent aucun paramtre et ne renvoient aucune valeur. Leur comportement doit tre le suivant :
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 librairie standard dnit une version avec placement des oprateurs 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 librairie 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-21. Utilisation de new sans exception
char *data = new(std::nothrow) char[25]; if (data == NULL) { // Traitement de lerreur... . . . }
Note: La plupart des compilateurs ne respecte 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.
135
de les convertir en chane C classiques (cest dire en tableau de caractres) si loprateur (char const *) a t redni :
chaine::operator char const *(void) const ;
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 *).
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); matrice operator+(const matrice &m1) const; matrice operator-(const matrice &m1) const; matrice operator*(const matrice &m1) const;
136
}; // 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.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];
137
} 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; } // Oprateur daccs : double &matrice::operator()(unsigned short int i, unsigned short int j) { return lignes[i][j]; } // Addition : matrice matrice::operator+(const matrice &m1) const { matrice tmp(n,m); for (unsigned short int i=0; i<n; i++) // Double boucle. for (unsigned short int j=0; j<m; j++) tmp.lignes[i][j] = lignes[i][j]+m1.lignes[i][j]; return tmp; } // Soustraction : matrice matrice::operator-(const matrice &m1) const { matrice tmp(n,m); for (unsigned short int i=0; i<n; i++) // Double boucle. for (unsigned short int j=0; j<m; j++) tmp.lignes[i][j]=lignes[i][j]-m1.lignes[i][j]; return tmp; } // Multiplication : matrice matrice::operator*(const matrice &m1) const { matrice tmp(n,m1.m); for (unsigned short int i=0; i<n; i++) // Double boucle. for (unsigned short int j=0; j<m1.m; j++) { tmp.lignes[i][j]=0.; // Produit scalaire. for (unsigned short int k=0; k<m; k++) tmp.lignes[i][j] += lignes[i][k]*m1.lignes[k][j]; } return tmp; }
138
matrice m(2,3) ;
Et les oprations de bases telles que a=b+c, o a, b et c sont des matrices seront autorises. Les oprations dinversion, transposition, etc. . . nont pas t reportes par souci de clart.
// Cette classe est la classe encapsulante : struct Encapsulante { Encapsulee *operator->(void) const { return &o; } Encapsulee *operator&(void) const { return &o; } Encapsulee &operator*(void) const
139
{ 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 ; }
On constate quil est possible denvoyer plusieurs valeurs au ux de sortie, puisque loprateur << renvoie cout. Exemple 7-24. Flux dentre / sortie cin et cout
#include <iostream> using namespace std;
140
int main(void) { int i; cin >> i; cout << i << i+1; return 0; }
Note: Les ux dentre / sortie cin, cout et cerr sont dclars dans lespace de nommage std:: de la librairie standard C++. On devra donc faire prcder leur nom du prxe std:: pour y accder, ou utiliser un directive using pour importer les symboles de la librairie standard C++ dans lespace de nommage global. Vous trouverez de plus amples renseignements sur les espaces de nommages dans le Chapitre 10.
le type des donnes est automatiquement pris en compte par les oprateurs << et >> (ils sont surchargs pour tous les types prdnis) ; ils travaillent par rfrence (on ne risque plus domettre loprateur & dans scanf) ; ils sont plus simple demploi.
Les ux dentre / sortie dnis par la librairie C++ sont donc dune extrme souplesse. Nous les dtaillerons plus dans le Chapitre 14, o nous verrons comment personnaliser le format des sorties et comment raliser des entres / sorties dans des chiers.
141
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. Ceci se fait en faisant prcder par le mot-cl virtual dans la classe de base. Exemple 7-25. Surcharge 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.
void DonneeBase::Entre(void) { cin >> Numero; // Entre le numro de la donne. cout << \n; cin >> Valeur; // Entre sa valeur. cout << \n; 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
142
void DonneeDetaillee::Entre(void) { DonneeBase::Entre(); // Appelle la mthode de base. cin >> ValeurEtendue; // Entre la valeur tendue. cout << \n; 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 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
143
Nous allons voir ici les rgles de drivation. Ces rgles permettent de savoir ce qui est autoris et ce qui ne lest pas lorsque lon 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. 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. Ceci 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, ceci 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 dans 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)
144
{ cout << "Constructeur de la classe mre.\n" ; return ; } Mere::~Mere(void) { cout << "Destructeur de la classe mre.\n" ; return ; } class Fille : public Mere { public: Fille(void) ; ~Fille(void) ; }; Fille::Fille(void) : Mere() { cout << "Constructeur de la classe fille.\n" ; return ; } Fille::~Fille(void) { cout << "Destructeur de la classe fille.\n" ; return ; }
Avec ces dnitions, seule la premire des deux affectations suivantes est autorise :
Mere m ; Fille f ; m=f ; f=m ; // Instanciation de deux objets.
// Ceci est autoris, mais linverse ne le serait pas : // ERREUR ! ! (ne compile pas).
145
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.
146
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 dinstance 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, quel que soit leur type. 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. . . Exemple 7-26. Conteneur dobjets polymorphiques
#include <iostream> using namespace std; /************* LA CLASSE DE ABSTRAITE DE BASE *****************/
147
class Object { unsigned long int new_handle(void); protected: unsigned long int h;
// Handle 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 le handle 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 ******************/
// La classe sac. Elle hrite // de Object, car un sac peut // en contenir un autre. Le sac
148
// est implment sous la forme // dune liste chane. { typedef struct baglist { baglist *next; Object *ptr; } BagList; 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; }
void Bag::print(void) { BagList *tmp = head; cout << "Sac n " << handle() << ".\n Contenu : \n"; while (tmp != NULL) { cout << "\t"; tmp->ptr->print(); tmp = tmp->next;
149
} return; } bool Bag::has(unsigned long int h) const { BagList *tmp = head; while (tmp != NULL && tmp->ptr->handle() != h) tmp = tmp->next; // Cherche lobjet. 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; }
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 :
class MonObjet : public Object {
150
/* };
*/
Bag MonSac ; int main(void) { MonObjet a, b, c ; MonSac.add(a) ; MonSac.add(b) ; MonSac.add(c) ; MonSac.print() ; MonSac.remove(b) ; MonSac.add(MonSac) ; MonSac.print() ; return 0 ; }
// 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. Ceci 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 le handle 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 ; // // };
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
151
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, ceci fonctionne galement avec les fonctions membres virtuelles non pures, et mme avec les fonctions non virtuelles.
Par exemple, si une classe test contient des entiers, le type de pointeurs utiliser pour stocker leur adresse est :
int 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.
152
Cependant, ces pointeurs ne sont pas utilisables directement. En effet, les donnes dune classe sont instancies pour chaque objet, et les fonctions membres reoivent de manire implicite systmatiquement le pointeur this sur lobjet. On ne peut donc pas faire un drfrencement direct de ces pointeurs. Il faut spcier lobjet pour lequel le pointeur va tre utilis. Ceci 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.
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 ne reoivent plus 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.
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
153
sur lequel la fonction doit travailler aux fonctions membres statiques. Ceci 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 <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 ! n */ return j; // Autoris. } }; 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.
154
155
par lalgorithme doivent tre accessibles depuis le code de traitement des erreurs. Ces ressources doivent donc tre place dans une porte relativement globale, voire dclares en tte de fonction. De plus, le traitement des codes derreurs multiples pose toujours les mmes problmes de complication des tests. La solution qui met en uvre les exceptions est beaucoup plus simple, puisque la fonction qui dtecte une erreur peut se contenter de lancer une exception. 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 des erreurs : savoir la liste des appelants. La premire fonction appelante qui contient un gestionnaire dexception appropri prend donc le contrle, et effectue le traitement de lerreur. Si le traitement est complet, le programme reprend son excution normale. Dans le cas contraire, le gestionnaire dexception peut relancer lexception (auquel cas le gestionnaire dexception suivant est recherch) ou terminer le programme. Le mcanisme des exceptions du C++ garantie que tous les objets de classe de stockage automatique sont dtruits lorsque lexception qui remonte sort de leur porte. Ainsi, si toutes les ressources sont encapsules dans des classes disposant dun destructeur capable de les dtruire ou de les ramener dans un tat cohrent, la remonte des exceptions effectue automatiquement le mnage. De plus, les exceptions peuvent tre types, et caractriser ainsi la nature de lerreur qui sest produite. Ce mcanisme est donc strictement quivalent en termes de fonctionnalits aux codes derreurs utiliss prcdemment. Comme on le voit, les exceptions permettent de simplier le code, en reportant en dehors de lalgorithme normal le traitement des erreurs. Par ailleurs, la logique derreur est compltement prise en charge par le langage, et le programmeur na plus faire les tests qui permettent de dterminer le traitement appropri pour chaque type derreur. Les mcanismes de gestion des exceptions du C++ sont dcrits dans les paragraphes suivants.
o objet est lobjet correspondant lexception. Cet objet peut tre de nimporte quel type, et pourra ainsi caractriser pleinement lexception. Lexception doit alors tre traite par la routine dexception correspondante. On ne peut attraper que les exceptions qui sont apparues dans une zone de code limite (cette zone est dite protge contre les erreurs dexcution), pas sur tout un programme. On doit donc placer le code susceptible de lancer une exception dun bloc dinstructions particulier. Ce bloc est introduit avec le mot-cl try :
try { // Code susceptible de gnrer des exceptions... }
156
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 }
Notez que les objets de classe de stockage automatique dnis dans le bloc try sont automatiquement dtruits lorsquune exception fait sortir le contrle du programme de leur porte. Cest galement le cas de lobjet construit pour lancer lexception. Le compilateur effectue donc une copie de cet objet pour le transfrer au premier bloc catch capable de le recevoir. Ceci implique quil y ait un constructeur de copie pour les classes dexceptions non triviales. De mme, les blocs catch peuvent recevoir leur paramtre par valeur ou par rfrence, comme le montre la syntaxe indique ci-dessus. En gnral, il est prfrable dutiliser une rfrence, an dviter une nouvelle copie de lobjet de lexception pour le bloc catch. Toutefois, on prendra garde au fait que dans ce cas, les modications effectues sur le paramtre 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. 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, ceci 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 dexceptions universel, qui rcuprera toutes les exceptions possibles, quel 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-1. Utilisation des exceptions
#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) {} };
157
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). // Ceci interrompt le code. break est // donc inutile ici. } case 1: { int a=1; throw (a); // 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 << ")\n"; } catch (int tmp) // Traitement de lexception int... { cout << "Erreur int ! (cause " << tmp << ")\n"; } catch (...) // Traitement de toutes les autres { // exceptions (...). // On ne peut pas rcuprer lobjet ici. cout << "Exception inattendue !\n"; } return 0;
158
Selon ce quentre lutilisateur, une exception du type erreur, int ou other est gnre.
Lexception est alors relance, avec comme valeur lobjet que le compilateur a construit en interne pour propager lexception. Un gestionnaire dexception peut donc modier les paramtres de lexception, sil lattrape avec une rfrence. 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 librairie 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 est 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 leur nom lindique, std::terminate et std::set_terminate sont dclares dans lespace de nommage std::, qui est rserv pour toutes les objets de la librairie 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.
159
160
na le droit de lancer que des exceptions du type int, double ou erreur. Si une autre exception est lance, par exemple une exception du type char *, il se produit encore une fois une erreur lexcution. En fait, 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 ellemme appelle la fonction std::terminate (et donc abort en n de compte). Ceci 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 prend aucun paramtre et qui renvoie void. std::set_unexpected renvoie le pointeur sur la fonction de traitement derreur prcdemment appele par std::unexpected.
Note: Comme leur nom lindique, std::unexpected et std::set_unexpected sont dclares dans lespace de nommage std::, qui est rserv pour les objets de la librairie 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() ; };
Ceci 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-3. Gestion de la liste des exceptions autorises
#include <iostream> #include <exception> using namespace std;
161
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(); } catch (int i) { cout << "Exception de type int reue : " << i << endl; } return 0; }
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.
162
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 type. En effet, les objets exceptions peuvent tre des instances de classes disposant de relations dhritages. Comme les objets des classes drives peuvent tre considrs comme des instances de leurs classes de base, les gestionnaire dexceptions peuvent rcuprer les exceptions de ces classes drives en rcuprant un objet du type de leur classe 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 slction du gestionnaire derreur est automatiquement ralise par le langage. Ceci peut tre trs pratique pour peu que lon ait dni correctement sa hirarchie de classes dexceptions. Exemple 8-4. Classication des exceptions
#include <iostream> using namespace std; // 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 { };
163
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 &) { 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 0; }
La librairie standard C++ dnit elle-mme un certain nombre dexceptions standard, 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 librairie standard du C++ dans la Section 12.2.
164
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: Comme il la t dit plus haut, le compilateur nappelle pas le destructeur pour les objets dont le constructeur a gnr une exception. Cette rgle est valide mme dans le cas des objets allous dynamiquement. Le comportement de loprateur delete est donc lui aussi lgrement modi par le fait que lexception sest produite dans un constructeur.
165
class A { char *pBuffer; int *pData; public: A() throw (int); ~A() { cout << "A::~A()" << endl; } 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 (int) try { pBuffer = NULL; pData = NULL; cout << "Dbut du constructeur" << endl; pBuffer = new char[256]; cout << "Lancement de lexception" << endl; throw 2; // Code inaccessible : pData = new int; } catch (int) { cout << "Je fais le mnage..." << endl; delete[] pBuffer; delete pData; }
int main(void) {
166
try { A *a = new A; } catch (...) { cout << "Ae, mme pas mal !" << endl; } return 0; }
Dans cet exemple, lors de la cration dynamique dun objet A, une erreur dinitialisation se produit et une exception est lance. Celle-ci est alors traite dans le bloc catch qui suit la dnition du constructeur de la classe A. Loprateur delete est bien appel automatiquement, mais le destructeur de A nest jamais excut. 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 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 ...
167
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. Ceci 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 <typeinfo>
168
using namespace std; class Base { public: virtual ~Base(void); }; 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 0 ; }
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 librairie 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 librairie 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 :
169
class bad_typeid : public logic { public: bad_typeid(const char * what_arg) : logic(what_arg) { return ; } void raise(void) { handle_raise() ; throw *this ; } };
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.
170
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.
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 2.3 et dans la Section 3.9. Il ne peut pas travailler sur les types de base du langage, sauf void *. La syntaxe de loprateur dynamic_cast est donne ci-dessous :
171
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) ;
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 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).
172
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
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) { return ; } }; struct E : public B, public C, public D { }; int main(void) { E e;
// // // // A *pA=&e; // // // C *pC=(C *)
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
173
o type et expression ont les mmes 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 ne peut avoir lieu quentre classes drives et classes de base. Loprateur static_cast permet deffectuer ces transtypages dans les deux sens (classe de base vers classe drive et classe drive vers classe de base). Le transtypage dune classe de base vers une classe drive ne doit tre fait que lorsque lon 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).
174
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.
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 ;
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 donner lobjet initial).
175
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 // Dclaration de lespace de nommage A.
176
{ int j; }
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 espace de nommage se comporte donc exactement comme aux 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. Exemple 10-2. Accs aux membres dun namespace
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 0; }
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.
177
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 { int j; } }
// Conteneur::i.
// Conteneur::Contenu::j.
// 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. Dans le cas des espaces de nommage nomms, laccs peut tre ralis laide de loprateur de rsolution de porte. En revanche, il est impossible dy accder avec les espaces de nommage anonymes, puisquon ne peut pas prciser le nom de ces derniers. Exemple 10-6. Ambiguts entre namespaces
namespace
178
{ int i; } void f(void) { i++; } namespace A { namespace { int i; int j; } void g(void) { i++; A::i++; j++; } } // Dclare unique::i.
// Utilise unique::i.
// // // // //
Erreur : ambigut entre unique::i et A::unique::i. Erreur : A::i nest pas dfini (seul A::unique::i lest). Correct : A::unique::j++.
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.
179
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).
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; } 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. Ceci 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
namespace A { int i; void f(void) { } } namespace B {
180
using A::i; using A::i; using A::f; } int main(void) { B::f(); return 0; }
// Dclaration de lalias B::i, qui reprsente A::i. // Lgal : double dclaration de A::i. // Dclare void B::f(void), // fonction identique A::f.
// Appelle 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 { void f(char); } void g() { f(a); } // f est synonyme de A::f(int).
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 {
181
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.
182
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 rtablir les droits daccs, modis par un hritage, des membres de classes de base. Pour cela, il suft de placer la dclaration using dans une zone de dclaration public, protected ou private dans laquelle le membre se trouvait dans la classe de base. Cependant, comme on la vu ci-dessus, une classe ne peut pas rtablir les droits daccs dun membre private des classes de base. Exemple 10-12. Rtablissement de droits daccs laide dune directive using
class Base { public: int i; int j; }; class Derivee : private Base { public: using Base::i; // Rtablit laccessibilit sur Base::i. protected: using Base::i; // Interdit : restreint laccessibilit // sur Base::i autrement que par hritage. };
Note: Certains compilateurs interprtent diffremment le paragraphe 11.3 des Draft Papers, qui concerne laccessibilit des membres introduits avec une dclaration using. Selon eux, les dclarations using permettent de restreindre laccessibilit des droits et non pas de les rtablir. Ceci implique quil est impossible de redonner laccessibilit des donnes pour lesquelles lhritage a restreint laccs. Par consquent, lhritage doit tre fait de la manire la plus permissive possible, et les accs doivent tre ajusts au cas par cas. Bien que cette interprtation soit tout fait valable, lexemple donn dans les Draft Papers semble indiquer quelle nest pas correcte.
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.
183
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; }
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; }
184
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 ; }
185
186
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 utilis 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. 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. Lorsque lon 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.
187
};
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.
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 tre obligatoirement lun des types suivants :
type intgral (char, wchar_t, int, long, short et leurs versions signes et non signes) ou numr ; pointeur ou rfrences dobjets ; pointeurs ou rfrences de fonctions ; pointeurs sur membres.
Ce sont donc tous les types qui peuvent tre assimiles 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.
188
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. Ceci 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 normaux : des variables peuvent tre dclars 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.
189
Une fonction template peut tre dclare amie de toute 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.
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) { ... }
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 crochets, 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 {
190
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 crochets ("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) // La classe est rfrence en indiquant // son type entre crochets ("Stack<T>"). // Cest impratif en dehors de la // dclaration de la classe. { Tete = NULL; return; } 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;
191
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; } template <class T> void Stack<T>::push(T Item) { StackItem *tmp = new StackItem; tmp->Item = Item; tmp->Next = Tete; Tete = tmp; return;
192
} 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.
193
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 ; }
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) { // ...
194
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 surcharge pas cette fonction. Par consquent, les mcanismes de virtualit sont inutilisables avec les 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 surcharge 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 infrieurs et suprieurs lors de lappel de la fonction. Exemple 11-9. Surcharge de fonction membre par une fonction membre template
#include <iostream> using namespace std; struct A { void f(int); template <class T>
195
void f(T) { cout << "Template" << endl; } }; // Fonction non template : void A::f(int) { cout << "Non template" << endl; } // Fonction 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 0; }
Pour plus de dtails sur la spcialisation des template, voir la Section 11.5.
196
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 :
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. Ceci 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 <iostream> using namespace std ; template <class T>
197
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 ; a.f() ; return 0 ; }
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.
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 :
198
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 est 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>.
199
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 mme 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. Ceci 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.
200
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; };
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.
201
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> {
202
};
le type dune des valeurs de la spcialisation ne peut pas dpendre dun autre paramtre ;
template <class T, T t> struct C { }; template <class T> struct C<T, 1> ;
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.
203
template <class T> Item<T>::Item(T i) { item = i; } // Accesseurs : template <class T> void Item<T>::set(T i) { item = i; } template <class T> T Item<T>::get(void) const { return item; }
// Constructeur
// 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; }
204
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 quun identicateur identicateur inconnu est un type. Exemple 11-15. Mot-cl typename
class A { public: typedef int Y; }; template <class T> class X { typename T::Y i; }; X<A> x; // A peut servir instancier une classe // partir de la classe template X.
205
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 ; }
206
Tout comme pour le langage C, pour lequel un certain nombre de fonctions ont t dnies et standardises, et constituent la librairie C, une librairie de classes et de fonctions a t spcie pour le langage C++. Cette librairie est le rsultat de lvolution de plusieurs librairies, parfois dveloppes indpendamment par plusieurs fournisseurs denvironnement C++, qui ont t fusionnes et normalises an de garantir la portabilit des programmes qui les utilisent. Une des principales briques de cette librairie 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 librairie standard C++. Bien entendu, il est hors de question de dcrire compltement chaque fonction ou chaque dtail du fonctionnement de la librairie standard, car cela rendrait illisibles et incomprhensibles les explications. Cependant, les informations de base vous seront donnes, an de vous permettre dutiliser efcacement la librairie standard C++ et de comprendre les fonctionnalits les plus avances lorsque vous vous y intresserez. La librairie 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 librairie standard. En particulier, tous les algorithmes et toutes les classes fournies par la librairie sont susceptibles de travailler sur des donnes de type arbitraire. La librairie utilise donc pour cela 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 librairie utilise les mcanismes dexceptions 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 librairie. 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 soient ncessaires au dbutant pour assimiler toutes les subtilits de la librairie. Le premier chapitre de cette partir (Chapitre 12) prsente les notions de bases qui sont utilises dans toute la libraire : encapsulation des fonctions de la librairie C classique, classes de traits pour les types de base, notion ditrateurs, de foncteurs et dallocateurs mmoire. Le Chapitre 13 prsente les types complmentaires que la librairie 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 14 prsente les notions de ux dentre / sortie standard, et la notion de buffer pour ces ux. Les mcanismes de localisation (cest dire les fonctions de paramtrage du programme en fonction des conventions et des pfrences nationales) seront dcrits dans le Chapitre 15. Le Chapitre 16 est sans doute lun des plus importants, puisquil prsente tous les conteneurs fournis par la librairie
207
standard. Enn, le Chapitre 17 dcrit les principaux algorithmes fournis par la librairie, 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 compilateurs actuels. Il est donc fortement probable que bon nombre dexemples fournis ici ne seront pas utilisables tels quels sur les environnement de dveloppement existants sur le march, bien que lon commence voir apparatre des compilateurs implmentant quasiment la totalit 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 librairie standard seront rellement portables au niveau source.
208
209
Par exemple, on peut rcrire notre tout premier programme que lon a fait la Section 1.9 de la manire suivante :
#include <cstdio> 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-ttes classiques de la librairie C, soit inclure une directive using namespace std; pour intgrer les fonctionnalits de la librairie 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 librairie C ont t encapsules dans des fonctionnalits quivalentes de la librairie 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 librairie 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,
210
= 0, = 1, = 2, = 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() ; 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.
211
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 de chiffres maximum que les nombres en base 10 peuvent avoir en tant sr de pouvoir tre stock tel quel.
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.
212
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 lorsquon 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 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 manque de mmoire lorsque loprateur new ou loprateur new[] nont 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() ; };
Les autres exceptions sont dclares classes en deux grandes catgories. La premire catgorie regroupe toutes les exceptions dont loccurrence 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) ; };
213
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 librairie standard an de faciliter la manipulation des chanes de caractres, et sera dcrite plus en dtail dans la Section 13.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 lorsque lon 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 prvues pour elle ; la classe out_of_range, qui spcie quune valeur situe en dehors de la plage de valeur autorise 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 dexception 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 valeur dans laquelle elle devait se trouver, suite dbordement interne la librairie ; la classe overow_error, qui signie quun dbordement par valeurs suprieures sest produit dans un calcul interne la librairie ; la classe underow_error, qui signie quun dbordement par valeurs infrieures sest produit dans un calcul interne la librairie.
214
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 librairie 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 standard (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, et 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 la librairie standard qui manipulent les squences de caractres doivent donc stocker le contexte
215
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)
216
{ 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 ; 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 librairie standard, et nont donc quun intrt limit pour les programmeurs, mais il est important de savoir quelles existent.
217
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 leurs 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 conteneurs 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 un itrateur, la validit de celui-ci est restreinte. Premirement, ils deviennent obligatoirement invalides ds lors que le conteneur auquel ils permettent daccder est dtruit. De plus, les conteneurs stockent leurs donnes de manire dynamique, et sont susceptibles de rorganiser leurs donnes 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.
218
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, et 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 effectuer plusieurs parcours, par exemple en copiant la valeur initiale de litrateur et de parcourir le conteneur plusieurs fois avec chaque copie effectue ; 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 passe, 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.
219
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 des diffrences 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 librairie 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 pour les types 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 pour les itrateurs par la librairie 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 };
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 ;
220
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 librairie 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 mthodes 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_ype 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 sacrier les performances.
221
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 ces itrateurs. 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 12-2. Itrateurs de ux dentre
#include <iostream> #include <iterator> using namespace std; int main(void) { double somme = 0; istream_iterator<double, char> is(cin); while (is != istream_iterator<double, char>()) { somme = somme + *is; is++; }
222
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, et 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. Exemple 12-3. Itrateur de ux de sortie
#include <iostream> #include <iterator> using namespace std;
223
const char *texte[6] = { "Bonjour", "tout", "le", "monde", "!", NULL }; int main(void) { ostream_iterator<const 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, et 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 et de sassurer que cette valeur est toujours drfrenable laide de loprateur *. La raison de cette particularit est que le contenu du tampon peut tre modi suite lappel de loprateur ++(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 14.2.
224
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 drfrences sont traites par loprateur = de litrateur. Cest 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) ; };
225
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> { 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 litrateur. La librairie 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 dafcher le contenu de cette liste. Exemple 12-4. Itrateur dinsertion
#include <iostream> #include <list>
226
#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); // 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; } return 0; }
La manire dutiliser le conteneur de type list sera dcrite en dtail dans le Chapitre 16.
227
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 ; 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 + et - sont galement dnis dans cette 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 avait rfrenc. 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 le dernier lment pass 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.
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.
228
229
Les algorithmes de la librairie 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 librairie standard ne fait donc pas dautre hypothses sur leur nature. Cependant, il est ncessaire de donner que des foncteurs en paramtres aux algorithmes de la librairie standard, pas de fonctions. Cest pour cette raison que la librairie standard dnit un certain nombre de foncteurs standard an de faciliter la tche du programmeur.
Ces classes sont dnies dans len-tte functional. La librairie dnit galement un certain nombre de foncteurs standard qui encapsulent les oprateurs du langage dans cette 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 ; };
230
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 ; }; 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>
231
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 dappliquer les principaux oprateurs du langage comme des fonctions classiques dans les algorithmes de la librairie standard. Exemple 12-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 // 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 librairie standard fournit donc les adaptateurs suivants, qui permettent de convertir respectivement nimporte quelle fonction unaire ou binaire en foncteurs :
template <class Arg, class Result>
232
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 12-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; } int main(void) {
233
// 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 fait, 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 librairie 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> {
234
public: explicit mem_fun_ref_t(Result (Class::*methode)()) ; 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 8 adaptateurs au total permettant de construire des foncteurs partir des fonctions membres de classes. Pour diminuer cette complexit, la librairie 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 fonctions membres utiliss. Il est fortement recommand de les utiliser plutt que de chercher construire ces objets manuellement.
235
La librairie standard fournit des prdicats prdnis, qui effectuent les oprations logiques des oprateurs logiques 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 ; }; 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 vu dans la section prcdente. La librairie 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) ;
236
Les fonctions not1 et not2 servent faciliter la construction dun prdicat inverse pour les prdicats unaires et binaires.
237
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 12-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( 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; }
238
239
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 librairie 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 spcie la mthode deallocate doit tre exactement que celle utilise pour lallocation dans lappel correspondant allocate. Autrement dit, lallocateur ne mmorise pas lui-mme la taille des blocs mmoire quil a fourni.
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.
240
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 maximum 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 ladresse indique en paramtre. Elles doivent tre utilises lorsque lon 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.
241
{ // 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 librairie 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 dallocation mmoire pourra, sil le dsire, effectuer les allocations de mmoire une bonne fois pour toutes grce lallocateur standard, et de neffectuer que les oprations de construction et de destruction des objets lorsque cela est ncessaire. En procdant ainsi, le temps pass dans les routines de gestion de la mmoire est limin, et lalgorithme sera 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 librairie 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 librairie 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 ;
242
toutes les instances dun allocateur dun type donn permettent galement daccder la mme mmoire. Ceci 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 ; toutes les mthodes de lallocateur doivent sexcuter dans un temps amorti constant (ceci 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 librairie standard, mais il est plus simple de la gnraliser tous les cas dutilisation.
Pour terminer ce tour dhorizon des allocateurs, sachez que la librairie 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 librairie standard. De mme, la librairie 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.
243
compatibilit quasi-totale avec les chanes de caractres C standard ; 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 librairie standard, en particulier au niveau des ux dentre / sortie.
Comme il la t dit plus haut, la classe basic_string est une classe template. Ceci signie quelle est capable de prendre en charge des chanes de nimporte quel type de caractres. Pour cela, elle ne se base que sur la classe des traits du type des caractres manipuls. Il est donc parfaitement possible de lutiliser avec des types dnis par lutilisateur, pourvu que la classe des traits des caractres soient dnis pour ces types. Bien entendu, la classe basic_string peut tre utilise avec les types 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
244
traits traits_type ; typename traits::char_type value_type ; Allocator allocator_type ; typename Allocator::size_type size_type ; typename Allocator::difference_type difference_type ; typename Allocator::reference reference_type ; typename Allocator::const_reference const_reference ; typename Allocator::pointer pointer ; typename Allocator::const_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 fin = 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 ;
245
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, 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 basic_string &insert(size_type size_type debut, size_type basic_string &insert(size_type
position, const basic_string &source) ; position, const basic_string &source, nombre) ; position, const charT *chaine,
246
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 fin = 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) ; 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 ;
247
// 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, 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 ;
248
Les oprateurs de concatnation, de comparaison et de srialisation dans les ux dentre / sortie sont galement dnis dans cette 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 librairie standard dnit deux types chanes de caractres pour les types 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 libres dutiliser des instances de la classe basic_string pour dautres types de caractres.
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 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" ;
249
a strictement le mme effet que celui de lexemple prcdent. Enn, il existe un constructeur dont le but est dinitialiser basic_string avec une suite de caractres identiques et 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 identique. En pratique donc, il est trs rare davoir spcier un allocateur mmoire dans ces constructeurs.
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 pas souvent 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 manoeuvre, pour le cas o la chane devrait tre agrandie la suite dune opration particulire. Ceci permet de rduire les rallocation 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 de la mthode capacity. Nous verrons comment rserver de la place mmoire en prvision dun redimensionnement ultrieur dans la section suivante.
250
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.
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 de chanes de caractres de la librairie C. Elle est dnie dans le chier den-tte string.h (que lon ne confondra pas avec len-tte string de la librairie standard C++), et renvoie la longueur de la chane de caractres qui lui est fournie en paramtre.
251
Si la valeur par dfaut utilise pour les caractres complmentaires dans la mthode resize nest pas celle qui est dsire, il faut indiquer en utiliser une autre version. Cette deuxime version prend, en plus 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 librairie, et le programmeur na en gnral pas sen soucier. Cependant, il peut exister des situations o lon sait lavance la taille minimum 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. Ceci est ralisable laide de la mthode reserve. Cette mthode prend en paramtre la capacit minimum 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 13-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.
252
Les caractres des basic_string peuvent tre accdes de nombreuses manires. Premirement, elle rednit loprateur daccs aux lments dun tableau, que lon utilisera 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> 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, qui, contrairement loprateur tableau, permet deffectuer un contrle sur la validit de lindice utilis. Il sagit de la mthode at. Cette mthode 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) ; }
253
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. 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 13-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. Ceci est normal, car il est absolument interdit de modier les donnes internes la basic_string qui a fourni ces pointeurs.
254
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.
255
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. Exemple 13-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 :
256
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. La mthode copy permet dobtenir la copie dune sous-chane de la chane contenue dans la basic_string. Cependant, 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.
257
258
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. Exemple 13-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 supprimer de la basic_string 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-chanes 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 13-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);
259
cout << s << endl; // Efface la chane de caractre complte : s.clear(); if (s.empty()) cout << "Vide !" << 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 caractre, 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.
260
261
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 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 donns 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 15.
262
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 suivant 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 13-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.
rfind
Cette mthode permet deffectuer une recherche similaire celle de la mthode find, mais en parcourant la chane de caractre
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.
find_first_of
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.
find_last_of
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.
263
Mthode
find_first_not_of
find_last_not_of
264
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 nationnaux de lutilisateur. La raison de ce choix est essentiellement la recherche de lefcacit dans la librairie standard. Nous verrons dans le Chapitre 15 la manire de procder pour prendre en compte les paramtres nationnaux au niveau des chanes de caractres.
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 librairie 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 lectures de textes constitus en lignes, la librairie 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.
265
266
auto_ptr(const auto_ptr &source) throw() ; template <class U> auto_ptr(const auto_ptr<U> &source) throw() ; ~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. Ceci 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 soit-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 13-15. Utilisation des pointeurs automatiques
#include <iostream> #include <memory> using namespace std; class A { public: A() {
267
cout << "Constructeur" << endl; } ~A() { cout << "Destructeur" << endl; } }; // Fonction susceptible de lancer une exception : 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. Ceci 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 pas a priori 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 au-
268
tomatiques ne devront jamais tre initialiss avec des pointeurs obtenus lors dune allocation dynamique avec loprateur new[] ou avec la fonction malloc de la librairie C.
Il est possible de rcuprer la valeur du pointeur pris en charge par un pointeur automatique simplement, grce la mthode get. Ceci 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. Exemple 13-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;
269
return 0; }
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 quantiques. 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 librairie 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.
270
o relle est la valeur de la partie relle, et imaginaire la valeur de la partie imaginaire. En France, il est galement trs courant 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
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 complexes, 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 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 ))
271
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. En mathmatiques, il est galement courant dutiliser un autre systme de coordonnes : le systme de coordonnes polaire. 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 notes 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, ou, 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).
272
#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 standard, qui formate un nombre complexe en utilisant la notation (rel,imaginaire). Il existe galement une surcharge de loprateur dentre pour les ux dentre :
#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 librairie C lorsque lon 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
273
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 ; }
Enn, outre les oprateurs arithmtiques externes et les fonctions standard, plusieurs fonctions spciques aux complexes sont dnies par la librairie standard. Ces fonctions permettent de manipuler les complexes et de leur appliquer les oprations qui leurs sont propres. Ces fonctions sont rcapitules dans le tableau suivant : Tableau 13-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.
274
Fonction
conj
Description 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 (ceci 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.
polar
275
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 les accolant les uns aux 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 performances 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. Ceci se traduit souvent par une augmentation de la frquence de ce processeur, technique que tout le monde connat. Les avantages de cette solution sont vident : 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 oeuvre 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 oeuvre dans les processeurs modernes, disposant 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
276
paralllisme de donnes ncessite la dnition de types de donnes complexes, capables de reprsenter 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 plate-forme utilise. La librairie standard C++ permet dviter ces cueils, car elle dnit un type de donnes 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.
277
valarray &source) ; mask_array<T> &source) ; indirect_array<T> &source) ; slice_array<T> &source) ; gslice_array<T> &source) ;
// Oprateurs daffectation : valarray<T> &operator=(const valarray<T> &operator=(const valarray<T> &operator=(const valarray<T> &operator=(const valarray<T> &operator=(const valarray<T> &operator=(const
T &valeur) ; valarray<T> &source) ; 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 !()
// Oprateurs daffectation compose : valarray<T> operator*=(const T &valeur) ; valarray<T> operator*=(const valaray<T> &tableau) ; valarray<T> operator/=(const T &valeur) ; valarray<T> operator/=(const valaray<T> &tableau) ; valarray<T> operator%=(const T &valeur) ; valarray<T> operator%=(const valaray<T> &tableau) ; valarray<T> operator+=(const T &valeur) ; valarray<T> operator+=(const valaray<T> &tableau) ; valarray<T> operator-=(const T &valeur) ; valarray<T> operator-=(const valaray<T> &tableau) ; valarray<T> operator^=(const T &valeur) ; valarray<T> operator^=(const valaray<T> &tableau) ; valarray<T> operator&=(const T &valeur) ;
278
operator&=(const valaray<T> &tableau) ; operator|=(const T &valeur) ; operator|=(const valaray<T> &tableau) ; operator<<=(const T &valeur) ; operator<<=(const valaray<T> &tableau) ; operator>>=(const T &valeur) ; operator>>=(const valaray<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()) ; };
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(int)) ; // Construit un valarray de 10 entiers initialiss 2 : 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 affecter aux nouveaux lments, dans le cas dun agrandissement. 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.
279
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 : puisque lopration du type de base est applique simplement chaque lment contenu dans le tableau de valeurs. La librairie 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 leur 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 13-19. 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) {
280
// 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 nombres 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. Exemple 13-20. 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) {
281
// 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); // ffectue un dcalage gauche de deux positions : res = vect1.shift(2); affiche(res); // ffectue une rotation de 2 positions vers la droite : res = vect1.cshift(-2); affiche(res); return 0; }
Enn, il existe deux mthodes apply surcharges 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.
282
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 de possibilit doprations. En fait, ses instances ne doivent tre utiliss que pour effectuer des oprations sur les lments du tableau slectionn par le masque fourni loprateur []. Les oprations ralisables seront dcrites dans la Section 13.4.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 13-21. 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; }
283
La librairie standard C++ fournit donc un autre mcanisme de slection, qui est 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 13-22. 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.
284
dindices dcrits de manire implicite. La librairie 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, ainsi que le nombre de valeurs gnrer pour chaque variable ainsi que le pas qui sparent ces valeurs. Les variables de contrle commencent toutes leurs itrations 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 ; };
285
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); 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,
286
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 un classe gslice. La librairie 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.
287
};
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 une mme valeur tous.
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.
288
14.3. Les classes de base : ios_base et basic_ios 14.4. Flux dentre 14.5. Flux de sortie 14.6. Flux dentre / sortie
289
290
291
292
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. Ceci 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. Ceci 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. Lannexe B dcrit lorganisation gnrale de ce document et donne quelques renseignements pour faciliter leur lecture. Bonne continuation. . .
293
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 dobjet Oprateur de cration dynamique de tableaux Oprateur de destruction dobjet cr dynamiquement Oprateur de destruction de tableaux crs dynamiquement Oprateur dincrmentation prxe Oprateur de dcrmentation prxe Oprateur de drfrencement Oprateur dadresse Oprateur plus unaire Oprateur moins 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
294
Oprateur
static_cast .* ->* * / % + << >> < > <= >= == != & ^ | && || ?: = *= /= %= += -= <<= >>= &= |= ^=
Nom ou signication 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 modulo 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
295
Oprateur
,
296
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 donnes non statiques 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 pointeurs sur des membres non statiques, pas de rfrences, 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.
297
298
The "Cover Texts" are certain short passages of text that are listed, as Front-Cover Texts or BackCover Texts, in the notice that says that the Document is released under this License. A "Transparent" copy of the Document means a machine-readable copy, represented in a format whose specication is available to the general public, whose contents can be viewed and edited directly and straightforwardly with generic text editors or (for images composed of pixels) generic paint programs or (for drawings) some widely available drawing editor, and that is suitable for input to text formatters or for automatic translation to a variety of formats suitable for input to text formatters. A copy made in an otherwise Transparent le format whose markup has been designed to thwart or discourage subsequent modication by readers is not Transparent. A copy that is not "Transparent" is called "Opaque". Examples of suitable formats for Transparent copies include plain ASCII without markup, Texinfo input format, LaTeX input format, SGML or XML using a publicly available DTD, and standardconforming simple HTML designed for human modication. Opaque formats include PostScript, PDF, proprietary formats that can be read and edited only by proprietary word processors, SGML or XML for which the DTD and/or processing tools are not generally available, and the machinegenerated HTML produced by some word processors for output purposes only. The "Title Page" means, for a printed book, the title page itself, plus such following pages as are needed to hold, legibly, the material this License requires to appear in the title page. For works in formats which do not have any title page as such, "Title Page" means the text near the most prominent appearance of the works title, preceding the beginning of the body of the text. 2. VERBATIM COPYING You may copy and distribute the Document in any medium, either commercially or noncommercially, provided that this License, the copyright notices, and the license notice saying this License applies to the Document are reproduced in all copies, and that you add no other conditions whatsoever to those of this License. You may not use technical measures to obstruct or control the reading or further copying of the copies you make or distribute. However, you may accept compensation in exchange for copies. If you distribute a large enough number of copies you must also follow the conditions in section 3. You may also lend copies, under the same conditions stated above, and you may publicly display copies. 3. COPYING IN QUANTITY If you publish printed copies of the Document numbering more than 100, and the Documents license notice requires Cover Texts, you must enclose the copies in covers that carry, clearly and legibly, all these Cover Texts: Front-Cover Texts on the front cover, and Back-Cover Texts on the back cover. Both covers must also clearly and legibly identify you as the publisher of these copies. The front cover must present the full title with all words of the title equally prominent and visible. You may add other material on the covers in addition. Copying with changes limited to the covers, as long as they preserve the title of the Document and satisfy these conditions, can be treated as verbatim copying in other respects. If the required texts for either cover are too voluminous to t legibly, you should put the rst ones listed (as many as t reasonably) on the actual cover, and continue the rest onto adjacent pages.
299
If you publish or distribute Opaque copies of the Document numbering more than 100, you must either include a machine-readable Transparent copy along with each Opaque copy, or state in or with each Opaque copy a publicly-accessible computer-network location containing a complete Transparent copy of the Document, free of added material, which the general network-using public has access to download anonymously at no charge using public-standard network protocols. If you use the latter option, you must take reasonably prudent steps, when you begin distribution of Opaque copies in quantity, to ensure that this Transparent copy will remain thus accessible at the stated location until at least one year after the last time you distribute an Opaque copy (directly or through your agents or retailers) of that edition to the public. It is requested, but not required, that you contact the authors of the Document well before redistributing any large number of copies, to give them a chance to provide you with an updated version of the Document. 4. MODIFICATIONS You may copy and distribute a Modied Version of the Document under the conditions of sections 2 and 3 above, provided that you release the Modied Version under precisely this License, with the Modied Version lling the role of the Document, thus licensing distribution and modication of the Modied Version to whoever possesses a copy of it. In addition, you must do these things in the Modied Version: A. Use in the Title Page (and on the covers, if any) a title distinct from that of the Document, and from those of previous versions (which should, if there were any, be listed in the History section of the Document). You may use the same title as a previous version if the original publisher of that version gives permission. B. List on the Title Page, as authors, one or more persons or entities responsible for authorship of the modications in the Modied Version, together with at least ve of the principal authors of the Document (all of its principal authors, if it has less than ve). C. State on the Title page the name of the publisher of the Modied Version, as the publisher. D. Preserve all the copyright notices of the Document. E. Add an appropriate copyright notice for your modications adjacent to the other copyright notices. F. Include, immediately after the copyright notices, a license notice giving the public permission to use the Modied Version under the terms of this License, in the form shown in the Addendum below. G. Preserve in that license notice the full lists of Invariant Sections and required Cover Texts given in the Documents license notice. H. Include an unaltered copy of this License. I. Preserve the section entitled "History", and its title, and add to it an item stating at least the title, year, new authors, and publisher of the Modied Version as given on the Title Page. If there is no section entitled "History" in the Document, create one stating the title, year, authors, and
300
publisher of the Document as given on its Title Page, then add an item describing the Modied Version as stated in the previous sentence. J. Preserve the network location, if any, given in the Document for public access to a Transparent copy of the Document, and likewise the network locations given in the Document for previous versions it was based on. These may be placed in the "History" section. You may omit a network location for a work that was published at least four years before the Document itself, or if the original publisher of the version it refers to gives permission. K. In any section entitled "Acknowledgements" or "Dedications", preserve the sections title, and preserve in the section all the substance and tone of each of the contributor acknowledgements and/or dedications given therein. L. Preserve all the Invariant Sections of the Document, unaltered in their text and in their titles. Section numbers or the equivalent are not considered part of the section titles. M. Delete any section entitled "Endorsements". Such a section may not be included in the Modied Version. N. Do not retitle any existing section as "Endorsements" or to conict in title with any Invariant Section. If the Modied Version includes new front-matter sections or appendices that qualify as Secondary Sections and contain no material copied from the Document, you may at your option designate some or all of these sections as invariant. To do this, add their titles to the list of Invariant Sections in the Modied Versions license notice. These titles must be distinct from any other section titles. You may add a section entitled "Endorsements", provided it contains nothing but endorsements of your Modied Version by various partiesfor example, statements of peer review or that the text has been approved by an organization as the authoritative denition of a standard. You may add a passage of up to ve words as a Front-Cover Text, and a passage of up to 25 words as a Back-Cover Text, to the end of the list of Cover Texts in the Modied Version. Only one passage of Front-Cover Text and one of Back-Cover Text may be added by (or through arrangements made by) any one entity. If the Document already includes a cover text for the same cover, previously added by you or by arrangement made by the same entity you are acting on behalf of, you may not add another; but you may replace the old one, on explicit permission from the previous publisher that added the old one. The author(s) and publisher(s) of the Document do not by this License give permission to use their names for publicity for or to assert or imply endorsement of any Modied Version. 5. COMBINING DOCUMENTS You may combine the Document with other documents released under this License, under the terms dened in section 4 above for modied versions, provided that you include in the combination all of the Invariant Sections of all of the original documents, unmodied, and list them all as Invariant Sections of your combined work in its license notice. The combined work need only contain one copy of this License, and multiple identical Invariant Sections may be replaced with a single copy. If there are multiple Invariant Sections with the same
301
name but different contents, make the title of each such section unique by adding at the end of it, in parentheses, the name of the original author or publisher of that section if known, or else a unique number. Make the same adjustment to the section titles in the list of Invariant Sections in the license notice of the combined work. In the combination, you must combine any sections entitled "History" in the various original documents, forming one section entitled "History"; likewise combine any sections entitled "Acknowledgements", and any sections entitled "Dedications". You must delete all sections entitled "Endorsements." 6. COLLECTIONS OF DOCUMENTS You may make a collection consisting of the Document and other documents released under this License, and replace the individual copies of this License in the various documents with a single copy that is included in the collection, provided that you follow the rules of this License for verbatim copying of each of the documents in all other respects. You may extract a single document from such a collection, and distribute it individually under this License, provided you insert a copy of this License into the extracted document, and follow this License in all other respects regarding verbatim copying of that document. 7. AGGREGATION WITH INDEPENDENT WORKS A compilation of the Document or its derivatives with other separate and independent documents or works, in or on a volume of a storage or distribution medium, does not as a whole count as a Modied Version of the Document, provided no compilation copyright is claimed for the compilation. Such a compilation is called an "aggregate", and this License does not apply to the other self-contained works thus compiled with the Document, on account of their being thus compiled, if they are not themselves derivative works of the Document. If the Cover Text requirement of section 3 is applicable to these copies of the Document, then if the Document is less than one quarter of the entire aggregate, the Documents Cover Texts may be placed on covers that surround only the Document within the aggregate. Otherwise they must appear on covers around the whole aggregate. 8. TRANSLATION Translation is considered a kind of modication, so you may distribute translations of the Document under the terms of section 4. Replacing Invariant Sections with translations requires special permission from their copyright holders, but you may include translations of some or all Invariant Sections in addition to the original versions of these Invariant Sections. You may include a translation of this License provided that you also include the original English version of this License. In case of a disagreement between the translation and the original English version of this License, the original English version will prevail. 9. TERMINATION You may not copy, modify, sublicense, or distribute the Document except as expressly provided for under this License. Any other attempt to copy, modify, sublicense or distribute the Document is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance.
302
10. FUTURE REVISIONS OF THIS LICENSE The Free Software Foundation may publish new, revised versions of the GNU Free Documentation License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. See http://www.gnu.org/copyleft/. Each version of the License is given a distinguishing version number. If the Document species that a particular numbered version of this License "or any later version" applies to it, you have the option of following the terms and conditions either of that specied version or of any later version that has been published (not as a draft) by the Free Software Foundation. If the Document does not specify a version number of this License, you may choose any version ever published (not as a draft) by the Free Software Foundation.
303
BIBLIOGRAPHIE
Les titres marqus dun astrisque sont vraiment bons (au niveau pdagogique).
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++, ISO.
304