Sunteți pe pagina 1din 280

Date de publication :

Dernière mise à jour : 25/09/2009

Cette FAQ a été réalisée à partir des questions fréquemment posées sur les forums de
http://www.developpez.com et de l'expérience personnelle des auteurs.

Je tiens à souligner que cette faq ne garantit en aucun cas que les informations qu'elle
propose sont correctes ; les auteurs font le maximum, mais l'erreur est humaine. Cette
faq ne prétend pas non plus être complète. Si vous trouvez une erreur ou si vous
souhaitez devenir rédacteur, lisez Comment participer à cette FAQ ?.

Sur ce, nous vous souhaitons une bonne lecture.


Ont contribué à cette FAQ :

Alp Mestan - Clément Cunin - LFE - pipin - Bob - HRS


- Gilles Louïse - ovh - Musaran - dj.motte - JEG - haypo
- Anomaly - Marshall Cline - Alacazam - Luc Hermitte
- Laurent Gomila - Aurélien Regat-Barrel - JolyLoic
- superspag - Jean-Marc.Bourguet - gege2061 - r0d -
screetch - Medinoc - Davidbrcz - Mat007 - Herb Sutter -
white_tentacle - 3DArchi - Florian Goujeon - Arzar - dourouc05 -
1. Information générale (6) ............................................................................................................................................................4
2. Généralités sur le C++ (22) .......................................................................................................................................................7
2.1. Guide de démarrage (7) ..................................................................................................................................................11
2.2. Bibliothèques complémentaires (4) ................................................................................................................................ 14
2.3. Organisation du code source (6) .................................................................................................................................... 17
3. Caractéristiques du langage (7) ............................................................................................................................................... 20
4. Programmation objet en C++ (25) .......................................................................................................................................... 23
5. Les classes en C++ (69) ..........................................................................................................................................................39
5.1. Les constructeurs (15) .................................................................................................................................................... 52
5.2. Les destructeurs (11) ...................................................................................................................................................... 67
5.3. Les amis (friend) (5) ...................................................................................................................................................... 73
5.4. Les données et fonctions membres statiques (9) ........................................................................................................... 76
5.5. Sémantique de copie (8) .................................................................................................................................................85
6. L'héritage (13) ..........................................................................................................................................................................90
7. Les fonctions (23) ..................................................................................................................................................................106
7.1. Les fonctions membres virtuelles (6) ...........................................................................................................................114
7.2. Les fonctions inline (8) ................................................................................................................................................ 120
8. Les références (6) .................................................................................................................................................................. 125
9. Les opérateurs (19) ................................................................................................................................................................ 129
9.1. Les conversions de types (3) ........................................................................................................................................130
9.2. La surcharge d'opérateurs (16) ..................................................................................................................................... 134
10. Gestion dynamique de la mémoire (18) ..............................................................................................................................148
11. Les namespaces (5) ..............................................................................................................................................................164
12. Utilisation des exceptions (12) ............................................................................................................................................168
13. Les chaînes de caractères (30) ............................................................................................................................................ 179
14. Entrées / sorties avec les flux (29) ......................................................................................................................................197
14.1. Manipulation de la console (6) ...................................................................................................................................207
14.2. Manipulation des fichiers (11) ................................................................................................................................... 211
15. Les templates (15) ............................................................................................................................................................... 219
16. La STL (15) ......................................................................................................................................................................... 236
17. Boost (11) ............................................................................................................................................................................ 247
18. Système (2) .......................................................................................................................................................................... 259
19. Divers (18) ........................................................................................................................................................................... 261
20. Problèmes avec les compilateurs (10) .................................................................................................................................277

-3-
Les sources présentées sur cette pages sont libres de droits, et vous pouvez les utiliser à votre convenance. Par contre la page de présentation de ces sources
constitue une oeuvre intellectuelle protégée par les droits d'auteurs. Copyright © 2008 Developpez LLC. Tout droits réservés Developpez LLC. Aucune
reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents et images sans l'autorisation expresse de Developpez
LLC. Sinon vous encourez selon la loi jusqu'à 3 ans de prison et jusqu'à 300 000 E de dommages et intérêts.
http://cpp.developpez.com/faq/cpp/
Sommaire > Information générale
Comment bien utiliser cette FAQ ?
Auteurs : Clément Cunin ,
Le but : Cette FAQ a été conçue pour être la plus simple possible d'utilisation. Elle tente d'apporter des réponses simples
et complètes aux questions auxquelles sont confrontés tous les débutants (et les autres).

L'organisation : Les questions sont organisées par thème, les thèmes pouvant eux-mêmes contenir des sous thèmes.
Lorsqu'une question porte sur plusieurs thèmes, celle-ci est insérée dans chacun des thèmes, rendant la recherche plus
facile.

Les réponses : Les réponses contiennent des explications et des codes sources. Certaines sont complétées de fichiers
à télécharger contenant un programme de démonstration. Ces programmes sont volontairement très simples afin qu'il
soit aisé de localiser le code intéressant. Les réponses peuvent également être complétées de liens vers d'autres réponses
ou vers un autre site en rapport.

Nouveautés et mises à jour : Lors de l'ajout ou de la modification d'une question/réponse, un indicateur est placé à côté
du titre de la question. Cet indicateur reste visible pour une durée de 15 jours afin de vous permettre de voir rapidement
les modifications apportées.

J'espère que cette faq pourra répondre à vos questions. N'hésitez pas à nous faire part de tout commentaires/remarques/
critiques.

lien : Comment participer à cette FAQ ?

La question que je me pose ne se trouve pas dans cette FAQ, que faire ?
Auteurs : LFE ,
Si la question que vous vous posez ne se trouve pas dans cette FAQ, il est possible que, s'il ne s'agit pas d'une question
spécifique au C++, elle soit traitée dans la FAQ C. S'il s'agit par contre d'une question qui a sa place dans cette FAQ,
je vous invite à m'envoyer un message privé avec la question ET la réponse correspondante et je me ferai un plaisir de
l'intégrer lors de la prochaine mise à jour.

Comment participer à cette FAQ ?


Auteurs : Clément Cunin ,
Cette FAQ est ouverte à toute collaboration. Pour éviter la multiplication des versions, il serait préférable que toutes
collaborations soient transmises aux administrateurs de la faq.
Plusieurs compétences sont actuellement recherchées pour améliorer cette faq :

Rédacteur : Bien évidemment, toute nouvelle question/réponse est la bienvenue.

Web designer : Toute personne capable de faire une meilleure mise en page, une feuille de style ou de belles images...

-4-
Les sources présentées sur cette pages sont libres de droits, et vous pouvez les utiliser à votre convenance. Par contre la page de présentation de ces sources
constitue une oeuvre intellectuelle protégée par les droits d'auteurs. Copyright © 2008 Developpez LLC. Tout droits réservés Developpez LLC. Aucune
reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents et images sans l'autorisation expresse de Developpez
LLC. Sinon vous encourez selon la loi jusqu'à 3 ans de prison et jusqu'à 300 000 E de dommages et intérêts.
http://cpp.developpez.com/faq/cpp/
Correcteur : Malgré nos efforts des fautes d'orthographe ou de grammaire peuvent subsister. Merci de contacter les
administrateurs si vous en débusquez une... Idem pour les liens erronés.

lien : Quels sont les droits de reproduction de cette FAQ ?

Quels sont les droits de reproduction de cette FAQ ?


Auteurs : Clément Cunin ,
La FAQ est publiée sous licence FDL.,

Bien que la licence ne l'oblige pas, il est recommandé de contacter les auteurs avant de publier une copie de ce document,
voir Comment participer à cette FAQ ?.

lien : Licence en anglais


lien : Comment participer à cette FAQ ?

Remerciements
Auteurs : Clément Cunin , LFE , Alacazam ,
Un grand merci à tous ceux qui ont pris de leur temps pour la réalisation de cette FAQ.

Aux rédacteurs : Remerciements tout d'abord à tous ceux qui ont rédigé les questions et les réponses.
,.

Aux correcteurs : Remerciements également aux personnes qui ont relu les textes pour supprimer un maximum de
fautes de français, tout particulièrement à . .

Aux visiteurs : Remerciements enfin à tous ceux qui ont consulté cette FAQ, et qui, par leurs remarques, nous ont aidé
à la perfectionner.

Un merci tout particulier Un merci tout particulier à qui nous a créé de superbes outils très utiles pour générer ces FAQ.

Je tiens aussi à remercier Marshall Cline pour l'autorisation qu'il m'a donnée de traduire et d'intégrer à cette FAQ son
document C++ FAQ LITE. La traduction qui a été faite tente de garder l'esprit du document original, mais je ne
maîtrise pas suffisamment la langue anglaise pour être à l'abri d'une erreur.

Que signifie exactement "mauvais" dans cette FAQ ?


Auteurs : Marshall Cline ,
Cela signifie que c'est quelque chose que vous devez éviter la plupart du temps, mais pas tout le temps. Par exemple,
vous finirez par utiliser la solution "mauvaise" quand elle est la moins mauvaise solution possible. C'est de l'humour.
Ne le prenez pas au premier degré.

La véritable raison de l'utilisation de ce terme (je vous entends dire : "il y a un motif réel caché derrière cela". Et bien
c'est tout à fait le cas) est de pousser les nouveaux programmeurs C++ à remettre en question leurs vieilles habitudes.
Par exemple, les programmeurs C qui passent au C++ utilisent souvent des pointeurs, des tableaux, des #define plus
que nécessaire. Cette FAQ liste ces choses comme "mauvaises" afin de pousser ces nouveaux programmeurs C++

-5-
Les sources présentées sur cette pages sont libres de droits, et vous pouvez les utiliser à votre convenance. Par contre la page de présentation de ces sources
constitue une oeuvre intellectuelle protégée par les droits d'auteurs. Copyright © 2008 Developpez LLC. Tout droits réservés Developpez LLC. Aucune
reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents et images sans l'autorisation expresse de Developpez
LLC. Sinon vous encourez selon la loi jusqu'à 3 ans de prison et jusqu'à 300 000 E de dommages et intérêts.
http://cpp.developpez.com/faq/cpp/
dans la bonne direction. Le but des métaphores du genre "les pointeurs sont mauvais" est de convaincre les nouveaux
programmeurs C++ que le C++ n'est pas "simplement du C avec les commentaires //".

Un peu plus sérieusement, je ne veux pas dire que les macros, les tableaux, les pointeurs sont criminels comme un
meurtre ou un enlèvement. Bien que pour les pointeurs ... (ceci est une PLAISANTERIE). "Mauvais" , dans le cas
présent, veut dire "choquant". Ne cherchez donc pas une définition technique pour savoir ce qui est "mauvais" ou ne
l'est pas.

-6-
Les sources présentées sur cette pages sont libres de droits, et vous pouvez les utiliser à votre convenance. Par contre la page de présentation de ces sources
constitue une oeuvre intellectuelle protégée par les droits d'auteurs. Copyright © 2008 Developpez LLC. Tout droits réservés Developpez LLC. Aucune
reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents et images sans l'autorisation expresse de Developpez
LLC. Sinon vous encourez selon la loi jusqu'à 3 ans de prison et jusqu'à 300 000 E de dommages et intérêts.
http://cpp.developpez.com/faq/cpp/
Sommaire > Généralités sur le C++
Que faut-il savoir avant de commencer en C++ ?
Auteurs : Aurélien Regat-Barrel ,
Vous avez décidé de vous lancer dans l'aventure C++ et nous vous en félicitons. Mais avant de démarrer votre apprentissage,
il est bon de lire ce qui suit afin de partir sur de bonnes bases.

Le C++ est un langage dit "libre", c'est-à-dire non propriétaire, et soumis à une standardisation par un organisme
indépendant : l'ISO (voir Le C++ est-il normalisé ?). Le but de ce standard est de permettre la portabilité d'un
programme, moyennant la recompilation de son code source. Le C++ est en effet conçu pour être compilé en un
exécutable spécifique à une plateforme donnée. On ne parle donc pas de script ni de commande C++, mais de code
source et d'instruction. Le C++ n'est pas non plus un programme, un environnement ou un éditeur de logiciel. Ainsi,
on ne développe pas avec le C++ de Microsoft, mais en C++ avec le compilateur de Microsoft.

Ce langage a été initialement développé comme extension du langage C par Bjarne Stroustrup au sein de Bell Labs,
au début des années 1980. Ce dernier y a développé le premier compilateur C++ (nommé cfront), qui a évolué au fil
du temps. Petit à petit, cette extension du C est devenue un langage à part entière, réellement différent du langage C.
Aussi, ne vous y méprenez pas : programmer en C++ ne se résume pas à faire du C avec des classes, loin de là ! Ce sont
deux langages très différents, qu'il faut distinguer. De plus, beaucoup de spécialistes s'accordent à dire qu'il n'est pas
nécessaire d'apprendre le C avant le C++, voire même qu'il ne faut pas apprendre le C.

Ce n'est qu'en 1998 que le langage C++ a été normalisé, soit une quinzaine d'années après le début de son développement.
Or, la communauté C++ était déjà très importante à ce moment, et la plupart des compilateurs actuels existaient déjà
(dans d'anciennes versions) et ne pouvaient donc pas respecter une norme postérieure à leur sortie. Chacun d'entre-
eux évoluant indépendamment des autres, on fait donc une distinction importante au niveau des compilateurs et de
leur version, et non au niveau du langage (comme pour PHP4, PHP5, ...) qui reste toujours le même (celui défini par
la norme).

Cela implique deux choses importantes :


- Un code C++ valide vis à vis de la norme peut parfaitement ne pas compiler sur la plupart des compilateurs, car
utilisant des possibilités avancées du langage qui n'ont pas encore été implémentées.
- Un code C++ invalide du point de vue de la norme peut être accepté par certains compilateurs peu rigoureux

On peut néanmoins relativiser ces propos en précisant que dans l'ensemble, depuis quelques années, la majorité des
compilateurs tend vers une conformité élevée avec la norme, voire totale. C'est pourquoi il faut veiller à en utiliser une
version récente.

Cette situation peut un peu être comparée à celle des navigateurs web. Bien que le HTML/CSS soient standardisés,
chaque navigateur propose des extensions qui lui sont propres, et ne respecte pas totalement le standard. Un code HTML
valide peut donc produire un résultat différent en fonction du navigateur utilisé. Plus le navigateur est ancien, plus
il sera difficile de lui faire accepter un code aux normes. A l'inverse, les navigateurs récents tendent à s'uniformiser
autour des standards, et en implémentent de plus en plus de choses. En C++, c'est un peu la même chose. Il est facile
de lier son code à un compilateur en particulier, en profitant des extensions de ce dernier. Et plus le compilateur est
ancien, et moins il respecte le standard.
C'est pourquoi on fait une distinction entre ce qui est portable, et ce qui ne l'est pas (la notion de portabilité incluant
la compatibilité entre compilateurs).

Il existe un grand nombre de compilateurs. Certains sont Open Source (GCC, Open Watcom), et beaucoup d'autres
sont commerciaux (Borland, Comeau, HP, IBM, Intel, Microsoft, SGI, Sun, ...). Le C++ étant simplement une norme, il
n'y a pas d'implémentation de référence, ni même de site internet central comme c'est le cas pour des langages comme
Python, Perl, ... Il n'y a donc pas non plus de documentation de référence autre que la norme elle même (voir Où trouver
de la documentation de référence sur le C++ ? et Où puis-je obtenir une copie de la norme ?).

-7-
Les sources présentées sur cette pages sont libres de droits, et vous pouvez les utiliser à votre convenance. Par contre la page de présentation de ces sources
constitue une oeuvre intellectuelle protégée par les droits d'auteurs. Copyright © 2008 Developpez LLC. Tout droits réservés Developpez LLC. Aucune
reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents et images sans l'autorisation expresse de Developpez
LLC. Sinon vous encourez selon la loi jusqu'à 3 ans de prison et jusqu'à 300 000 E de dommages et intérêts.
http://cpp.developpez.com/faq/cpp/
Cette norme couvre deux aspects :
- le langage lui même, c'est-à-dire sa syntaxe, etc...
- la bibliothèque standard qui l'accompagne
Cette dernière est assez limitée au regard d'autres langages. En particulier en ce qui concerne l'interaction avec le
système (afin de faciliter son implémentation). Ainsi, on ne peut pas faire grand chose de plus que des opérations
élémentaires sur la console et les fichiers. La norme ne définit rien en ce qui concerne :
- la programmation réseau / internet
- le multitâche / la gestion de processus
- les interfaces graphiques
- le graphisme / multimédia
- etc...

Ceci explique en partie le manque d'empressement des développeurs de compilateurs à réaliser des outils qui respectent
le standard à 100%. Ils ont généralement préféré investir dans la réalisation de bibliothèques et d'environnements
comblant ces lacunes. De ce fait, on peut bien faire tout cela en C++, mais pas en C++ standard. Il faut avoir recours à
des bibliothèques tierces, et pour chacun de ces domaines, le C++ est richement fourni, très richement même.
Ainsi, il n'y a pas une manière de développer des interfaces graphiques en C++, mais des dizaines (voir Comment créer
une interface graphique en C++ ?), et aucune n'est standard.

On peut résumer les choses ainsi :


- ce qui est standard peut ne pas être portable à cause des anciens compilateurs encore utilisés. Mais un code standard
est toujours préférable dans la mesure où c'est au compilateur de s'adapter pour parvenir à le compiler, quelque soit
la plateforme cible. Les principaux compilateurs modernes vont dans ce sens.
- ce qui n'est pas standard peut malgré tout être rendu portable en adaptant le code pour qu'il compile sous plusieurs
compilateurs / systèmes. C'est donc au code de s'adapter au compilateur, ce qui revient parfois à écrire plusieurs fois
la même bibliothèque (une version pour Windows, une autre pour Linux...).

Sachez que la portabilité a un coût, notamment au niveau des fonctionnalités. Pour qu'une bibliothèque soit portable,
on a intérêt à ce qu'elle utilise le moins possible les spécificités d'une plateforme donnée. Par exemple, pour réaliser
une interface graphique, vous pouvez utiliser les MFC de Microsoft, uniquement avec le compilateur Visual C++ sous
Windows. Ou alors vous pouvez utiliser Qt de Trolltech, qui compile avec Visual C++, mais aussi Borland C++ Builder,
GCC, ... sous Windows, UNIX/Linux ou Mac. Pour que cela soit possible, elle a forcément fait l'impasse sur certaines
spécificités de Windows absentes des autres OS (comme la base de registre...) alors que les MFC, plus proches du
système, y donnent accès de manière directe. Alors, des MFC ou de Qt, laquelle est la meilleure ? A vous de le dire, en
fonction de vos souhaits et de vos contraintes.

Les nombreuses bibliothèques tierces existantes se distinguent donc d'abord par le compromis qu'elles offrent entre
portabilité et fonctionnalités. C'est l'antagonisme entre ces deux notions qui guidera votre choix de l'une d'entre elle,
ainsi que les critères de coût, de pérennité, de documentation, etc...

Ayez donc bien à l'esprit ce qui est standard de ce qui ne l'est pas. Si vous demandez de l'aide à propos d'une bibliothèque
C++ non standard sur un forum dédié au C++ standard, il est parfaitement normal que personne ne sache répondre,
bien qu'il s'agisse de C++. Essayez de toujours vous orienter vers le forum le plus adapté à votre outil de développement.

Qui utilise le C++ ?


Auteurs : Marshall Cline ,
Enormément de sociétés et d'administrations.

-8-
Les sources présentées sur cette pages sont libres de droits, et vous pouvez les utiliser à votre convenance. Par contre la page de présentation de ces sources
constitue une oeuvre intellectuelle protégée par les droits d'auteurs. Copyright © 2008 Developpez LLC. Tout droits réservés Developpez LLC. Aucune
reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents et images sans l'autorisation expresse de Developpez
LLC. Sinon vous encourez selon la loi jusqu'à 3 ans de prison et jusqu'à 300 000 E de dommages et intérêts.
http://cpp.developpez.com/faq/cpp/
Le nombre important de développeurs (et par conséquent le grand nombre d'infrastructures de support, y compris
vendeurs, outils, cours, ....) est une des caractéristiques importantes du C++.

Le C++ est-il normalisé ?


Auteurs : Marshall Cline ,
Oui.

Le standard du C++ a été finalisé et adopté par l'ISO (International Organization for Standardization) et par
d'autres organismes de normalisation tels que l'ANSI (The American National Standards Institute), le BSI (The British
Standards Institute), le DIN (The German National Standards Organization).
L'adoption du standard s'est faite à l'unanimité le 14 novembre 1997.

Le comité ANSI C++ est appelé "X3J16". Le groupe de travail de l'ISO se nomme "WG21". Le processus de
normalisation implique un grand nombre d'acteurs : des représentants de l'Australie, du Canada, du Danemark, de
la France, de l'Allemagne, de l'Irlande, du Japon, de la Hollande, de la Nouvelle-Zélande, de la Suède, du Royaume-
Uni, et des Etats-Unis ainsi que des représentants d'une centaine de sociétés plus de nombreux particuliers impliqués.
Les acteurs majeurs incluent AT&T, Ericsson, Digital, Borland, Hewlett Packard, IBM, Mentor Graphics, Microsoft,
Silicon Graphics, Sun Microsystems et Siemens. Après 8 ans de travaux, le standard est maintenant finalisé. Le standard
a été approuvé par un vote à l'unanimité des représentants présents à Morristown le 14 novembre 1997.

lien : Où puis-je obtenir une copie de la norme ?

Combien de temps faut-il pour apprendre le C++ ?


Auteurs : Marshall Cline ,
Des sociétés arrivent à prodiguer des cours intensifs, où un semestre de cours de niveau universitaire est compressé en
une semaine de 40 heures. Mais, indépendamment du lieu où vous suivrez vos cours, assurez-vous que les cours ont des
séances 'pratiques', étant donné que la plupart des gens apprennent mieux en ayant des projets sur lesquels travailler
et faire des essais. Mais même avec la meilleure formation, ils ne sont pas encore prêts.

Cela prend de 6 à 12 mois pour devenir productif en technique OO / C++, moins si les développeurs ont accès facilement
à un groupe local d'experts, plus s'il n'y a pas de bonne bibliothèque des classes à usage général de disponible.
Devenir un de ces experts capable de guider les autres prend à peu près 3 ans.

Certaines personnes n'y arrivent jamais. Vous n'avez aucune chance à moins que vous n'acceptiez que l'on vous
apprenne et que vous soyez motivé. Un minimum de réceptivité signifie que vous devez être capable de reconnaître
quand vous vous êtes trompé. Un minimum de motivation signifie que vous devez être disposé à investir quelques heures
supplémentaires (Il est nettement plus facile d'apprendre quelques nouveautés que de modifier votre paradigme [par
ex., de changer votre façon de penser, la notion du bien, etc....])

Il y a 2 choses que vous devriez faire :

• trouver un mentor, un guide


• fournir 2 livres à vos élèves : un pour leur expliquer ce qui est légal, l'autre pour ce qui est moral.

et 2 que vous ne devriez pas faire


• considérer le C comme un passage obligé pour apprendre le C++

-9-
Les sources présentées sur cette pages sont libres de droits, et vous pouvez les utiliser à votre convenance. Par contre la page de présentation de ces sources
constitue une oeuvre intellectuelle protégée par les droits d'auteurs. Copyright © 2008 Developpez LLC. Tout droits réservés Developpez LLC. Aucune
reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents et images sans l'autorisation expresse de Developpez
LLC. Sinon vous encourez selon la loi jusqu'à 3 ans de prison et jusqu'à 300 000 E de dommages et intérêts.
http://cpp.developpez.com/faq/cpp/
• considérer SmallTalk comme un passage obligé pour apprendre le C++

Dois-je apprendre le C avant le C++ ?


Auteurs : LFE ,
Il s'agit sûrement de la question la plus posée et pour laquelle la réponse est sujette à une controverse interminable.

Il n'est pas nécessaire ni obligatoire de connaître le C pour faire du C++. Il est tout à fait possible et raisonnable
d'apprendre le C++ directement.

Le C et le C++ sont deux langages différents qui n'ont pas la même notion d'abstraction : le C reste relativement
proche de la logique de la machine et introduit des notions de gestion manuelle de la mémoire (entre autre) qui peuvent
être particulièrement compliquées pour les néophytes; le C++ se suffit à lui même pour l'apprentissage des concepts
fondamentaux.

Ainsi si vous ne connaissez pas encore la programmation et que vous souhaitez apprendre le C++, il est préférable
d'apprendre à utiliser le C++ directement.

- 10 -
Les sources présentées sur cette pages sont libres de droits, et vous pouvez les utiliser à votre convenance. Par contre la page de présentation de ces sources
constitue une oeuvre intellectuelle protégée par les droits d'auteurs. Copyright © 2008 Developpez LLC. Tout droits réservés Developpez LLC. Aucune
reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents et images sans l'autorisation expresse de Developpez
LLC. Sinon vous encourez selon la loi jusqu'à 3 ans de prison et jusqu'à 300 000 E de dommages et intérêts.
http://cpp.developpez.com/faq/cpp/
Sommaire > Généralités sur le C++ > Guide de démarrage
Où trouver de la documentation de référence sur le C++ ?
Auteurs : Laurent Gomila , Aurélien Regat-Barrel , JolyLoic ,
Les références les plus populaires sont :
• Dinkumware : Dinkum C++ Library
• RogueWave : Standard C++ Library User Guide and Tutorial
• cpluplus resources : C++ Reference
• C/C++ Reference
• Le Standard Template Library Programmer's Guide de SGI
• La Microsoft MSDN Library contient aussi de nombreuses informations sur le C++, en particulier C++ Language
Reference et Standard C++ Library Overview

Il y a aussi le standard lui même, dont on peut acheter une version PDF pour $18.

On peut également trouver les brouillons du comité standard concernant les évolutions passées et futures du C++ ( C
++ Standards Committee Papers), dont un document assez complet concernant le standard C++ actuel ( Working
draft, standard for programming language C++).

Ou trouver un compilateur C++ ?


Auteurs : LFE ,
http://cpp.developpez.com/outils/ reprend une liste de compilateurs téléchargeables gratuitement.

Où puis-je obtenir une copie de la norme ?


Auteurs : Marshall Cline , Jean-Marc.Bourguet , JolyLoic ,
Il y a au moins 3 façons d'obtenir une copie électronique de la norme :
• 18 $ (publié par l'ANSI) : allez sur la page suivante : http://webstore.ansi.org/ansidocstore/product.asp?sku=ISO
%2FIEC+14882%2D1998 et cliquez sur "Add To Basket".
• 34 $ (publié par l'ANSI) : allez sur la page suivante : http://www.techstreet.com/cgi-bin/detail?product_id=49964,
sélectionnez "PDF File" dans la liste, et cliquez sur "Place this in your shopping basket".
• 245 $ (publié par l'ISO) : allez sur la page suivante : http://webstore.ansi.org/ansidocstore/product.asp?sku=ISO
%2FIEC+14882%3A1998 et cliquez sur "Add To Basket".

Note : le document fourni par l'ISO est plus de 10 fois plus onéreux que celui de l'ANSI, alors que le contenu technique
est identique. La page de garde est différente, mais le contenu technique est identique. Ne me demandez pas pourquoi
l'ISO demande aussi cher par rapport aux autres pour la même chose ; c'est la technique commerciale de l'ISO ; si
malgré tout vous voulez poser la question, faites-le à leur service commercial.

Il y a au moins 2 façons d'obtenir une copie papier du document :


• 175 $ (publié par l'ANSI) : allez sur cette page http://www.techstreet.com/cgi-bin/detail?product_id=49964,
sélectionnez "HardCopy" dans la liste, et cliquez sur "Place this in your shopping basket".
• Prix non connu (publié par l'ANSI) : appelez le NCITS (National Committee for Information Technology
Standards ; c'est le nouveau nom du comité autrefois connu sous le nom de "X3"). La personne de contact est
Monica Vega, 202-626-5739 ou 202-626-5738. Demandez le document FDC 14882. Soyez prêt à dépenser un peu
d'argent, il n'est sûrement pas gratuit.

- 11 -
Les sources présentées sur cette pages sont libres de droits, et vous pouvez les utiliser à votre convenance. Par contre la page de présentation de ces sources
constitue une oeuvre intellectuelle protégée par les droits d'auteurs. Copyright © 2008 Developpez LLC. Tout droits réservés Developpez LLC. Aucune
reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents et images sans l'autorisation expresse de Developpez
LLC. Sinon vous encourez selon la loi jusqu'à 3 ans de prison et jusqu'à 300 000 E de dommages et intérêts.
http://cpp.developpez.com/faq/cpp/
On pourra également noter la publication de la norme de 2003 chez Wiley : The C++ Standard: Incorporating Technical
Corrigendum 1.

Il y a 3 autres moyens d'obtenir (gratuitement) un document intéressant à consulter :


• Si vous voulez consulter un document (gratuit) mais qui n'est pas officiel, déprécié, et partiellement incorrect,
vous pouvez obtenir le "committee draft #2" : ftp://ftp.research.att.com/dist/c++std/WP/CD2/
• Si vous voulez suivre le document en cours d'évolution pour la prochaine version de la norme (et qui est donc
susceptible d'évoluer avant que cette version ne sorte) : Version d'octobre 2008
• le communiqué de presse de l'ISO (ce n'est pas une copie de la norme) est ici : http://www.research.att.com/~bs/
iso_release.html. Celui-ci est compréhensible par des non développeurs.

Quel livre acheter ou lire ?


Auteurs : LFE ,
Vous trouverez une liste de livres sur le C++ ainsi qu'un descriptif à leur sujet à cette adresse : http://
cpp.developpez.com/livres/.

Où puis-je trouver des cours à télécharger et à consulter ?


Auteurs : LFE ,
Un énorme travail de recherche à ce niveau a déjà été effectué et les résultats se trouvent ici : http://cpp.developpez.com/
cours/

Où trouver la solution des exercices du livre "Le langage C++" de Stroustrup ?


Auteurs : JolyLoic ,

La solution des exercices du livre Le langage C++ est disponible ici : http://www.vandevoorde.com/C++Solutions/.

Au secours, à l'aide, j'ai une erreur ! Que faire ?


Auteurs : Aurélien Regat-Barrel ,
Commencez par dire laquelle ! Si vous vous contentez de dire que votre programme ne marche pas, que vous avez une
erreur, personne ne pourra vous aider. Comme cela est stipulé dans les règles du forum, soyez précis et clair sur la
nature de votre problème :

• erreur de compilation ou erreur d'exécution


• le message d'erreur rencontré
• si c'est une erreur de compilation, donnez le bout de code concerné
• si c'est une erreur d'exécution, localisez-la au moyen d'un débogueur

Sans ces éléments minimums, n'espérez pas avoir une réponse utile. Dans le cas d'une erreur de compilation, en
particulier avec les templates, il est aussi judicieux de préciser le compilateur que vous utilisez ainsi que sa version. Le
titre de votre question est lui aussi important, il doit donner une brève description de votre problème. Un mauvais titre
et une mauvaise explication n'inciteront personne à vous aider.

- 12 -
Les sources présentées sur cette pages sont libres de droits, et vous pouvez les utiliser à votre convenance. Par contre la page de présentation de ces sources
constitue une oeuvre intellectuelle protégée par les droits d'auteurs. Copyright © 2008 Developpez LLC. Tout droits réservés Developpez LLC. Aucune
reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents et images sans l'autorisation expresse de Developpez
LLC. Sinon vous encourez selon la loi jusqu'à 3 ans de prison et jusqu'à 300 000 E de dommages et intérêts.
http://cpp.developpez.com/faq/cpp/
L'idéal pour espérer une réponse rapide est de donner un exemple complet minimal (ECM) de votre erreur, c'est-à-dire
un petit programme le plus court possible que l'on peut copier-coller et compiler tel quel afin de constater le problème.
A question claire, réponse claire !

- 13 -
Les sources présentées sur cette pages sont libres de droits, et vous pouvez les utiliser à votre convenance. Par contre la page de présentation de ces sources
constitue une oeuvre intellectuelle protégée par les droits d'auteurs. Copyright © 2008 Developpez LLC. Tout droits réservés Developpez LLC. Aucune
reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents et images sans l'autorisation expresse de Developpez
LLC. Sinon vous encourez selon la loi jusqu'à 3 ans de prison et jusqu'à 300 000 E de dommages et intérêts.
http://cpp.developpez.com/faq/cpp/
Sommaire > Généralités sur le C++ > Bibliothèques complémentaires
Où trouver des bibliothèques de fonctions toutes faites ?
Auteurs : LFE ,
http://cpp.developpez.com/bibliotheques/ reprend une liste des bibliothèques les plus utiles téléchargeables
gratuitement.

Comment créer une interface graphique en C++ ?


Auteurs : Aurélien Regat-Barrel , Luc Hermitte ,
Le C++ standard ne permet pas de créer une interface graphique. Cela a déjà été discuté, doit probablement l'être encore
et le sera encore longtemps. Il faut pour cela utiliser une des très nombreuses bibliothèques spécialisées existantes. Les
plus courantes sont :

• les MFC de Microsoft, pour Windows et destinée à être utilisée avec Visual C++.
• la VCL de Borland, pour Windows et destinée à être utilisée avec C++ Builder.
• Qt de Nokia, pour Linux/UNIX, Mac OS X, Windows, etc avec des licenses spécifiques (dont certaines libres).
Qt dispose d'une rubrique à part entière : Rubrique Qt.
• wxWidgets, anciennement wxWindows open source, pour Linux/UNIX, Mac, Windows (y compris Windows 3.1
et Windows CE), et d'autres encore. Utilisable avec un grand nombre de compilateurs.

Il en existe encore beaucoup d'autres. La The GUI Toolkit, Framework Page en recense une bonne partie, parmi
lesquelles les bibliothèques portables et gratuites suivantes sont régulièrement citées :

• FLTK, open source, pour Linux/UNIX, Mac OS X, Windows.



FOX TOOLKIT, open source, pour UNIX/Linux, et Windows (voir tutoriel "Le Fox Toolkit").

Ces bibliothèques sont pour la plupart assez anciennes, ce qui est souvent un gage de maturité. Mais la conséquence est
qu'elles utilisent finalement assez peu les possibilités du C++, chose qui devient possible depuis assez peu de temps grâce
la généralisation de très bons compilateurs. Ainsi, les bibliothèques précédentes utilisent toutes leur propre classe chaîne
de caractères au lieu de std::string, leurs propres conteneurs au lieu de ceux de la STL, n'utilisent pas les exceptions,
les espaces de noms, ...

D'autres bibliothèques plus récentes ont la réputation d'être écrites de façon plus "moderne". On peut citer à ce titre le
Visual Component Framework ou encore gtkmm qui est un wrapper C++ pour la bibliothèque C GTK+. Le revers
de la médaille est que ces bibliothèques sont plus difficilement portables.

On fait la distinction entre programmer en C++ standard et utiliser une de ces bibliothèques C++. Cela veut dire que
les forums C++ ne sont généralement pas le bon endroit pour poser une question relative à l'une d'entre elles. De même
cette FAQ ne traite pas de leur utilisation.

Si vous avez des questions relatives aux MFC, orientez vous vers le forum Visual C++, la FAQ Visual C++ ainsi que
la page de cours et tutoriaux Visual C++.

Si vous avez des questions relatives à la VCL, orientez vous vers le forum Borland C++ Builder, la FAQ Borland C++
Builder ainsi que la page de cours et tutoriaux Borland C++ Builder.

Pour les autres bibliothèques, vous pouvez utiliser le Forum C++. Si personne ne vous répond, orientez-vous vers le
site / newsgroup / mailing list dédié au toolkit que vous utilisez. A ce titre on peut citer :

- 14 -
Les sources présentées sur cette pages sont libres de droits, et vous pouvez les utiliser à votre convenance. Par contre la page de présentation de ces sources
constitue une oeuvre intellectuelle protégée par les droits d'auteurs. Copyright © 2008 Developpez LLC. Tout droits réservés Developpez LLC. Aucune
reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents et images sans l'autorisation expresse de Developpez
LLC. Sinon vous encourez selon la loi jusqu'à 3 ans de prison et jusqu'à 300 000 E de dommages et intérêts.
http://cpp.developpez.com/faq/cpp/

La Qt-interest Mailing List pour des questions sur Qt.

Le newsgroup comp.soft-sys.wxwindows pour des questions sur wxWidgets.
• Les FAQ et documentation en ligne dédiées à chacun de ces tooklits sur leur site respectif.

Concernant wxWidgets, vous pouvez lire les articles en français sur la page personnelle de CGi.

Comment manipuler des images ?


Auteurs : Laurent Gomila , Aurélien Regat-Barrel ,
Il n'existe malheureusement rien en C++ standard qui ait un rapport avec la gestion d'images, quelque soit leur
format. Ainsi pour charger, sauvegarder, récupérer les dimensions d'une image, faire du dessin ou gérer un quelconque
graphisme, il faudra se tourner vers une bibliothèque externe :


Le GDI+ (API C++, gère les formats les plus courants, seulement sous Windows).

OpenIL (bibliothèque C, multiplateforme, gère une vingtaine de formats d'images).

FreeImage (bibliothèque C++, multiplateforme, gère elle aussi une bonne vingtaine de formats).

CxImage (classe Windows, gère une dizaine de formats d'images).

Anti-Grain Geometry (bibliothèque C++ portable pour faire du rendu 2D haute qualité).

ImageMagick (multilangages, multiplateformes, gère plus de 100 formats d'images).

CImg (bibliothèque C++ sous forme d'un seul en-tête, multiplateforme, plutôt simple à utiliser).
• ...
• Et bien sûr si vous utilisez un toolkit tel que Qt, les MFC ou encore la VCL, n'oubliez pas que ceux-ci proposent
généralement ces classes pour manipuler facilement les images.

D'autres bibliothèques de manipulation d'images sont listées et évaluées sur cette page : Investigating Image
Libraries

Si vous avez du courage et du temps à perdre vous pouvez aussi construire votre propre gestion des images ;
pour y arriver vous aurez besoin des descriptions des formats, que vous pourrez trouver entre autre sur http://
www.wotsit.org.

Enfin, si votre but est de développer une interface graphique alors reportez-vous à la question Comment créer une
interface graphique en C++ ?.

Comment gérer les dates et les heures en C++ ?


Auteurs : Laurent Gomila ,
Le C++ ne propose rien pour gérer les dates, ni de base ni dans sa bibliothèque standard. Afin d'effectuer des opérations
sur les dates et les heures, il vous faudra donc au choix :

• Utiliser des fonctions et structures de la bibliothèque standard du C (voir FAQ C : gestion des dates et heures).
• Utiliser les classes CTime et CTimeSpan si vous développez avec Visual C++ et les MFC (voir FAQ VC++ : les
dates).

- 15 -
Les sources présentées sur cette pages sont libres de droits, et vous pouvez les utiliser à votre convenance. Par contre la page de présentation de ces sources
constitue une oeuvre intellectuelle protégée par les droits d'auteurs. Copyright © 2008 Developpez LLC. Tout droits réservés Developpez LLC. Aucune
reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents et images sans l'autorisation expresse de Developpez
LLC. Sinon vous encourez selon la loi jusqu'à 3 ans de prison et jusqu'à 300 000 E de dommages et intérêts.
http://cpp.developpez.com/faq/cpp/
• Utiliser les classes TDate et TDateTime si vous développez avec C++ Builder et la VCL (voir FAQ C++ Builder :
gestion du temps).
• Utiliser la bibliothèque boost::date_time si vous souhaitez garder un code C++ portable.
• Enfin, n'oubliez pas que bon nombre de bibliothèques d'interfaces graphiques (WxWidgets, Qt, ...) proposent
également des classes de gestion du temps.

- 16 -
Les sources présentées sur cette pages sont libres de droits, et vous pouvez les utiliser à votre convenance. Par contre la page de présentation de ces sources
constitue une oeuvre intellectuelle protégée par les droits d'auteurs. Copyright © 2008 Developpez LLC. Tout droits réservés Developpez LLC. Aucune
reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents et images sans l'autorisation expresse de Developpez
LLC. Sinon vous encourez selon la loi jusqu'à 3 ans de prison et jusqu'à 300 000 E de dommages et intérêts.
http://cpp.developpez.com/faq/cpp/
Sommaire > Généralités sur le C++ > Organisation du code source
Quels fichiers d'en-tête dois-je inclure ?
Auteurs : 3DArchi ,
Le strict minimum : c'est-à-dire les seuls fichiers d'en-tête contenant les déclarations ou définitions de ce qui va être
utilisé et permettant ainsi à la compilation de réussir.

lien : Où dois-je inclure les fichiers d'en-tête ?


lien : Dans quel ordre dois-je mettre mes fichiers d'en-tête ?
lien : Comment faire avec les templates ?
lien : Comment vérifier que mon fichier d'en-tête peut être inclus indépendamment de tout autre ?
lien : Et avec les en-têtes précompilés ?

Où dois-je inclure les fichiers d'en-tête ?


Auteurs : 3DArchi ,
Il est préférable d'avoir des déclarations anticipées (forward declaration) dans les fichiers d'en-tête et d'inclure l'en-
tête de déclaration dans le fichier source. L'objectif à atteindre est que tout fichier d'en-tête doit pouvoir être inclus
indépendamment des autres et compiler. Ce qui veut dire qu'un fichier d'en-tête doit contenir le moins de choses
possibles.

A.h
class A
{
// déclaration de la classe A
};

B.h
class A; // déclaration anticipée
class B
{
// déclaration de la classe B
// ...

void do_someting_with_a_A(A const &);


};

B.cpp
#include "B.h"
#include "A.h"
// définition de B...

A noter que la déclaration anticipée n'est possible que si le compilateur n'a pas besoin de connaître complètement le
type : pointeur, référence. On ne peut l'utiliser pour l'héritage ou la composition.
Pour aller plus loin avec les déclarations anticipées, jusqu'à présent vous avez l'habitude de découper votre code en
deux fichiers : un fichier d'en-tête MyClass.h pour la déclaration et un fichier d'implémentation MyClass.cpp pour
la définition. Notez qu'on peut associer un troisième fichier MyClassFwd.h pour regrouper les déclarations anticipées
liées à CMyClass. Ce fichier contient tout naturellement une déclaration anticipée de la classe :

class CMyClass;

- 17 -
Les sources présentées sur cette pages sont libres de droits, et vous pouvez les utiliser à votre convenance. Par contre la page de présentation de ces sources
constitue une oeuvre intellectuelle protégée par les droits d'auteurs. Copyright © 2008 Developpez LLC. Tout droits réservés Developpez LLC. Aucune
reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents et images sans l'autorisation expresse de Developpez
LLC. Sinon vous encourez selon la loi jusqu'à 3 ans de prison et jusqu'à 300 000 E de dommages et intérêts.
http://cpp.developpez.com/faq/cpp/
Mais, c'est l'occasion d'y mettre aussi d'autres déclarations associées qui vont systématiquement (ou presque) avec votre
classe. Ce peut être des enum, des déclarations de fonctions externes, etc... :

class CMyClass;
enum E_options_my_class
{
// ...
};
std::ostream& operator << (std::ostream& O, const CMyClass& B);

Lorsqu'une autre classe nécessite une déclaration anticipée de CMyClass, elle fait alors appel à cet en-tête :

#include "MyClassFwd.h"

Dans quel ordre dois-je mettre mes fichiers d'en-tête ?


Auteurs : Luc Hermitte , 3DArchi ,
1 Le fichier d'en-tête de la déclaration de la classe ;
2 les fichiers standards (stl) ;
3 les fichiers d'en-tête de l'O.S. ;
4 les fichiers d'en-tête des bibliothèques tierces (boost, xml, wxWidget, ...) ;
5 les fichiers d'en-tête de mes bibliothèques externes ;
6 les fichiers d'en-tête de mon projet.

Cet ordre est une proposition parmi d'autres qui ont aussi leur légitimité. Vous pouvez suivre un autre ordre d'inclusion
si vous en sentez le besoin compte tenu de vos pratiques habituelles ou du contexte de votre application. En fait, l'idée
maîtresse est de maintenir une cohérence sur l'ensemble du projet. Quelle que soit la politique que vous choisissez de
mettre en #uvre, utilisez la même systématiquement dans tous vos fichiers.

Comment faire avec les templates ?


Auteurs : 3DArchi ,
Les templates nécessitent d'avoir toute la définition dans le fichier d'en-tête. Pour maintenir une politique cohérente, on
peut séparer la déclaration d'une classe template (.h) et sa définition (.tpp). La définition est incluse à la fin du fichier
de déclaration :

MyTemplateClass.h
template<class T>
class TMyTemplateClass
{
public:
void do_something();
};
#include "MyTemplateClass.tpp"

MyTemplateClass.tpp
// en-tête si besoin

// définition
template<class T>
void TMyTemplateClass<T>::do_something()
{

- 18 -
Les sources présentées sur cette pages sont libres de droits, et vous pouvez les utiliser à votre convenance. Par contre la page de présentation de ces sources
constitue une oeuvre intellectuelle protégée par les droits d'auteurs. Copyright © 2008 Developpez LLC. Tout droits réservés Developpez LLC. Aucune
reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents et images sans l'autorisation expresse de Developpez
LLC. Sinon vous encourez selon la loi jusqu'à 3 ans de prison et jusqu'à 300 000 E de dommages et intérêts.
http://cpp.developpez.com/faq/cpp/
MyTemplateClass.tpp
}

Comment vérifier que mon fichier d'en-tête peut être inclus indépendamment de tout autre ?
Auteurs : JolyLoic , 3DArchi ,
Inclure le fichier en tout premier dans un fichier .cpp. Ce dernier doit compiler sans erreur.

Et avec les en-têtes précompilés ?


Auteurs : 3DArchi ,
Les en-têtes précompilés n'offrent malheureusement pas de standard dans leur mise en #uvre. Tous les compilateurs
ne les supportent pas, et ceux qui savent les gérer ne le font pas tous de la même façon. Est-ce une raison pour les
abandonner ? Non. Car ils offrent de vrais avantages en terme de temps de compilation, surtout pour les projets utilisant
des bibliothèques volumineuses ou complexes. La question qui se pose alors est de savoir quoi mettre dans les en-têtes
précompilés ? Il faut garder à l'esprit l'objectif des en-têtes précompilés : accélérer la compilation. Il faut donc veiller
à mettre les fichiers des bibliothèques externes récurrents dans vos sources ainsi que ceux qui sont susceptibles de
provoquer des inclusions en cascade (afxXXX.h avec les MFC, wx.h dans wxWidgets). En revanche ne mettez pas des
fichiers d'en-têtes du projet en cours car ce sont ceux qui sont le plus susceptibles de varier et dégradent ainsi le temps
de compilation en forçant à tout recompiler.
Il faut être vigilant à ne pas transformer le fichier d'en-têtes précompilés en un fourre-tout contenant tous les fichiers de
toutes les bibliothèques utilisées dans un projet. Cela rompt le principe qui veut que l'on minimise les dépendances entre
les unités de compilation. Identifiez correctement les fichiers clés qui sont significatifs dans le temps de compilation de
votre projet.

lien :

- 19 -
Les sources présentées sur cette pages sont libres de droits, et vous pouvez les utiliser à votre convenance. Par contre la page de présentation de ces sources
constitue une oeuvre intellectuelle protégée par les droits d'auteurs. Copyright © 2008 Developpez LLC. Tout droits réservés Developpez LLC. Aucune
reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents et images sans l'autorisation expresse de Developpez
LLC. Sinon vous encourez selon la loi jusqu'à 3 ans de prison et jusqu'à 300 000 E de dommages et intérêts.
http://cpp.developpez.com/faq/cpp/
Sommaire > Caractéristiques du langage
Quelles sont les caractéristiques du langage C++ ?
Auteurs : Aurélien Regat-Barrel , JolyLoic ,
Le C++ est un langage permettant de maîtriser la représentation bas niveau des données manipulées (arithmétique
de pointeurs, allocation manuelle de la mémoire, ...) tout en fournissant des outils (références, exceptions, classes,
templates, ...) permettant de construire des structures de plus haut niveau. Il est donc particulièrement adapté à des
programmes de taille assez importante mais où les performances comptent. C'est un langage typé statiquement (c'est-
à-dire une fois pour toutes lors de la compilation) permettant de créer des programmes compilés en natif au moyen de
compilateurs optimiseurs ce qui les rend généralement très performants.
C'est un langage de programmation multiparadigmes (c'est-à-dire qu'il permet plusieurs types de programmation)
parmi lesquels la programmation objet, la programmation procédurale ainsi que la programmation générique grâce
aux templates. Tout ceci fait de C++ un langage populaire avec un large spectre d'applications grâce à la très grande
quantité de bibliothèques et ressources disponibles.

Le C++ assure-t-il la compatibilité avec le C ?


Auteurs : Marshall Cline ,
Quasiment.

Le C++ est aussi compatible avec le C que faire se peut, mais pas parfaitement. Pratiquement, la différence la plus
marquante est que le C++ exige les prototypes, ce qui veut dire qu'une fonction déclarée

f();

ne prend pas de paramètres, alors qu'en C, on peut passer un nombre arbitraire de paramètres.

Il y a d'autres différences parfois très subtiles. Par exemple

sizeof('x')

vaut

sizeof(char)

en C++, alors qu'en C, cela vaut

sizeof(int)

Un autre exemple est celui des étiquettes de structures qui sont stockées dans le même namespace que les autres
identificateurs. Là où le C exige la déclaration explicite d'une structure, cela devient redondant en C++. Par exemple,
l'écriture suivante est valide en C++ mais est redondante, alors qu'elle est obligatoire en C.

- 20 -
Les sources présentées sur cette pages sont libres de droits, et vous pouvez les utiliser à votre convenance. Par contre la page de présentation de ces sources
constitue une oeuvre intellectuelle protégée par les droits d'auteurs. Copyright © 2008 Developpez LLC. Tout droits réservés Developpez LLC. Aucune
reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents et images sans l'autorisation expresse de Developpez
LLC. Sinon vous encourez selon la loi jusqu'à 3 ans de prison et jusqu'à 300 000 E de dommages et intérêts.
http://cpp.developpez.com/faq/cpp/
typedef struct Fred Fred;

Quelques caractéristiques du langage C++ d'un point de vue 'business' ?


Auteurs : Marshall Cline ,
• Le C++ bénéficie d'une large base installée, ce qui veut dire qu'il y a un support de plusieurs vendeurs pour les
outils, environnements, services de consultance, ...
• Le C++ permet aux développeurs de fournir des interfaces simplifiées, ce qui réduit la quantité de défauts
rencontrés lorsqu'elles sont réutilisées.
• Le C++ permet d'exploiter l'intuition des développeurs grâce à l'utilisation de la surcharge d'opérateur, ce qui
permet de réduire la courbe d'apprentissage pour les utilisateurs.
• Le C++ permet de "localiser l'accès" à une partie du logiciel, ce qui réduit la charge de travail causée par les
changements.
• Le C++ permet de réduire le compromis fiabilité / utilisabilité, donc le coût d'utilisation ou de réutilisation du
logiciel.
• Le C++ permet de réduire le compromis fiabilité / vitesse, donc d'améliorer la qualité sans dégradation des
performances.
• Le C++ offre les techniques d'héritage et d'appel dynamique, ce qui permet d'appeler du nouveau code à partir
de code plus ancien, permettant donc d'ajouter de nouvelles fonctionnalités ou d'adapter vos programmes à la
demande du marché.

C++ est-il un langage parfait ?


Auteurs : Marshall Cline ,
Le C++ n'a pas été créé pour démontrer à quoi ressemblait un langage Orienté Objet parfait. Il a été conçu pour être
un outil pratique, pour répondre à des problèmes pratiques.
Il présente quelques défauts, mais les seuls endroits où il serait judicieux d'apporter des modifications, pour atteindre
la perfection, n'auraient qu'un but purement académique, ce qui n'est pas le but du C++.

Le C++ est-il un meilleur langage que .... ?


Auteurs : Marshall Cline ,
Il s'agit sûrement de la question qui génère le plus de bruit par rapport à l'information utile qui en ressort. Veuillez
lire ce qui suit avant de vous lancer dans ce genre de débat.

Dans 99% des cas, le choix d'un langage de programmation est fait en fonction de considérations financières ou
commerciales, mais pas en fonction des considérations techniques.
Les choses réellement importantes qui pèsent lors de la décision sont la présence d'un environnement de développement,
la possibilité de faire fonctionner le logiciel sur la machine cible, les licences, la disponibilité de développeurs
expérimentés, de consultants, sans oublier la "culture de l'entreprise".
Ces considérations financières et commerciales jouent souvent un rôle plus important que la vitesse de compilation, la
vitesse d'exécution, le typage dynamique ou le typage statique, etc ....

- 21 -
Les sources présentées sur cette pages sont libres de droits, et vous pouvez les utiliser à votre convenance. Par contre la page de présentation de ces sources
constitue une oeuvre intellectuelle protégée par les droits d'auteurs. Copyright © 2008 Developpez LLC. Tout droits réservés Developpez LLC. Aucune
reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents et images sans l'autorisation expresse de Developpez
LLC. Sinon vous encourez selon la loi jusqu'à 3 ans de prison et jusqu'à 300 000 E de dommages et intérêts.
http://cpp.developpez.com/faq/cpp/
Quiconque argumente en faveur d'un langage par rapport à un autre de façon purement technique (c'est-à-dire en
ignorant les éléments commerciaux) s'expose à être traité de 'technicien extrémiste' et à ne pas être écouté.

Le C++ est-il un langage pratique ?


Auteurs : Marshall Cline ,
Oui, C++ est un langage pratique.
Dans le monde de l'industrie logicielle, C++ est vu comme un outil solide, mature et sans surprises. Il est largement
utilisé et supporté par l'industrie, ce qui le valorise d'un point de vue productif en général.

Puis-je utiliser des bibliothèques écrites en C dans mes programmes C++ ?


Auteurs : LFE , gege2061 ,
Oui, il est tout à fait possible d'utiliser des bibliothèques écrites en C dans un programme C++ pour autant qu'elles
soient déclarées correctement dans les fichiers d'en-tête en utilisant le extern "C" qui indique que la fonction doit être
considérée comme du code C et non C++.

#ifdef __cplusplus
extern "C" {
#endif

char* maFonctionC_1 (void);


void maFunctionC2 (char *param1);

#ifdef __cplusplus
}
#endif

- 22 -
Les sources présentées sur cette pages sont libres de droits, et vous pouvez les utiliser à votre convenance. Par contre la page de présentation de ces sources
constitue une oeuvre intellectuelle protégée par les droits d'auteurs. Copyright © 2008 Developpez LLC. Tout droits réservés Developpez LLC. Aucune
reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents et images sans l'autorisation expresse de Developpez
LLC. Sinon vous encourez selon la loi jusqu'à 3 ans de prison et jusqu'à 300 000 E de dommages et intérêts.
http://cpp.developpez.com/faq/cpp/
Sommaire > Programmation objet en C++
Quels sont les enjeux associés aux techniques Orientées Objets ?
Auteurs : Marshall Cline ,
Les techniques OO sont la meilleure façon connue de développer de grosses applications ou des systèmes complexes.

L'industrie du logiciel n'arrive pas à satisfaire les demandes pour des systèmes logiciels aussi imposants que
complexes, mais cet échec est dû à nos succès : nos réussites ont habitué les utilisateurs à toujours en demander plus.
Malheureusement, nous avons ainsi créé une demande du marché que les techniques 'classiques' de programmation ne
pouvaient satisfaire. Cela nous a obligé à créer un meilleur paradigme.

le C++ permet de programmer OO, mais il peut aussi être utilisé comme un langage classique ("un C amélioré"). Si
vous comptez l'utiliser de cette façon, n'espérez pas profiter des bénéfices apportés par la programmation OO.

Qu'est-ce qu'un objet ?


Auteurs : Marshall Cline ,
Une zone de stockage avec une sémantique associée.

Après la déclaration suivante,

int i;

on peut dire que i est un objet de type int. En programmation objet / C++, "Objet" signifie habituellement "une instance
d'une classe". Une classe définit donc le comportement d'un ou plusieurs objets (c'est-ce qu'on peut appeler "instance").

Qu'est-ce que l'héritage ?


Auteurs : LFE ,
L'héritage consiste à construire une classe (appelée classe fille) par spécialisation d'une autre classe (classe mère).
On peut illustrer ce principe en prenant l'exemple des mammifères (classe mère) et l'homme d'un côté (classe fille 1) et
les chiens (classe fille2). En effet, les chiens et les hommes sont tous deux des mammifères mais ont des spécificités.

Qu'est-ce que la surcharge ?


Auteurs : LFE ,
La surcharge est un mécanisme qui permet d'utiliser le même nom pour une fonction mais en lui passant des paramètres
de types différents et/ou en nombre différent. Le nom de la fonction et les types des paramètres constituent ce qu'on
appelle la signature de la fonction.

int moyenne(int i1, int i2);


float moyenne(float f1, float f2); //surcharge valide

- 23 -
Les sources présentées sur cette pages sont libres de droits, et vous pouvez les utiliser à votre convenance. Par contre la page de présentation de ces sources
constitue une oeuvre intellectuelle protégée par les droits d'auteurs. Copyright © 2008 Developpez LLC. Tout droits réservés Developpez LLC. Aucune
reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents et images sans l'autorisation expresse de Developpez
LLC. Sinon vous encourez selon la loi jusqu'à 3 ans de prison et jusqu'à 300 000 E de dommages et intérêts.
http://cpp.developpez.com/faq/cpp/
float moyenne(int i1, int i2); //surcharge non valide

Qu'est-ce que l'encapsulation ?


Auteurs : Marshall Cline ,
Il s'agit d'éviter des accès non autorisés à certaines informations et/ou fonctionnalités.

L'idée clé est de séparer la partie volatile de la partie stable. L'encapsulation permet de dresser un mur autour d'une
partie du code, ce qui permet d'empêcher une autre partie d'accéder à cette partie dite volatile ; les autres parties du
code ne peuvent accéder qu'à la partie stable. Cela évite que le reste du code ne fonctionne plus correctement lorsque
le code volatile est changé. Dans le cadre de la programmation objet, ces parties de code sont normalement une classe
ou un petit groupe de classe.

Les "parties volatiles" sont les détails d'implémentation. Si le morceau de code est une seule classe, la partie volatile
est habituellement encapsulée en utilisant les mots-clés private et protected. S'il s'agit d'un petit groupe de classe,
l'encapsulation peut être utilisée pour interdire à des classes entières de ce groupe. L'héritage peut aussi être utilisé
comme une forme d'encapsulation.

Les parties stables sont les interfaces. Une bonne interface procure une vue simplifiée exprimée dans le vocabulaire
de l'utilisateur, et est créée dans l'optique du client. (un utilisateur, dans le cas présent, signifie un autre développeur,
non pas le client qui achètera l'application). Si le morceau de code est une classe unique, l'interface est simplement
l'ensemble de ses membres publics et des fonctions amies. S'il s'agit d'un groupe de classes, l'interface peut inclure un
certain nombre de classes.

Concevoir une interface propre et séparer cette interface de son implémentation permet aux utilisateurs de l'utiliser
convenablement. Mais encapsuler (mettre dans une capsule) l'implémentation force l'utilisateur à utiliser l'interface.

L'encapsulation constitue-t-elle un mécanisme de sécurité ?


Auteurs : Marshall Cline ,
Non.

L'encapsulation ne constitue pas un mécanisme de sécurité. Il s'agit d'une protection contre les erreurs, pas contre
l'espionnage.

Comment le C++ permet-il d'améliorer le compromis entre fiabilité et simplicité d'utilisation ?


Auteurs : Marshall Cline ,
En C, l'encapsulation se faisait en définissant les static dans un fichier compilé ou dans un module. Cela permettait
d'éviter qu'un autre module n'accède à la partie déclarée statique. (Au passage, les données statiques dans la limite d'un
fichier est dépréciée en C++, ne le faites donc plus).

Malheureusement, cette approche ne permet pas de supporter plusieurs instances des données, étant donné qu'il n'y
a pas de support direct pour créer des instances multiples des données statiques d'un module. Si plusieurs instances
étaient nécessaires en C, les programmeurs utilisaient généralement une structure. Mais, pas de chance, les structures
C ne supportent pas l'encapsulation. Cela dégradait le compromis entre fiabilité (le fait ce dissimuler l'information) et
facilité d'utilisation (les instances multiples).

- 24 -
Les sources présentées sur cette pages sont libres de droits, et vous pouvez les utiliser à votre convenance. Par contre la page de présentation de ces sources
constitue une oeuvre intellectuelle protégée par les droits d'auteurs. Copyright © 2008 Developpez LLC. Tout droits réservés Developpez LLC. Aucune
reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents et images sans l'autorisation expresse de Developpez
LLC. Sinon vous encourez selon la loi jusqu'à 3 ans de prison et jusqu'à 300 000 E de dommages et intérêts.
http://cpp.developpez.com/faq/cpp/
En C++, vous pouvez avoir des instances multiples et l'encapsulation en utilisant les classes. La partie publique de la
classe contient son interface, qui consiste habituellement en ses fonctions membres publiques et ses fonctions amies.
Les parties protégées et/ou privées contiennent l'implémentation de la classe, ce qui est habituellement l'endroit où sont
stockées les données.

Le résultat final est comme une "structure encapsulée". Cela améliore le compromis entre fiabilité (dissimulation de
l'information) et facilité d'utilisation (les instances multiples).

Comment savoir si je dois dériver une classe ou l'encapsuler ?


Auteurs : LFE ,
J'applique une méthode simple : la question à se poser est la suivante : est-ce que X est un genre de Y, ou est-ce que
X utilise un Y ?
Si la réponse est X est un genre de Y, il s'agit d'un cas où je dérive une classe.
Si la réponse est X utilise Y, il s'agit d'un cas où je vais encapsuler une classe.

Qu'est-ce qu'une bonne interface ?


Auteurs : Marshall Cline ,
Quand elle présente une vue simplifiée d'un bout de logiciel, et est exprimée dans les termes de l'utilisateur (le bout de
logiciel correspond habituellement à une classe ou un petit groupe de classes et l'utilisateur est un autre développeur,
non le client final).

"Vue simplifiée" signifie que les détails sont intentionnellement cachés. Cela réduit donc le risque d'erreur lors de
l'utilisation de la classe.

"Vocabulaire de l'utilisateur" veut dire que l'utilisateur n'a pas besoin d'apprendre de nouveaux mots ou concepts.
Cela réduit donc la courbe d'apprentissage de l'utilisateur.

Que sont les accesseurs / mutateurs ?


Auteurs : Luc Hermitte , Aurélien Regat-Barrel ,
Un accesseur (accessor en anglais) est une fonction membre renvoyant la valeur d'une propriété d'un objet. Un mutateur
(mutator en anglais) ou encore modifieur (modifier en anglais) est une fonction membre qui modifie la valeur d'une
propriété d'un objet.
L'utilisation d'accesseurs / mutateurs permet de masquer l'implémentation des données de la classe (encapsulation) et
de faire évoluer celle-ci sans contraintes pour l'utilisateur final. Si ce dernier est obligé de passer par des accesseurs /
mutateurs au lieu d'accéder directement aux données internes, ces dernières peuvent être changées à tout moment et il
suffit alors d'adapter le code des accesseurs / mutateurs. Le code qui utilisait l'ancienne classe peut utiliser la nouvelle
sans s'apercevoir des changements effectués, alors qu'un accès direct aux données internes aurait nécessité de tout
reprendre.
Les accesseurs / mutateurs permettent donc de séparer l'utilisation des données de leur implémentation, en plus de
pouvoir effectuer des traitements ou des contrôles annexes lors de l'assignation des membres.
Dans l'exemple suivant :

class Person
{
public:

- 25 -
Les sources présentées sur cette pages sont libres de droits, et vous pouvez les utiliser à votre convenance. Par contre la page de présentation de ces sources
constitue une oeuvre intellectuelle protégée par les droits d'auteurs. Copyright © 2008 Developpez LLC. Tout droits réservés Developpez LLC. Aucune
reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents et images sans l'autorisation expresse de Developpez
LLC. Sinon vous encourez selon la loi jusqu'à 3 ans de prison et jusqu'à 300 000 E de dommages et intérêts.
http://cpp.developpez.com/faq/cpp/
// accesseur : renvoie le nom
const std::string & GetName() const // notez le const
{
return this->name;
}

// mutateur : change le nom


void SetName( const std::string & NewName )
{
this->name = NewName;
}

private:
std::string name; // nom de la personne
};

GetName est un accesseur car elle renvoie la valeur de la propriété Name. SetName est un mutateur car elle modifie
la valeur de la propriété Name.
Comme le montre cet exemple, il est courant de préfixer le nom des accesseurs / mutateurs respectivement par Get /
Set. Pour cette raison, on appelle aussi les accesseurs / mutateurs des getter / setter.
Les accesseurs ne modifiant pas l'objet mais se contentant de fournir un accès (d'où leur nom) en lecture seule sur une
de ses propriétés, c'est une bonne pratique que de rendre une telle fonction membre constante comme cela est le cas ici
pour GetName (lire à ce sujet Pourquoi certaines fonctions membres possèdent le mot clé const après leur nom ?.
Un point important est que les accesseurs / mutateurs ne s'appliquent pas forcément sur des données membres existantes
d'une classe, mais peuvent être utilisés pour simuler l'existence d'une propriété qui n'est pas directement stockées en
interne dans la classe. Lire à ce sujet Quand et comment faut-il utiliser des accesseurs / mutateurs ?.

Quand et comment faut-il utiliser des accesseurs / mutateurs ?


Auteurs : Luc Hermitte , JolyLoic , Aurélien Regat-Barrel ,
Parmi les fonctions publiques d'une classe, certaines miment la présence d'une donnée membre. On nomme aussi
de telles fonctions des accesseurs. Il n'y a pas forcément de relation un-pour-un entre un accesseur et une donnée
membre (comme cela est le cas pour l'accesseur GetName et la variable name dans l'exemple de la question Que sont les
accesseurs / mutateurs ?. Une donnée encapsulée ne doit pas forcement être exposée via à un accesseur. L'état interne
d'un objet est... interne, et doit le rester.
Il faut distinguer deux choses lorsque l'on écrit une classe : son interface et son implémentation. Le but des accesseurs /
mutateurs est d'effectuer le lien entre les deux, lien qui n'a pas à être direct. L'interface, qui sera visible du reste du
monde et qui est donc la première chose à déterminer quand on écrit une classe, expose un certain nombre de propriétés,
qui peuvent ou non être directement stockées dans la classe. Ce dernier point est un détail d'implémentation qui n'a pas
à être connu, et c'est le rôle des accesseurs / mutateurs de le masquer.
Prenons l'exemple d'une classe qui permet de connaître l'âge d'un individu :

#include "date.h" // classe permettant de stocker une date (pour l'exemple)

class Person
{
public:
// age de la personne
int GetAge() const;

private:
// date de naissance
Date date_of_birth;
};

L'âge d'une personne évolue constamment au fil du temps, c'est pourquoi il a été décidé dans cet exemple de ne pas le
stocker mais de conserver à la place sa date de naissance. L'accesseur GetAge se charge de calculer son âge courant à

- 26 -
Les sources présentées sur cette pages sont libres de droits, et vous pouvez les utiliser à votre convenance. Par contre la page de présentation de ces sources
constitue une oeuvre intellectuelle protégée par les droits d'auteurs. Copyright © 2008 Developpez LLC. Tout droits réservés Developpez LLC. Aucune
reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents et images sans l'autorisation expresse de Developpez
LLC. Sinon vous encourez selon la loi jusqu'à 3 ans de prison et jusqu'à 300 000 E de dommages et intérêts.
http://cpp.developpez.com/faq/cpp/
partir de sa date de naissance et de la date du jour. Ainsi nous avons bien un accesseur sur la propriété Age de la classe,
mais il n'y a pas de transposition directe sous forme de donnée membre int age; pour autant. On utilise à la place une
autre donnée membre : la date de naissance. S'agissant d'un détail d'implémentation, aucun accesseur n'existe pour
renvoyer cette dernière.
Cet exemple illustre bien le fait qu'un accesseur exporte une propriété qui n'a nullement l'obligation d'exister de manière
explicite dans la classe. De même, une variable membre ne doit pas forcément être exportée via un accesseur, comme
dans cet exemple avec la date de naissance.
Un autre exemple typique est celui de la classe Temperature qui permet de manipuler des températures en degrés
Celsius ou Fahrenheit :

class Temperature
{
public:
// degrés Celsius
double GetCelsius() const
{
return this->temp_celsius;
}
void SetCelsius( double NewTemp )
{
this->temp_celsius = NewTemp;
}

// degrés Fahrenheit
double GetFahrenheit() const
{
return ( ( this->temp_celsius * 9.0 ) / 5.0 ) + 32.0;
}
void SetFahrenheit( double NewTemp )
{
this->temp_celsius = ( NewTemp - 32.0 ) * 5.0 / 9.0;
}

private:
// en interne, on stocke en degrés Celsius
double temp_celsius;
};

D'un point de vue logique il y a deux propriétés : Celsius et Fahrenheit. Mais en interne il n'y a qu'une seule donnée
membre. Imaginons maintenant que l'utilisation de cette classe montre que la plupart du temps on manipule les
températures en degrés Fahrenheit, ce qui a chaque fois nécessite de faire un calcul de conversion. On décide alors de
changer l'implémentation de la classe pour stocker directement en Fahrenheit, ce qui donne :

class Temperature
{
public:
// degrés Celsius
double GetCelsius() const
{
return ( this->temp_fahrenheit - 32.0 ) * 5.0 / 9.0;
}
void SetCelsius( double NewTemp )
{
this->temp_fahrenheit = ( ( NewTemp * 9.0 ) / 5.0 ) + 32.0;
}

// degrés Fahrenheit
double GetFahrenheit() const
{
return this->temp_fahrenheit;
}
void SetFahrenheit( double NewTemp )
{

- 27 -
Les sources présentées sur cette pages sont libres de droits, et vous pouvez les utiliser à votre convenance. Par contre la page de présentation de ces sources
constitue une oeuvre intellectuelle protégée par les droits d'auteurs. Copyright © 2008 Developpez LLC. Tout droits réservés Developpez LLC. Aucune
reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents et images sans l'autorisation expresse de Developpez
LLC. Sinon vous encourez selon la loi jusqu'à 3 ans de prison et jusqu'à 300 000 E de dommages et intérêts.
http://cpp.developpez.com/faq/cpp/
this->temp_fahrenheit = NewTemp;
}

private:
// en interne, on stocke en degrés Fahrenheit
double temp_fahrenheit;
};

Comme on peut le constater, cette nouvelle implémentation est sans conséquence d'un point de vue logique sur la classe.
Son interface est intacte, ce qui la rend inchangée vis à vis de l'extérieur. Pourtant en interne il a été fait des modifications
qui la rendent plus performante. C'est un des intérêts des accesseurs : s'adapter de façon transparente aux évolutions
de l'implémentation, chose que l'on ne peut pas garantir avec des données membre publiques.
Vous l'aurez compris : le choix de définir des accesseurs / mutateurs doit être en accord avec la conception et l'analyse
du problème. Il ne faut pas systématiser leur définition pour toutes les données membres d'une classe.

La conception d'une classe doit-elle se faire plutôt par l'extérieur ou par l'intérieur ?
Auteurs : Marshall Cline ,
Par l'extérieur !

Une bonne interface fournit une vue simplifiée exprimée dans le vocabulaire de l'utilisateur. Dans le cas de la
programmation par objets, une interface est généralement représentée par une classe unique ou par un groupe de
classes très proches.

Réfléchissez d'abord à ce qu'un objet de la classe est du point de vue logique, plutôt que de réfléchir à la façon dont
vous allez le représenter physiquement. Imaginez par exemple que vous ayez une classe Stack (une pile) et que vous
vouliez que son implémentation utilise une LinkedList (une liste chaînée)

class Stack {
public:
// ...
private:
LinkedList list_;
};

La classe Stack doit-elle avoir une fonction membre get() qui retourne la LinkedList ? Ou une fonction set() qui prenne
une LinkedList ? Ou encore une constructeur qui prenne une LinkedList ? La réponse est évidemment non, puisque la
conception d'une classe doit s'effectuer de l'extérieur vers l'intérieur. Les utilisateurs des objets Stack n'ont rien à faire
des LinkedLists ; ce qui les intéresse, c'est de pouvoir faire des push (empiler) et des pop (dépiler).

Voyons maintenant un cas un peu plus subtil. Supposez que l'implémentation de la classe LinkedList soit basée sur une
liste chaînée d'objets Node (noeuds), et que chaque Node ait un pointeur sur le Node suivant :

class Node
{
/*...*/
};

class LinkedList {
public:
// ...
private:
Node* first_;
};

- 28 -
Les sources présentées sur cette pages sont libres de droits, et vous pouvez les utiliser à votre convenance. Par contre la page de présentation de ces sources
constitue une oeuvre intellectuelle protégée par les droits d'auteurs. Copyright © 2008 Developpez LLC. Tout droits réservés Developpez LLC. Aucune
reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents et images sans l'autorisation expresse de Developpez
LLC. Sinon vous encourez selon la loi jusqu'à 3 ans de prison et jusqu'à 300 000 E de dommages et intérêts.
http://cpp.developpez.com/faq/cpp/
La classe LinkedList doit-elle avoir une fonction get() qui donne accès au premier Node ? L'objet Node doit-il avoir une
fonction get() qui permette aux utilisateurs de passer au Node suivant dans la chaîne ? La question est en fait : à quoi
une LinkedList doit-elle ressembler vu de l'extérieur ? Une LinkedList est-elle vraiment une chaîne d'objets Node ?
Ou cela n'est-il finalement qu'un détail d'implémentation ? Et si c'est juste un détail d'implémentation, comment la
LinkedList va-t-elle donner à ses utilisateurs la possibilité d'accéder à chacun de ses éléments ?

Une réponse parmi d'autres : une LinkedList n'est pas une chaîne d'objets Nodes. C'est peut-être bien comme ça qu'elle
est implémentée, mais ce n'est pas ce qu'elle est. Ce qu'elle est, c'est une suite d'éléments. L'abstraction LinkedList doit
donc être fournie avec une classe "LinkedListIterator", et c'est cette classe "LinkedListIterator" qui doit disposer d'un
operator++ permettant de passer à l'élément suivant, ainsi que de fonctions get()/set() donnant accès à la valeur stockée
dans un Node (la valeur stockée dans un Node est sous l'unique responsabilité de l'utilisateur de la LinkedList, c'est
pourquoi il faut des fonctions get()/set() permettant à cet utilisateur de la manipuler comme il l'entend).

Toujours du point de vue de l'utilisateur, il pourrait être souhaitable que la classe LinkedList offre un moyen d'accéder
à ses éléments qui mimique la façon dont on accède aux éléments d'un tableau en utilisant l'arithmétique des pointeurs :

void userCode(LinkedList& a)
{
for (LinkedListIterator p = a.begin(); p != a.end(); ++p)
cout << *p << '\n';
}

Pour implémenter cette interface, la LinkedList va avoir besoin d'une fonction begin() et d'une fonction end(). Ces
fonctions devront renvoyer un objet de type "LinkedListIterator". Et cet objet "LinkedListIterator" aura lui besoin :
d'une fonction pour se déplacer vers l'avant (de façon à pouvoir écrire ++p); d'une fonction pour pouvoir accéder à
la valeur de l'élément courant (de façon à pouvoir écrire *p); et d'un opérateur de comparaison (de façon à pouvoir
écrire p != a.end()).

Le code se trouve ci-dessous. L'idée centrale est que la classe LinkedList n'a pas de fonction donnant accès aux Nodes.
Les Nodes sont une technique d'implémentation, technique qui est complètement masquée. Les internes de la classe
LinkedList pourraient tout à fait être remplacés par une liste doublement chaînée, ou même par un tableau, avec pour
seule différence une modification au niveau de la performance des fonctions prepend(elem) et append(elem).

#include <cassert> // Succédané de gestion d'exceptions

class LinkedListIterator;
class LinkedList;

class Node {
// Pas de membres public; c'est une "classe privée"
friend LinkedListIterator; // Une classe amie
friend LinkedList;
Node* next_;
int elem_;
};

class LinkedListIterator {
public:
bool operator== (LinkedListIterator i) const;
bool operator!= (LinkedListIterator i) const;
void operator++ (); // Aller à l'élément suivant
int& operator* (); // Accéder à l'élément courant
private:
LinkedListIterator(Node* p);
Node* p_;
};

class LinkedList {
public:

- 29 -
Les sources présentées sur cette pages sont libres de droits, et vous pouvez les utiliser à votre convenance. Par contre la page de présentation de ces sources
constitue une oeuvre intellectuelle protégée par les droits d'auteurs. Copyright © 2008 Developpez LLC. Tout droits réservés Developpez LLC. Aucune
reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents et images sans l'autorisation expresse de Developpez
LLC. Sinon vous encourez selon la loi jusqu'à 3 ans de prison et jusqu'à 300 000 E de dommages et intérêts.
http://cpp.developpez.com/faq/cpp/
void append(int elem); // Ajoute elem après le dernier élément
void prepend(int elem); // Ajoute elem avant le premier élément
// ...
LinkedListIterator begin();
LinkedListIterator end();
// ...
private:
Node* first_;
};

Les fonctions membres suivantes sont de bonnes candidates pour être inline (à mettre sans doute dans le même .h):

inline bool LinkedListIterator::operator== (LinkedListIterator i) const


{
return p_ == i.p_;
}

inline bool LinkedListIterator::operator!= (LinkedListIterator i) const


{
return p_ != i.p_;
}

inline void LinkedListIterator::operator++()


{
assert(p_ != NULL); // ou bien if (p_==NULL) throw ...
p_ = p_->next_;
}

inline int& LinkedListIterator::operator*()


{
assert(p_ != NULL); // ou bien if (p_==NULL) throw ...
return p_->elem_;
}

inline LinkedListIterator::LinkedListIterator(Node* p)
: p_(p)
{
}

inline LinkedListIterator LinkedList::begin()


{
return first_;
}

inline LinkedListIterator LinkedList::end()


{
return NULL;
}

Pour conclure : la liste chaînée gère deux sortes de données différentes. On trouve d'un côté les valeurs des éléments
qui sont stockés dans la liste chaînée. Ces valeurs sont sous la responsabilité de l'utilisateur de la liste et seulement
de l'utilisateur. La liste elle-même ne fera rien par exemple pour empêcher à un utilisateur de donner la valeur 5 au
troisième élément, même si ça n'a pas de sens dans le contexte de cet utilisateur. On trouve de l'autre côté les données
d'implémentation de la liste (pointeurs next, etc.), dont les valeurs sont sous la responsabilité de la liste et seulement
de la liste, laquelle ne donne aux utilisateurs aucun accès (que ce soit en lecture ou en écriture) aux divers pointeurs
qui composent son implémentation.

Ainsi, les seules fonctions get()/set() présentes sont là pour permettre la modification des éléments de la liste chaînée,
mais ne permettent absolument pas la modification des données d'implémentation de la liste. Et la liste chaînée ayant
complètement masqué son implémentation, elle peut donner des garanties très fortes concernant cette implémentation
(dans le cas d'une liste doublement chaînée par exemple, la garantie pourrait être qu'il y a pour chaque pointeur avant,
un pointeur arrière dans le Node suivant).

- 30 -
Les sources présentées sur cette pages sont libres de droits, et vous pouvez les utiliser à votre convenance. Par contre la page de présentation de ces sources
constitue une oeuvre intellectuelle protégée par les droits d'auteurs. Copyright © 2008 Developpez LLC. Tout droits réservés Developpez LLC. Aucune
reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents et images sans l'autorisation expresse de Developpez
LLC. Sinon vous encourez selon la loi jusqu'à 3 ans de prison et jusqu'à 300 000 E de dommages et intérêts.
http://cpp.developpez.com/faq/cpp/
Nous avons donc vu un exemple dans lequel les valeurs de certaines des données d'une classe étaient sous la
responsabilité des utilisateurs de la classe (et la classe a besoin d'exposer des fonctions get()/set() pour ces données) mais
dans lequel les données contrôlées uniquement par la classe ne sont pas nécessairement accessibles par des fonctions
get()/set().

Note : le but de cet exemple n'était pas de vous montrer comment écrire une classe de liste chaînée. Et d'abord, vous ne
devriez pas "pondre" votre propre classe liste, vous devriez plutôt utiliser l'une des classes de type "conteneur standard"
fournie avec votre compilateur. La meilleure solution est d'utiliser l'une des classes conteneurs du standard C++ , par
exemple la classe template list<T>.

Qu'est-ce que le polymorphisme ?


Auteurs : Jean-Marc.Bourguet , 3DArchi ,
Le polymorphisme, c'est la capacité d'une expression à être valide quand les valeurs présentes ont des types différents.
On trouve différents types de polymorphismes :

• ad-hoc : surcharge et coercition ;


• universel (ou non ad-hoc) : paramétrique et d'inclusion.

Ces deux distinctions segmentent le polymorphisme suivant l'axe de réutilisabilité face à un nouveau type : le
polymorphisme ad-hoc nécessite une nouvelle définition pour chaque nouveau type alors que le polymorphisme
universel recouvre un ensemble potentiellement illimité de types.

lien :
lien :
lien :
lien : Qu'est-ce que la coercition ?
lien : Qu'est-ce que la surcharge ?
lien : Qu'est-ce que le polymorphisme paramétrique ?
lien : Qu'est-ce que le polymorphisme d'inclusion ?

Qu'est-ce que la coercition ?


Auteurs : 3DArchi ,
Comme Mr Jourdain écrivait de la prose sans le savoir, vous avez certainement déjà utilisé la coercition sans le savoir.
Derrière cette expression se cache tout simplement les mécanismes de conversion implicite :

int op1(1);
double op2(2.1);
double result = op1 + op2; // polymorphisme de coercition

Ici, op1 est implicitement converti en double pour faire l'opération d'addition.
Une classe s'appuie sur la définition d'opérateur de conversion pour pouvoir être utilisée dans ce type de
polymorphisme :

#include <iostream>

class CMyClass
{
public:

- 31 -
Les sources présentées sur cette pages sont libres de droits, et vous pouvez les utiliser à votre convenance. Par contre la page de présentation de ces sources
constitue une oeuvre intellectuelle protégée par les droits d'auteurs. Copyright © 2008 Developpez LLC. Tout droits réservés Developpez LLC. Aucune
reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents et images sans l'autorisation expresse de Developpez
LLC. Sinon vous encourez selon la loi jusqu'à 3 ans de prison et jusqu'à 300 000 E de dommages et intérêts.
http://cpp.developpez.com/faq/cpp/
operator bool()const
{
return true;
}
};

int main()
{
CMyClass a;
std::cout<<std::boolalpha<< a<<std::endl;
return 0;
}

L'objet 'a' est implicitement converti en bool. Le code présenté ici illustre la définition. Il est en général déconseillé
d'utiliser ce mécanisme (cf bool idiom), les cas pertinents demeurant rares (enveloppes sur des handles CWnd<->HWND
par exemple).

lien : Qu'est-ce que le polymorphisme ?


lien : The Safe Bool Idiom, par Bjorn Karlsson

Qu'est-ce que la surcharge ?


Auteurs : 3DArchi ,
Il s'agit probablement d'une des premières caractéristiques du C++ présentées : la capacité à surcharger une opération
avec différentes signatures :

void function(int);
void function(double);
void function(CMyClass);
// ...

L'adjonction d'un nouveau type passe par la définition d'une nouvelle surcharge pour ce type.
Les fonctions mathématiques (std::sqrt) par exemple sont définies sur plusieurs types (pour std::sqrt, en général, float,
double et long double).
C'est aussi par cette approche que les flux sont utilisés de façon polymorphe.

lien : Qu'est-ce que le polymorphisme ?


lien : Comment utiliser les flux pour afficher ou saisir mes objets ?

Qu'est-ce que le polymorphisme paramétrique ?


Auteurs : 3DArchi ,
Le polymorphisme paramétrique passe par l'utilisation des techniques génériques pour offrir un même service pour
tout un ensemble de types :

template<class T>
void dump(T var)
{
std::cout<<Timestamp()<<" : "<<var<<std::endl;
}

Cette opération peut être appelée pour n'importe quel type d'objet du moment qu'il supporte l'opérateur << sur le
flux de sortie.

- 32 -
Les sources présentées sur cette pages sont libres de droits, et vous pouvez les utiliser à votre convenance. Par contre la page de présentation de ces sources
constitue une oeuvre intellectuelle protégée par les droits d'auteurs. Copyright © 2008 Developpez LLC. Tout droits réservés Developpez LLC. Aucune
reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents et images sans l'autorisation expresse de Developpez
LLC. Sinon vous encourez selon la loi jusqu'à 3 ans de prison et jusqu'à 300 000 E de dommages et intérêts.
http://cpp.developpez.com/faq/cpp/
La plupart des bibliothèques modernes en C++ s'appuient sur ce mécanisme. C'est le cas de la STL ou de Boost par
exemple.
On parle parfois de polymorphisme contraint ou borné lorsqu'il s'agit d'introduire des contraintes sur les types avec
lesquels une fonction ou une classe générique peut effectivement être instanciée. Cela n'est pas possible nativement avec
le C++ mais peut être mis en #uvre en combinant les classes traits et des bibliothèques comme Boost (boost::enable_if).

lien : Qu'est-ce que le polymorphisme ?

Qu'est-ce que le polymorphisme d'inclusion ?


Auteurs : 3DArchi ,
Souvent résumé tout simplement (et trop hativement) à 'polymorphisme', le polymorphisme d'inclusion s'appuie sur
l'héritage public :

void function(IInterface const &var_)


{
var_.Action();
}
class IInterface
{//...
};
class CConcrete : public IInterface
{//...
};

int main()
{
CConcrete c;
Fonction(c);
return 0;
}

Les fonctions virtuelles utilisent bien sûr ce polymophisme.


Le polymorphisme d'inclusion doit faire sens avec l'héritage public. Il ne doit pas être utilisé uniquement pour bénéficier
d'une surcharge.

lien : Qu'est-ce que le polymorphisme ?

Qu'est-ce qui est entendu par "paramétrer un comportement" ?


Auteurs : Alp Mestan , 3DArchi ,
Il s'agit simplement en fait d'introduire un point de variabilité dans votre code, de faire en sorte que selon <on ne sait
pas trop quoi>, le comportement de ce morceau de code soit différent.
Le comportement d'un code C++ peut être paramétré de différentes façons :

• pendant l'écriture du code : les templates ;


• à la compilation : surcharges, conversions implicites et directives de compilation ;
• à l'édition des liens ;
• à l'exécution par le chargement dynamique de bibliothèque ;
• à l'exécution par le polymorphisme d'inclusion (fonctions virtuelles).

lien : Multi-Paradigm Design, de James O. Coplien


lien : Multi-Paradigm Design for C++, de James O. Coplien

- 33 -
Les sources présentées sur cette pages sont libres de droits, et vous pouvez les utiliser à votre convenance. Par contre la page de présentation de ces sources
constitue une oeuvre intellectuelle protégée par les droits d'auteurs. Copyright © 2008 Developpez LLC. Tout droits réservés Developpez LLC. Aucune
reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents et images sans l'autorisation expresse de Developpez
LLC. Sinon vous encourez selon la loi jusqu'à 3 ans de prison et jusqu'à 300 000 E de dommages et intérêts.
http://cpp.developpez.com/faq/cpp/
lien : Comment varier le comportement au moment de l'exécution par le polymorphisme d'inclusion ?
lien : Comment varier le comportement au moment de l'écriture de code (template) ?
lien : Comment varier le comportement à la compilation par les directives du préprocesseur ?
lien : Comment varier le comportement à l'édition des liens ?
lien : Comment varier le comportement à l'exécution par le chargement dynamique de bibliothèque ?
lien : Comment varier le comportement au moment de l'exécution par agrégation ?
lien : Comment choisir entre les différents types de paramétrage de comportement ?

Comment varier le comportement au moment de l'exécution par le polymorphisme d'inclusion ?


Auteurs : Alp Mestan , 3DArchi ,
En C++, on utilise souvent l'héritage pour ce faire. En effet, imaginez que nous soyons en présence d'une hiérarchie
de composants graphiques, dont la classe de base serait Widget. On aurait ainsi Button et Textfield qui hériteraient de
Widget par exemple. Enfin, chacun possèderait une méthode show() qui permet d'afficher le composant en question.
Bien entendu, un Button et un Textfield étant de natures différentes, leur affichage le serait aussi. C'est grâce au
polymorphisme d'héritage, mis en oeuvre en C++ grâce au mot clé virtual, que l'on peut réaliser cela dynamiquement :
à l'exécution du programme, il sera choisi d'utiliser la méthode Button::show() ou la méthode Textfield::show() selon
le type réel de l'objet sur lequel on appelle show(). Voici un exemple minimal illustrant cela.

class Widget
{
public:
virtual ~Widget() { /* ... */ }
void show()
{
// ...
do_show();
// ...
}
// ...

private :
virtual void do_show()=0; // fonction virtuelle pure
};

class Button : public Widget


{
private :
virtual void do_show() { std::cout << "Button" << std::endl; }
// ...
};

class Textfield : public Widget


{
private :
virtual void do_show() { std::cout << "Textfield" << std::endl; }
// ...
};

void show_widget(Widget& w)
{
w.show();
}

// ...

Button b;
Textfield t;

show_widget(b); // affiche "Button"


show_widget(t); // affiche "Textfield"

- 34 -
Les sources présentées sur cette pages sont libres de droits, et vous pouvez les utiliser à votre convenance. Par contre la page de présentation de ces sources
constitue une oeuvre intellectuelle protégée par les droits d'auteurs. Copyright © 2008 Developpez LLC. Tout droits réservés Developpez LLC. Aucune
reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents et images sans l'autorisation expresse de Developpez
LLC. Sinon vous encourez selon la loi jusqu'à 3 ans de prison et jusqu'à 300 000 E de dommages et intérêts.
http://cpp.developpez.com/faq/cpp/
Dans ce cas, rien à redire, vous avez fait un choix correct.

Comment varier le comportement au moment de l'écriture de code (template) ?


Auteurs : Alp Mestan , 3DArchi ,
Imaginez que vous ayez conçu une classe qui encapsule un calcul très lourd, au point que vous ayez mis sur pieds
deux implémentations, l'une monothreadée, l'autre multithreadée. Il serait dommage de les faire hériter d'une classe
abstraite et d'en hériter pour chacune des versions, induisant un coût à cause de la virtualité, qui est ici superflue. Vous
avez une possibilité qui vous permettra de tout gérer à la compilation, en utilisant les templates. Nous allons illustrer
avec une fonction qui mesure le temps pris par le calcul.

template <class Computation>


void do_something()
{
std::time_t start = time(NULL);
Computation::compute();
std::time_t end = time(NULL);
std::cout << end - start << " seconds." << std::endl;
}

struct SingleThreadedComputation
{
static void compute()
{
// implémentation monothread
}
};

struct MultiThreadedComputation
{
static void compute()
{
// implémentation multithread
}
};

// par exemple :
#ifdef STCOMPUTATION
do_something<SingleThreadedComputation>();
#elif defined MTCOMPUTATION
do_something<MultiThreadedComputation>();
#endif

// comportement que l'on peut choisir soit avec un #define,


// soit avec l'option de compilation -DSTCOMPUTATION ou -DMTCOMPUTATION

Ainsi, on a factorisé la variabilité (multithreading ou pas) de notre calcul dans une fonction paramétrée, Compute, sans
ajouter la surcharge induite par l'utilisation de la virtualité. Le "défaut" est que la variabilité est statique, c'est-à-dire
fixée à la compilation, tandis que le polymorphisme lié à l'héritage nous permet d'avoir une variabilité à l'exécution.

- 35 -
Les sources présentées sur cette pages sont libres de droits, et vous pouvez les utiliser à votre convenance. Par contre la page de présentation de ces sources
constitue une oeuvre intellectuelle protégée par les droits d'auteurs. Copyright © 2008 Developpez LLC. Tout droits réservés Developpez LLC. Aucune
reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents et images sans l'autorisation expresse de Developpez
LLC. Sinon vous encourez selon la loi jusqu'à 3 ans de prison et jusqu'à 300 000 E de dommages et intérêts.
http://cpp.developpez.com/faq/cpp/
Cette façon de faire s'approche de ce que l'on appelle le Policy Based Design, qui permet de paramétrer de
manière très flexible le comportement, dès la compilation, avec une utilisation intelligente des templates. C'est une sorte
d'équivalent du Design Pattern Strategy, à la sauce C++ et templates.

Comment varier le comportement à la compilation par les directives du préprocesseur ?


Auteurs : Alp Mestan , 3DArchi ,
Ensuite vient le polymorphisme issu de la manipulation du préprocesseur de votre compilateur. En effet, en jouant avec
les #ifdef, nous pouvons par exemple sélectionner un certain code ou un autre selon des directives de compilation, qui
permettent de modifier le comportement de l'application générée, et ce au moment de compiler. Cela se base sur le
schéma basique suivant (qui a été utilisé pour l'exemple de calcul mono|multithread) :

#ifdef OPTION1
// code 1
#elif defined OPTION2
// code 2
#elif defined OPTION3
// code 3
#else
// code 4
#endif

Ainsi, vous pouvez sélectionner le code à utiliser de 2 façons principalement. Soit vous écrivez dans votre programme
principal quelque chose comme #define OPTION3, soit vous passez l'option -DOPTION3 à votre compilateur. Si aucune
option n'est passée, ici ce sera le code 4 qui sera sélectionné, par exemple.

Comment varier le comportement à l'édition des liens ?


Auteurs : Alp Mestan , 3DArchi ,
Vous pouvez également obtenir du polymorphisme en jouant sur la liaison avec des bibliothèques. A partir d'une même
interface, vous pouvez avoir différentes implémentations produisant des bibliothèques statiques différentes (.lib, .a, ...).
La commande d'édition des liens (ou dans votre makefile ou dans les options d'un projet avec un I.D.E.) précise la
bibliothèque avec laquelle les liens doivent être résolus. L'exécutable généré fait alors appel à l'interface implémentée
dans la bibliothèque avec laquelle il a été liée.

Comment varier le comportement à l'exécution par le chargement dynamique de bibliothèque ?


Auteurs : Alp Mestan , 3DArchi ,
Une application peut choisir de varier son comportement en chargeant dynamiquement des bibliothèques (.dll, .so, ...)
et en allant chercher dans celles-ci l'implémentation de l'interface variable. Le comportement va alors changer selon la
DLL proposée à l'exécution du moment qu'elle respecte l'interface qu'attend le programme.

Comment varier le comportement au moment de l'exécution par agrégation ?


Auteurs : Florian Goujeon ,
S'il est nécessaire de pouvoir modifier le comportement d'un objet au cour de l'exécution, la solution la plus adaptée
est sans doute l'application du design pattern Strategy.

- 36 -
Les sources présentées sur cette pages sont libres de droits, et vous pouvez les utiliser à votre convenance. Par contre la page de présentation de ces sources
constitue une oeuvre intellectuelle protégée par les droits d'auteurs. Copyright © 2008 Developpez LLC. Tout droits réservés Developpez LLC. Aucune
reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents et images sans l'autorisation expresse de Developpez
LLC. Sinon vous encourez selon la loi jusqu'à 3 ans de prison et jusqu'à 300 000 E de dommages et intérêts.
http://cpp.developpez.com/faq/cpp/
Le principe de ce patron de conception est de définir autant de classes que de comportements différents. Toutes ces
classes implémentent une même interface. La classe à paramétrer possède une agrégation vers un objet du type de
l'interface.

//interface de comportement pour baladeur (utilisant le pattern NVI)


class walkman_behaviour
{
public:
virtual
~walkman_behaviour(){}

void
click_back_button()
{
do_click_back_button();
}

void
click_forward_button()
{
do_click_forward_button();
}

private:
virtual
void
do_click_back_button() = 0;

virtual
void
do_click_forward_button() = 0;
};

//collection de comportements pour baladeur


namespace walkman_behaviours
{
class mp3_reader: public walkman_behaviour
{
public:
void
do_click_back_button()
{
//Lire chanson précédente...
}

void
do_click_forward_button()
{
//Lire chanson suivante...
}
};

class fm_tuner: public walkman_behaviour


{
public:
void
do_click_back_button()
{
//Passer à la fréquence précédente...
}

void
do_click_forward_button()
{
//Passer à la fréquence suivante...
}
};

- 37 -
Les sources présentées sur cette pages sont libres de droits, et vous pouvez les utiliser à votre convenance. Par contre la page de présentation de ces sources
constitue une oeuvre intellectuelle protégée par les droits d'auteurs. Copyright © 2008 Developpez LLC. Tout droits réservés Developpez LLC. Aucune
reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents et images sans l'autorisation expresse de Developpez
LLC. Sinon vous encourez selon la loi jusqu'à 3 ans de prison et jusqu'à 300 000 E de dommages et intérêts.
http://cpp.developpez.com/faq/cpp/
}

//baladeur audio
class walkman
{
public:
walkman(walkman_behaviour& c):
behaviour_(&c)
{
}

void
behaviour(walkman_behaviour& c)
{
behaviour_ = &c;
}

void
click_back_button()
{
comportement_->click_back_button();
}

void
click_forward_button()
{
comportement_->click_forward_button();
}

private:
walkman_behaviour* behaviour_;
};

int
main()
{
walkman_behaviours::mp3_reader behave_mp3;
walkman_behaviours::fm_tuner behave_fm;

walkman b(behave_mp3); //comportement par défaut : lecteur mp3


b.click_forward_button(); //lit la chanson suivante

b.behaviour(behave_fm); //changement de comportement


b.click_back_button(); //passe à la fréquence précédente

return 0;
}

Pour changer le comportement de l'objet, il suffit de l'agréger à un autre objet de comportement.

Comment choisir entre les différents types de paramétrage de comportement ?


Auteurs : Alp Mestan , 3DArchi ,
Il faut désormais choisir celui qui convient au type de paramétrage de comportement que vous voulez introduire. Une
application complexe met souvent en #uvre les différentes solutions pour sa variabilité et son extension. Selon les cas, la
variabilité est intégrée par les templates (générique), par l'héritage (expl : Widget), par les directives de compilation ou
l'édition statique de liens (expl : dépendance de plateforme), ou par le chargement dynamique de bibliothèque (expl :
plugin).

- 38 -
Les sources présentées sur cette pages sont libres de droits, et vous pouvez les utiliser à votre convenance. Par contre la page de présentation de ces sources
constitue une oeuvre intellectuelle protégée par les droits d'auteurs. Copyright © 2008 Developpez LLC. Tout droits réservés Developpez LLC. Aucune
reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents et images sans l'autorisation expresse de Developpez
LLC. Sinon vous encourez selon la loi jusqu'à 3 ans de prison et jusqu'à 300 000 E de dommages et intérêts.
http://cpp.developpez.com/faq/cpp/
Sommaire > Les classes en C++
Qu'est-ce qu'une classe ?
Auteurs : LFE , Aurélien Regat-Barrel ,
D'une façon très simple et en faisant le parallèle avec le C, on peut dire qu'une classe est une structure à laquelle on
ajoute des fonctions permettant de gérer cette structure.
Mais outre l'ajout de ces fonctions, il existe deux grandes nouveautés par rapport aux structures du C:

• d'une part, les membres de classe, qu'ils soient des variables ou des fonctions, peuvent être privés c'est-à-dire
inaccessibles en dehors de la classe (par opposition aux membres publics d'une structure C où tous ses membres
sont accessibles).
• d'autre part, une classe peut être dérivée. La classe dérivée hérite alors de toutes les propriétés et fonctions de la
classe mère. Une classe peut d'ailleurs hériter de plusieurs classes simultanément.

Une classe se déclare via le mot-clé class suivi du nom de la classe, d'un bloc (accolades ouvrante et fermante) et d'un
point virgule (ne pas l'oublier !). Les membres de la classe (variables ou fonctions) doivent être déclarés à l'intérieur
de ce bloc, à la manière des structures en C.

class MaClasse
{
// Déclaration de toutes les variables ou fonctions membres
// constitutives de la classe

int a; // variable membre


void b(); // fonction membre
};

Quand est-ce qu'une classe a une sémantique de valeur ?


Auteurs : Medinoc , 3DArchi ,
On dit d'une classe qu'elle a une sémantique de valeur si deux objets situés à des adresses différentes, mais au contenu
identique, sont considérés égaux.
Par exemple, une classe modélisant une somme d'argent a une sémantique de valeur. Une somme de 100 # est toujours
une somme de 100#. On peut ajouter, soustraire, multiplier différentes sommes d'argent. Deux sommes de 100 # sont
identiques même si l'une est sous forme d'espèces et l'autre sous forme de chèque.
On voit qu'il peut être pertinent :

• de redéfinir des opérateurs arithmétiques (+, -, *, /) ;


• d'avoir un opérateur d'affectation (=, constructeur par copie ?) ;
• de redéfinir des opérateurs de comparaison (==, <, etc.).

Inversement, une classe à sémantique de valeur n'a pas beaucoup de sens pour servir de classe de base à un héritage.
On ne trouvera donc en général pas de fonction virtuelle dans une classe à sémantique de valeur.

Quand est-ce qu'une classe a une sémantique de d'entité ?


Auteurs : Davidbrcz , 3DArchi ,
A l'inverse des classes à sémantique de valeur, une classe a une sémantique d'entité si toutes les instances de cette classe
sont nécessairement deux à deux distinctes, même si tous les champs de ces instances sont égaux. Elle modélise un
concept d'identité : chaque objet représente un individu unique.

- 39 -
Les sources présentées sur cette pages sont libres de droits, et vous pouvez les utiliser à votre convenance. Par contre la page de présentation de ces sources
constitue une oeuvre intellectuelle protégée par les droits d'auteurs. Copyright © 2008 Developpez LLC. Tout droits réservés Developpez LLC. Aucune
reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents et images sans l'autorisation expresse de Developpez
LLC. Sinon vous encourez selon la loi jusqu'à 3 ans de prison et jusqu'à 300 000 E de dommages et intérêts.
http://cpp.developpez.com/faq/cpp/
Une classe modélisant un compte a une sémantique d'entité. Deux comptes sont distincts même s'ils ont la même somme
d'argent. Cela n'a pas de sens d'ajouter des comptes (on peut vider un compte pour le verser dans un autre, mais ce
n'est pas un ajout). En revanche, on peut avoir des comptes courants, des comptes d'épargnes, des comptes titres, etc.
On voit qu'une classe à sémantique d'entité peut servir de base à un héritage. Mais, une classe à sémantique d'entité :

• ne redéfinit pas les opérateurs arithmétiques (+,-,/*) ;


• n'a pas d'opérateur d'affectation (=, constructeur par copie) ;
• ne redéfinit pas les opérateurs de comparaison (==, < etc.).

Si on veut créer une copie d'un objet à sémantique d'entité, on s'appuie sur une méthode Clone spécifique retournant
un nouvel objet dans un état semblable.

Comment structurer ma classe en un fichier .h et un fichier .cpp ?


Auteurs : Aurélien Regat-Barrel , Laurent Gomila ,
Une classe est déclarée dans un fichier header (extension .h, .hpp. ou encore .hxx) dont l'inclusion multiple est protégée
grâce à des directives du préprocesseur :

maclasse.h
#ifndef MACLASSE_H
#define MACLASSE_H

class MaClasse
{
public:
void Fonction();
};

#endif

Le corps de la classe est généralement placé dans un fichier séparé dont l'extension varie (.cpp, .cxx, .C). Ce fichier
contient le code compilable :

maclasse.cpp
#include "maclasse.h"

void MaClasse::Fonction()
{
// implémentation de la fonction
}

Pour utiliser une classe dans d'autres fichiers .cpp, il suffira d'inclure l'en-tête qui la déclare ; c'est l'éditeur de liens qui
se chargera de trouver tout seul où se trouve le corps des fonctions (ie. vous n'avez pas à vous en préoccuper). Certains
seront parfois tentés d'inclure un fichier .cpp : c'est une erreur et cela ne doit jamais être fait (il en résulterait plusieurs
corps pour une même fonction, par exemple).

autreclasse.cpp
#include "autreclasse.h"
#include "maclasse.h"

void AutreClasse::Fonction()
{
MaClasse M;
M.Fonction();
}

- 40 -
Les sources présentées sur cette pages sont libres de droits, et vous pouvez les utiliser à votre convenance. Par contre la page de présentation de ces sources
constitue une oeuvre intellectuelle protégée par les droits d'auteurs. Copyright © 2008 Developpez LLC. Tout droits réservés Developpez LLC. Aucune
reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents et images sans l'autorisation expresse de Developpez
LLC. Sinon vous encourez selon la loi jusqu'à 3 ans de prison et jusqu'à 300 000 E de dommages et intérêts.
http://cpp.developpez.com/faq/cpp/
Le corps de certaines fonctions peut figurer dans le header, en particulier pour les fonctions inline et dans le cas de
fonctions/classes templates (lire à ce sujet Pourquoi mes templates ne sont-ils pas reconnus à l'édition des liens ? ).
Attention à ne pas oublier le mot clé inline si vous placez le corps de fonctions dans un header ailleurs que dans la
déclaration d'une classe :

maclasse.h
#ifndef MACLASSE_H
#define MACLASSE_H

class MaClasse
{
public:
void FonctionInline()
{
// placée dans la déclaration de la classe
// cette fonction est considérée en ligne sans
// que le mot clé inline ne figure
}
};

// placée en dehors de la déclaration de la classe, le mot clé inline


// est indispensable pour ne pas provoquer des définitions multiples
// de la fonction et donc des erreurs à l'édition de liens
inline void FonctionInline2()
{
}
#endif

Soyez aussi vigilants au respect de la casse dans l'inclusion d'une header. Si vous incluez maclasse.h, veillez à bien
nommer votre fichier header maclasse.h et non MaClasse.h car certains systèmes font la distinction de casse dans le
nom des fichiers et cela s'applique aussi lors de leur inclusion dans un fichier C++.

Qu'est-ce "this" ?
Auteurs : LFE ,
this est un pointeur créé par défaut et qui désigne l'objet lui-même. A noter que this est un pointeur non modifiable,
l'adresse pointée ne peut être changée (ce qui est d'ailleurs parfaitement logique).

Comment puis-je empêcher les autres programmeurs de violer


l'encapsulation en accédant aux membres privés de mes classes ?
Auteurs : Marshall Cline ,
Le jeu n'en vaut pas la chandelle, l'encapsulation est faite pour le code, pas pour les gens.

Ce n'est pas violer l'encapsulation pour un programmeur que de voir les parties privées et/ou protégées de vos classes,
tant qu'il n'écrit pas de code qui dépende d'une façon ou d'une autre de ce qu'il voit. En d'autres termes, l'encapsulation
n'empêche pas les gens de découvrir comment est constituée une classe; cela empêche que le code que l'on écrit ne soit
dépendant de l'intérieur de la classe. La société qui vous emploie ne doit pas payer un "contrat de maintenance" pour
entretenir la matière grise qui se trouve entre vos 2 oreilles, mais elle doit payer pour entretenir le code qui sort de vos
doigts. Ce que vous savez en tant que personne n'augmente pas le coût de maintenance, partant du principe que le code
que vous écrivez dépend de l'interface plus que de l'implémentation.

- 41 -
Les sources présentées sur cette pages sont libres de droits, et vous pouvez les utiliser à votre convenance. Par contre la page de présentation de ces sources
constitue une oeuvre intellectuelle protégée par les droits d'auteurs. Copyright © 2008 Developpez LLC. Tout droits réservés Developpez LLC. Aucune
reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents et images sans l'autorisation expresse de Developpez
LLC. Sinon vous encourez selon la loi jusqu'à 3 ans de prison et jusqu'à 300 000 E de dommages et intérêts.
http://cpp.developpez.com/faq/cpp/
D'un autre coté, ce n'est rarement, voire jamais un problème. Je ne connais aucun programmeur qui ait
intentionnellement essayé d'accéder aux parties privées d'une classe. "Mon avis dans un tel cas de figure serait de
changer le programmeur et non le code" (James Kanze, avec son autorisation)

Comment dériver une classe à partir d'une autre ?


Auteurs : LFE ,
Pour dériver une classe à partir d'une autre, il suffit de faire suivre la déclaration de la classe dérivée de : suivi d'un
modificateur d'accès et du nom de la classe mère.

class mere
{
// ...
}

class fille : public mere


{
// ...
}

L'héritage peut être public (public), privé (private) ou protégé (protected).


Si l'héritage est public, les membres de la classe mère garderont leur accès, à savoir les membres publics restent publics
et les protégés restent protégés.
Si l'héritage est protected, les membres publics de la classe mère deviendront protégés dans la classe fille, et les protégés
resteront tels quels.
Si l'héritage est private, tous les membres de la classe mère, qu'ils aient été publics ou protégés deviennent privés.
Les membres privés de la classe mère sont inaccessibles depuis la classe fille, quelque soit le type d'héritage.

Comment faire pour empêcher de créer plus d'une instance d'une classe ?
Auteurs : Laurent Gomila , Aurélien Regat-Barrel ,
Pour limiter le nombre d'instances d'une classe, on utilise le design pattern du singleton. Il permet de contrôler le nombre
d'instances d'une classe (en général une seule). Voici un exemple typique d'implémentation en C++ :

// fichier Singleton.h
#ifndef SINGLETON_H
#define SINGLETON_H

class Singleton
{
public :
// Fonction permettant de récupérer l'instance unique de notre classe
static Singleton & GetInstance();

private :

// On empêche la création directe d'objets Singleton en mettant son constructeur par défaut privé
// Ainsi on oblige l'utilisateur à passer par GetInstance
Singleton() {}

// On empêche la recopie d'objets Singleton en mettant le constructeur par copie


// et l'opérateur d'affectation en privé
// On peut même ne pas les implémenter, c'est encore mieux
Singleton( const Singleton & );
Singleton & operator =( const Singleton & );
};

- 42 -
Les sources présentées sur cette pages sont libres de droits, et vous pouvez les utiliser à votre convenance. Par contre la page de présentation de ces sources
constitue une oeuvre intellectuelle protégée par les droits d'auteurs. Copyright © 2008 Developpez LLC. Tout droits réservés Developpez LLC. Aucune
reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents et images sans l'autorisation expresse de Developpez
LLC. Sinon vous encourez selon la loi jusqu'à 3 ans de prison et jusqu'à 300 000 E de dommages et intérêts.
http://cpp.developpez.com/faq/cpp/
#endif

// fichier Singleton.cpp
#include "Singleton.h"

Singleton & Singleton::GetInstance()


{
static Singleton
instance; // instance unique cachée dans la fonction. Ne pas oublier le static !
return instance;
}

// exemple d'utilisation
#include "Singleton.h"

int main()
{
// Essai : on tente de créer un Singleton
Singleton erreur; // Ne compile pas - le constructeur par défaut n'est pas accessible

// Récupération de l'instance unique de la classe


Singleton & s = Singleton::GetInstance();
}

Bien sûr, ce n'est qu'un exemple d'implémentation. Selon les besoins on pourra coder notre singleton différemment.
Par exemple, GetInstance() peut renvoyer un pointeur fraîchement alloué, ce qui peut être justifié si l'on veut disposer
de plus d'une instance, ou si l'on veut contrôler le moment de sa destruction (dans le cas de dépendances entre plusieurs
singletons par exemple). A chaque appel un compteur interne est incrémenté et au delà d'un certain nombre d'instances
la fonction refuse d'en allouer de nouvelles. Il ne faut en revanche pas oublier de libérer les pointeurs ainsi obtenus.
On peut également modifier le code pour le rendre thread-safe en ajoutant une protection de type verrou dans
GetInstance() si l'on fait du multithreading.

Que signifient public, private et protected ?


Auteurs : LFE , Laurent Gomila ,
Un membre déclaré public dans une classe peut être accédé par toutes les autres classes et fonctions.
Un membre déclaré protected dans une classe ne peut être accédé que par les autres membres de cette même classe
ainsi que par les membres des classes dérivées.
Un membre déclaré private dans une classe ne peut être accédé que par les autres membres de cette même classe.

Ces mots-clés permettent également de modifier la visibilité des membres dans la classe dérivée lors d'un héritage :

Héritage
Accès aux public protected private
données
public public protected private
protected protected protected private
private interdit interdit interdit

Exemple :

class A
{
public :

- 43 -
Les sources présentées sur cette pages sont libres de droits, et vous pouvez les utiliser à votre convenance. Par contre la page de présentation de ces sources
constitue une oeuvre intellectuelle protégée par les droits d'auteurs. Copyright © 2008 Developpez LLC. Tout droits réservés Developpez LLC. Aucune
reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents et images sans l'autorisation expresse de Developpez
LLC. Sinon vous encourez selon la loi jusqu'à 3 ans de prison et jusqu'à 300 000 E de dommages et intérêts.
http://cpp.developpez.com/faq/cpp/
int x;

protected :

int y;

private :

int z;
};

class B : private A
{
// x et y sont ici en accès privé, et z est innaccessible
}

Quelle est la différence entre class et struct ?


Auteurs : Laurent Gomila , Aurélien Regat-Barrel ,
Contrairement à ce que l'on pourrait penser, les classes (class) et les structures (struct) sont équivalentes en C++.

Seules trois différences mineures existent :

1. La visibilité par défaut est public pour les structures, private pour les classes.

struct A
{
int a;

private :

int b;
};

class B
{
int b;

public :

int a;
};

// Ici, A et B sont équivalents

2. Le mode d'héritage par défaut est public pour les structures, private pour les classes.

class Base {}; // class ou struct, peu importe

class A1 : public Base


{
};

struct A2 : Base
{
};

class B1 : Base

- 44 -
Les sources présentées sur cette pages sont libres de droits, et vous pouvez les utiliser à votre convenance. Par contre la page de présentation de ces sources
constitue une oeuvre intellectuelle protégée par les droits d'auteurs. Copyright © 2008 Developpez LLC. Tout droits réservés Developpez LLC. Aucune
reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents et images sans l'autorisation expresse de Developpez
LLC. Sinon vous encourez selon la loi jusqu'à 3 ans de prison et jusqu'à 300 000 E de dommages et intérêts.
http://cpp.developpez.com/faq/cpp/
{
};

struct B2 : private Base


{
};

// Ici, A1 et A2 sont équivalents, ainsi que B1 et B2

3. L'utilisation en tant que type template est interdite pour struct.

// OK
template <class T>
struct A
{
};

// ERREUR
template <struct T>
class B
{
};

A noter que la norme permet même d'effectuer une déclaration anticipée de classe via le mot-clé struct, et inversement.
Cependant, certains compilateurs ne l'acceptent pas.

Fichier.h

struct A; // déclaration anticipée en tant que struct

A * Exemple();

Fichier.cpp

class A // déclaration en tant que class


{
};

A * Exemple()
{
return new A;
}

Classes et structures sont donc presque équivalentes, cependant on adopte souvent cette convention : on utilise struct
pour les structures type C (pas de fonction membre, d'héritage, de constructeurs, ...) et class pour tout le reste.

Comment créer 2 classes qui font référence l'une à l'autre ?


Auteurs : pipin , Laurent Gomila ,
Il arrive souvent qu'une classe A contienne un attribut (ou un argument de fonction) de type classe B et que la classe B
contienne elle aussi un attribut (ou un argument de fonction) de type classe A. Mais si l'on inclue A.h dans B.h et vice-
versa, on se retrouve avec un problème d'inclusions cycliques.
La solution à ce problème est d'utiliser des déclarations anticipées (forward declaration). Au lieu d'inclure l'en-tête
définissant une classe, on va simplement déclarer celle-ci pour indiquer au compilateur qu'elle existe. Cela marche car

- 45 -
Les sources présentées sur cette pages sont libres de droits, et vous pouvez les utiliser à votre convenance. Par contre la page de présentation de ces sources
constitue une oeuvre intellectuelle protégée par les droits d'auteurs. Copyright © 2008 Developpez LLC. Tout droits réservés Developpez LLC. Aucune
reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents et images sans l'autorisation expresse de Developpez
LLC. Sinon vous encourez selon la loi jusqu'à 3 ans de prison et jusqu'à 300 000 E de dommages et intérêts.
http://cpp.developpez.com/faq/cpp/
tant qu'on n'utilise qu'un pointeur ou une référence, le compilateur n'a pas besoin de connaître en détail le contenu de
la classe. Il a juste besoin de savoir qu'elle existe. Par contre au moment d'utiliser celle-ci (appel d'une fonction membre
par exemple) il faudra bien avoir inclus son en-tête, mais ce sera fait dans le .cpp et non plus dans le .h, ce qui élimine
le problème d'inclusion cyclique.

A.h
class B;

class A
{
B* PtrB;
};

A.cpp
#include "A.h"
#include "B.h"

// ...

B.h
#include "A.h"

class B
{
A a;
};

B.cpp
#include "B.h"

// ...

De manière générale les déclarations anticipées sont à utiliser autant que possible, à savoir dès qu'on déclare dans une
classe un pointeur ou une référence sur une autre classe. En effet, cela permet aussi de limiter les dépendances entre
les fichiers et de réduire considérablement le temps de compilation.

Que signifie objet.fonction1().fonction2() ?


Auteurs : Marshall Cline ,
C'est l'enchaînement des appels de ces fonctions membres, c'est pourquoi cela s'appelle le chaînage des fonctions.

La première chose qui est exécutée est objet.methode1(). Cela renvoie un objet ou une référence sur un objet (par ex.
methode1() peut se terminer en renvoyant *this, ou n'importe quel autre objet). Appelons cet objet retourné "ObjetB".
Ensuite, ObjetB devient l'objet auquel est appliqué methode2().

L'utilisation la plus courante du chaînage de fonctions est l'injection / extraction vers les flux standards.

cout << x << y ;

fonctionne parce que

cout << x

- 46 -
Les sources présentées sur cette pages sont libres de droits, et vous pouvez les utiliser à votre convenance. Par contre la page de présentation de ces sources
constitue une oeuvre intellectuelle protégée par les droits d'auteurs. Copyright © 2008 Developpez LLC. Tout droits réservés Developpez LLC. Aucune
reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents et images sans l'autorisation expresse de Developpez
LLC. Sinon vous encourez selon la loi jusqu'à 3 ans de prison et jusqu'à 300 000 E de dommages et intérêts.
http://cpp.developpez.com/faq/cpp/
est une fonction qui retourne cout.

Comment effectuer la copie d'objets polymorphes ?


Auteurs : Laurent Gomila , JolyLoic ,
Parfois, lorsqu'on manipule des objets polymorphes (ie. des classes dérivées via un pointeur sur leur classe de base),
on voudrait connaître leur type réel, par exemple pour les copier. Malheureusement, conceptuellement ce n'est souvent
pas la meilleure solution, et c'est parfois lourd à gérer. Le moyen le plus efficace de procéder à une copie d'objets
polymorphes est sans doute d'utiliser le design pattern Clone :

// Définition de notre classe de base


struct Base
{
virtual ~Base() {}
virtual Base* Clone() const = 0;
};

// Une classe dérivée de Base


struct Derivee1 : public Base
{
virtual Base* Clone() const
{
return new Derivee1(*this);
}
};

// Une autre classe dérivée de Base


struct Derivee2 : public Base
{
virtual Base* Clone() const
{
return new Derivee2(*this);
}
};

// Utilisation
Base* Obj1 = new Derivee1;
Base* Obj2 = new Derivee2;

Base* Copy1 = Obj1->Clone(); // Copy1 pointe sur un objet de type Derivee1


Base* Copy2 = Obj2->Clone(); // Copy2 pointe sur un objet de type Derivee2

delete Obj1;
delete Obj2;
delete Copy1;
delete Copy2;

Le code précédent est simple et fonctionne parfaitement. Une variante plus subtile existe : elle consiste à utiliser comme
type de retour de Clone() la classe dans laquelle la fonction membre est définie au lieu d'utiliser la classe de base :

struct Derivee1 : public Base


{
virtual Derivee1* Clone() const
{
return new Derivee1(*this);
}
};

- 47 -
Les sources présentées sur cette pages sont libres de droits, et vous pouvez les utiliser à votre convenance. Par contre la page de présentation de ces sources
constitue une oeuvre intellectuelle protégée par les droits d'auteurs. Copyright © 2008 Developpez LLC. Tout droits réservés Developpez LLC. Aucune
reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents et images sans l'autorisation expresse de Developpez
LLC. Sinon vous encourez selon la loi jusqu'à 3 ans de prison et jusqu'à 300 000 E de dommages et intérêts.
http://cpp.developpez.com/faq/cpp/
Ce code nécessite que les retours covariants soient supportés par le compilateur. Voir, pour plus de précisions, Qu'est-
ce qu'un type de retour covariant ?.

Qu'est-ce qu'une classe abstraite ?


Auteurs : LFE , Aurélien Regat-Barrel ,
Une classe abstraite est une classe qui possède au moins une fonction membre virtuelle pure (lire Qu'est-ce qu'une
fonction virtuelle pure ?). Cette fonction devant être supplantée, ce type de classe ne peut pas être instancié, et est donc
destiné à être dérivé pour être spécialisé. La ou les classes filles doivent supplanter l'ensemble des fonctions virtuelles
pures de leurs parents. On dit alors que les classes filles concrétisent la classe abstraite.

class Bienvenue // classe abstraite


{
public:
// le "= 0" à la fin indique que c'est
// une fonction virtuelle pure
virtual void Message() = 0;
};

class BienvenueEnFrancais : public Bienvenue


{
public:
void Message()
{
std::cout << "Bienvenue !\n";
}
};

class BienvenueEnAnglais : public Bienvenue


{
public:
void Message()
{
std::cout << "Welcome !\n";
}
};

lien : Qu'est-ce qu'une fonction virtuelle pure ?

Quelle forme canonique adopter en fonction de la sémantique de la classe ?


Auteurs : Medinoc ,
Lorsqu'une classe doit gérer de la mémoire, il est d'usage de la mettre sous une forme que l'on appelle forme canonique.
Mais il existe plusieurs variantes de la forme canonique, selon que la classe soit copiable, modifiable, swappable...

Classe non-copiable, non-assignable, mais swappable :

• constructeur ;
• constructeur par copie : privé, non-défini ;
• operator= : privé, non-défini ;
• swap() + spécialisation de std::swap ;
• destructeur.

Classe copiable, mais immuable (non-assignable, non-swappable)

- 48 -
Les sources présentées sur cette pages sont libres de droits, et vous pouvez les utiliser à votre convenance. Par contre la page de présentation de ces sources
constitue une oeuvre intellectuelle protégée par les droits d'auteurs. Copyright © 2008 Developpez LLC. Tout droits réservés Developpez LLC. Aucune
reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents et images sans l'autorisation expresse de Developpez
LLC. Sinon vous encourez selon la loi jusqu'à 3 ans de prison et jusqu'à 300 000 E de dommages et intérêts.
http://cpp.developpez.com/faq/cpp/
• constructeur ;
• constructeur par copie ;
• operator= : privé, non-défini ;
• destructeur.

Classe pleinement copiable, assignable, swappable :

• constructeur ;
• constructeur par copie ;
• operator= : utilise idiome copy-and-swap ;
• swap() + spécialisation de std::swap ;
• destructeur.

De plus, le destructeur n'a pas forcément à être virtuel, cela dépend des cas. Si l'héritage est public et que la classe de
base va servir en tant qu'interface alors le destructeur doit être virtuel. Par contre si l'héritage est protégé/privé et qu'il
sert à réutiliser du code sans notion d'interface et de polymorphisme, alors le destructeur de la classe de base doit être
protégé et non-virtuel.
Enfin pour les constructeurs, il est souvent préconisé de les faire protégés pour toutes les classes non-terminales, seules
les classes terminales doivent être instanciables.

Qu'est-ce que la forme canonique orthodoxe de Coplien ?


Auteurs : 3DArchi ,
La forme canonique orthodoxe de Coplien a été définie par Coplien pour les classes à Quand est-ce qu'une classe
a une sémantique de valeur ? . Elle précise que si une classe doit définir une des quatre méthodes suivantes alors elle
doit les définir toutes :

• un constructeur par défaut : CMyClass() ;


• un constructeur par copie : CMyClass(CMyClass const &) ;
• l'opérateur d'affectation : CMyClass&operator=(CMyClass const &) ;
• le destructeur : ~CMyClass().

Quand dois-je définir un constructeur par défaut ?


Auteurs : 3DArchi ,
Le constructeur par défaut est le constructeur qui ne prend aucun argument ou dont les arguments ont une valeur
par défaut :

class CMyClass
{
public:
CMyClass();// constructeur par défaut
};

ou

class CMyClass
{
public:
CMyClass(T1 param1=def_value, T2 param2=T2());// constructeur par défaut
};

- 49 -
Les sources présentées sur cette pages sont libres de droits, et vous pouvez les utiliser à votre convenance. Par contre la page de présentation de ces sources
constitue une oeuvre intellectuelle protégée par les droits d'auteurs. Copyright © 2008 Developpez LLC. Tout droits réservés Developpez LLC. Aucune
reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents et images sans l'autorisation expresse de Developpez
LLC. Sinon vous encourez selon la loi jusqu'à 3 ans de prison et jusqu'à 300 000 E de dommages et intérêts.
http://cpp.developpez.com/faq/cpp/
Si la classe n'en définit pas, alors le compilateur en propose un implicitement. Celui-ci appelle le constructeur par
défaut des classes de base et le constructeur par défaut des membres. Pour les types POD, les valeurs sont laissées non
initialisées.
Une exception : si la classe définit un autre constructeur (constructeur avec paramètres sans valeur par défaut ou
constructeur par copie), alors le compilateur ne génère pas de constructeur par défaut. Dans ce cas, si cela a un sens
pour la classe, un constructeur par défaut doit alors être explicitement défini.

Quand dois-je définir un constructeur par copie ?


Auteurs : 3DArchi ,
Le constructeur par copie se base sur un autre objet du même type pour construire l'objet en cours :

class CMyClass
{
public:
CMyClass(CMyClass const&);// constructeur par copie
};

Si la classe ne définit pas de constructeur par copie, le compilateur génère un constructeur de copie implicitement.
Celui-ci appelle le constructeur par copie des classes parents et le constructeur par copie des membres. Ceci peut être
désactivé Qu'est-ce qu'une classe copiable ?.
Si on souhaite donner une sémantique de copie et que le constructeur par copie ne convient pas (par exemple, parce
qu'une ressource est gérée), alors il faut définir un constructeur par copie.
Dans le cadre d'une utilisation polymorphe, on peut vouloir définir un constructeur par copie pour permettre le clonage.
Celui-ci est alors protégé car la copie n'a de sens que dans ce cadre. La fonction Clone est, elle, publique.

Quand dois-je définir l'opérateur d'affectation ?


Auteurs : 3DArchi ,
L'opérateur d'affectation permet de copier la valeur d'un objet dans un autre :

class CMyClass
{
public:
CMyClass& operator=(CMyClass const&);// opérateur d'affectation
};

Si la classe ne définit pas d'opérateur d'affectation, le compilateur en génère un implicitement. Celui-ci appelle
l'opérateur d'affectation des membres.
A noter que si la classe suit Quand est-ce qu'une classe a une sémantique de d'entité ?, la définition d'un opérateur
d'affectation a de grandes chances d'être une erreur de conception. Il faut alors le déclarer privé sans le définir de façon
à interdire son utilisation.

lien : Quand est-ce qu'une classe a une sémantique de d'entité ?


lien : Qu'est-ce qu'une classe copiable ?

- 50 -
Les sources présentées sur cette pages sont libres de droits, et vous pouvez les utiliser à votre convenance. Par contre la page de présentation de ces sources
constitue une oeuvre intellectuelle protégée par les droits d'auteurs. Copyright © 2008 Developpez LLC. Tout droits réservés Developpez LLC. Aucune
reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents et images sans l'autorisation expresse de Developpez
LLC. Sinon vous encourez selon la loi jusqu'à 3 ans de prison et jusqu'à 300 000 E de dommages et intérêts.
http://cpp.developpez.com/faq/cpp/
lien : Comment écrire un opérateur d'affectation correct ?

Quand dois-je définir un destructeur ?


Auteurs : 3DArchi ,
Le destructeur est appelé pour libérer les ressources acquises par un objet lorsque l'espace occupé par celui-ci doit
être libéré :

class CMyClass
{
public:
~CMyClass();// destructeur
};

Si la classe ne contient pas de destructeur par défaut, alors le compilateur en génère un implicitement. Celui-ci va
appeler les destructeurs des différents membres puis celui des classes parents.
Si on doit utiliser la classe comme base dans un héritage pour une utilisation polymorphe (utilisation via une référence
ou un pointeur sur la base), alors la classe de base doit définir un destructeur virtuel.

lien : Pourquoi le destructeur d'une classe de base doit être public et virtuel ou protégé et non virtuel ?

Que conclure de la forme canonique orthodoxe de Coplien ?


Auteurs : 3DArchi ,
• Si une des quatres méthodes ci-dessus a été définie de façon non triviale, alors il est fortement probable que les
trois autres doivent être définies.
• Si une classe doit servir de base pour une utilisation polymorphe, alors le destructeur doit être déclaré comme
virtuel. A noter qu'il est fort probable que Quand est-ce qu'une classe a une sémantique de d'entité ?.
• La sémantique d'une classe va imposer la politique de définition des méthodes ( Quelle forme canonique
adopter en fonction de la sémantique de la classe ?).
• Si la définition implicite convient, compte tenu des éléments ci-dessus, alors il n'est pas nécessaire de définir
explicitement ces méthodes. Et les définir peut avoir des impacts négatifs : il existe des outils d'analyse de code
(et peut-être des futures évolutions du langage) qui vérifient justement que l'on a respecté ces règles. Définir une
fonction ne faisant rien peut alors mettre en échec ces outils.

- 51 -
Les sources présentées sur cette pages sont libres de droits, et vous pouvez les utiliser à votre convenance. Par contre la page de présentation de ces sources
constitue une oeuvre intellectuelle protégée par les droits d'auteurs. Copyright © 2008 Developpez LLC. Tout droits réservés Developpez LLC. Aucune
reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents et images sans l'autorisation expresse de Developpez
LLC. Sinon vous encourez selon la loi jusqu'à 3 ans de prison et jusqu'à 300 000 E de dommages et intérêts.
http://cpp.developpez.com/faq/cpp/
Sommaire > Les classes en C++ > Les constructeurs
Qu'est-ce qu'un constructeur ?
Auteurs : LFE , Gilles Louïse ,
Un constructeur est ce qui construit un objet, initialise éventuellement les membres de la classe, alloue de la mémoire,
etc... On peut le comparer à une fonction d'initialisation de la classe.
On le reconnaît au fait qu'il porte le même nom que la classe elle-même. Sa déclaration se fait de la façon suivante :

class MaClasse
{
public:
MaClasse();
};

lien : Qu'est-ce qu'un destructeur ?

Qu'est-ce qu'un constructeur par défaut ?


Auteurs : LFE ,
Un constructeur par défaut est un constructeur qui peut être appelé sans paramètre. A noter qu'il peut s'agir d'un
constructeur sans paramètres, ou d'un constructeur dont les paramètres ont des valeurs par défaut.

class MaClasse1
{
public:
MaClasse1(); // ceci est le constructeur par défaut
};

class MaClasse2
{
public:
MaClasse2( int i = 1 ); // ceci est le constructeur par défaut
};

class MaClasse3
{
public:
MaClasse3( int i ); // ceci n'est pas le constructeur par défaut
};

Qu'est-ce qu'un constructeur de copie ?


Auteurs : LFE ,
Un constructeur de copie, comme son nom l'indique, sert à copier l'objet, à partir d'un autre objet. Ce constructeur
est utilisé pour

• la duplication d'un objet lors de la création d'un nouvel objet


• lors du passage en paramètre à une fonction, un template, ... pour générer l'objet utilisé en interne par cette
fonction
• au retour d'une fonction renvoyant un objet, dans le but de mettre cet objet à disposition sur la pile pour l'appelant

class MaClasse
{
public:
MaClasse( const MaClasse & ); // ceci est le constructeur de copie

- 52 -
Les sources présentées sur cette pages sont libres de droits, et vous pouvez les utiliser à votre convenance. Par contre la page de présentation de ces sources
constitue une oeuvre intellectuelle protégée par les droits d'auteurs. Copyright © 2008 Developpez LLC. Tout droits réservés Developpez LLC. Aucune
reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents et images sans l'autorisation expresse de Developpez
LLC. Sinon vous encourez selon la loi jusqu'à 3 ans de prison et jusqu'à 300 000 E de dommages et intérêts.
http://cpp.developpez.com/faq/cpp/
};

Quelles sont les différences fondamentales entre le constructeur d'une classe et sa méthode Init() ?
Auteurs : JolyLoic , Luc Hermitte ,
Le constructeur est une fonction spéciale appelée automatiquement à certains endroits, une fonction init n'est aucune
sémantique spéciale, l'utilisateur doit l'appeler manuellement (risque d'oubli). En revanche, un constructeur ne peut
pas être polymorphe, ni appeler une fonction de sa classe de manière polymorphe. L'appel de la fonction init est donc
une nécessité dans les cas extrêment rares où l'on a besoin de "postconstruction polymorphe".

Cela revient à dire qu'il faut utiliser uniquement un constructeur. Dans certains cas, on n'a pas assez d'infos à la
construction de l'objet pour tout initialiser, d'où une fonction init (toutefois, cela reste assez rare).

Pour signaler une erreur dans le constructeur, il peut lancer une exception, et dans ce cas, l'objet n'est pas construit
du tout.

Si on suit le code exemple suivant :

T * t = new T();
t->init(); // peut lever une exception, parce que la clé de connection n'est pas valide...
return t;

Si init lève une exception, la mémoire (ou autres ressources) allouée pour t n'est jamais libérée. Pour résoudre ce
problème, il existe deux solutions :

• Fusionner le résidu init() dans le constructeur principal des classes qui sont refactorisées.
• Utiliser les "auto_ptr<>".

Y a-t-il une différence quelconque entre MaClasse x; et MaClasse x(); ?


Auteurs : Marshall Cline , Aurélien Regat-Barrel ,
Une grande différence !
Supposez que MaClasse soit le nom d'une classe. Dans l'exemple suivant :

MaClasse x;
MaClasse y();

x est bien un objet de type MaClasse, alors que y est la déclaration d'une fonction qui retourne un objet de type MaClasse
! Et ceci même si l'on se trouve dans le corps d'une fonction :

#include "MaClasse.h"

int main()
{
MaClasse x; // ok
MaClasse y(); // déclaration de la fonction y !

- 53 -
Les sources présentées sur cette pages sont libres de droits, et vous pouvez les utiliser à votre convenance. Par contre la page de présentation de ces sources
constitue une oeuvre intellectuelle protégée par les droits d'auteurs. Copyright © 2008 Developpez LLC. Tout droits réservés Developpez LLC. Aucune
reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents et images sans l'autorisation expresse de Developpez
LLC. Sinon vous encourez selon la loi jusqu'à 3 ans de prison et jusqu'à 300 000 E de dommages et intérêts.
http://cpp.developpez.com/faq/cpp/
}

Un constructeur d'une classe peut-il appeler un autre


constructeur de la même classe pour initialiser 'this' ?
Auteurs : Marshall Cline ,
Absolument pas.

Prenons un exemple. Supposons que l'on veuille que le constructeur Foo::Foo(char) appelle un autre constructeur de
la même classe, Foo::Foo( char, int ) de façon que Foo::Foo( char, int ) initialise l'objet this. Malheureusement, ce n'est
pas possible en C++.

Pourtant, certaines personnes le font, mais cela ne fait pas ce qu'elles désirent. Par exemple,

line Foo( x, 0 );

n'appelle pas

Foo::Foo(char,int)

de l'objet désigné par this. Par contre,

Foo::Foo( char, int )

est appelé pour initialiser un objet local (et pas celui désigné par 'this') et qui est ensuite détruit immédiatement à la
fin de l'appel

class Foo
{
public:
Foo( char x );
Foo( char x, int y );
};

Foo::Foo( char x )
{
Foo( x, 0 ); // Cette ligne n'initialise pas l'objet !
}

Il est cependant possible de combiner deux constructeurs grâce aux paramètres par défaut

class Foo
{
public:
Foo( char x, int y = 0 ); // cette ligne combine les 2 constructeurs
};

Si cela ne fonctionne pas, par exemple s'il n'y a pas une valeur du paramètre par défaut qui permet de combiner les
deux constructeurs, il est possible de partager leur code commun via une fonction membre privée d'initialisation.

class Foo
{
public:
Foo( char x );

- 54 -
Les sources présentées sur cette pages sont libres de droits, et vous pouvez les utiliser à votre convenance. Par contre la page de présentation de ces sources
constitue une oeuvre intellectuelle protégée par les droits d'auteurs. Copyright © 2008 Developpez LLC. Tout droits réservés Developpez LLC. Aucune
reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents et images sans l'autorisation expresse de Developpez
LLC. Sinon vous encourez selon la loi jusqu'à 3 ans de prison et jusqu'à 300 000 E de dommages et intérêts.
http://cpp.developpez.com/faq/cpp/
Foo( char x, int y );

private:
void init( char x, int y );
};

Foo::Foo( char x )
{
init( x, int( x ) + 7 );
}

Foo::Foo( char x, int y )


{
init( x, y );
}

void Foo::init( char x, int y )


{
}

Est-ce que le constructeur par défaut pour Fred est toujours Fred::Fred() ?
Auteurs : Marshall Cline ,
Non. Un "constructeur par défaut" est un constructeur qui peut s'appeler sans arguments. Ainsi un constructeur qui
ne prend aucun argument est certainement un constructeur par défaut :

class Fred
{
public:
Fred(); // constructeur par défaut : peut s'appeler sans args
};

Toutefois il est possible (et probable) qu'un constructeur par défaut prenne des arguments, s'ils sont spécifiés par
défaut :

class Fred
{
public:
Fred( int i = 3, int j = 5 ); // constructeur par défaut : peut s'appeler sans args
};

Quel constructeur est appelé quand je crée un tableau d'objets Fred ?


Auteurs : Marshall Cline ,
Le constructeur par défaut.

Il n'y a aucun moyen de demander au compilateur d'appeler un constructeur différent. Si votre class Fred n'a pas de
constructeur par défaut, une tentative de créer un tableau de Fred, se soldera par une erreur de compilation.

class Fred
{
public:
Fred( int i, int j );
// Suppose qu'il n'y a aucun constructeur par défaut
};

int main()

- 55 -
Les sources présentées sur cette pages sont libres de droits, et vous pouvez les utiliser à votre convenance. Par contre la page de présentation de ces sources
constitue une oeuvre intellectuelle protégée par les droits d'auteurs. Copyright © 2008 Developpez LLC. Tout droits réservés Developpez LLC. Aucune
reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents et images sans l'autorisation expresse de Developpez
LLC. Sinon vous encourez selon la loi jusqu'à 3 ans de prison et jusqu'à 300 000 E de dommages et intérêts.
http://cpp.developpez.com/faq/cpp/
{
Fred a[ 10 ]; // ERREUR : Fred n'a pas de constructeur par défaut
Fred * p = new Fred[ 10 ]; // ERREUR : Fred n'a pas de constructeur par défaut
}

Cependant si vous créez un vector <Fred> plutôt qu'un tableau standard de Fred (ce que vous devriez faire de toute
façon puisque utiliser les tableaux est mauvais), vous n'avez plus besoin d'avoir un constructeur par défaut dans class
Fred, puisque vous passez un objet Fred au vector pour initialiser les éléments :

#include <vector>

int main()
{
std::vector <Fred> a(10, Fred(5,7));
// 10 objets Fred dans le vecteur a seront initialisés avec Fred(5,7)
}

Même si la plupart du temps, il vaut mieux utiliser un vecteur plutôt qu'un tableau, il y a certaines circonstances ou le
tableau est la meilleure chose à utiliser. Dans ce cas, il existe "l'initialisation explicite de tableau" :

class Fred
{
public:
Fred(int i, int j);
// suppose qu'il n'y a aucun constructeur par défaut
};

int main()
{
Fred a[10] = {
Fred(5,7), Fred(5,7), Fred(5,7), Fred(5,7), Fred(5,7),
Fred(5,7), Fred(5,7), Fred(5,7), Fred(5,7), Fred(5,7)
};
// 10 objets Fred dans le tableau a seront initialisés avec Fred(5,7)
}

Il n'est bien sur pas obligatoire de mettre un Fred(5,7) pour chaque entrée, vous pouvez en spécifier n'importe quel
nombre. L'important est que cette syntaxe est possible mais pas aussi belle que la syntaxe du vecteur.

Souvenez-vous : les tableaux sont mauvais, à moins qu'il y ait une raison valable d'en utiliser un, utilisez plutôt un
vecteur.

Mes constructeurs doivent-ils utiliser les listes d'initialisation ou l'affectation ?


Auteurs : Marshall Cline ,
Les listes d'initialisation. En fait, les constructeurs devraient initialiser tous les objets membre dans la liste
d'initialisation.

Par exemple, ce constructeur initialise l'objet membre x_ en utilisant une liste d'initialisation :

Fred::Fred() : x_(n'importe quoi)


{
}

Le bénéfice de faire cela est une performance accrue. Par exemple, si l'expression n'importe quoi est identique à la
variable membre x_, le résultat de l'expression n'importe quoi sera intégré directement dans x_ (le compilateur ne fait

- 56 -
Les sources présentées sur cette pages sont libres de droits, et vous pouvez les utiliser à votre convenance. Par contre la page de présentation de ces sources
constitue une oeuvre intellectuelle protégée par les droits d'auteurs. Copyright © 2008 Developpez LLC. Tout droits réservés Developpez LLC. Aucune
reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents et images sans l'autorisation expresse de Developpez
LLC. Sinon vous encourez selon la loi jusqu'à 3 ans de prison et jusqu'à 300 000 E de dommages et intérêts.
http://cpp.developpez.com/faq/cpp/
pas une copie séparée de l'objet). Même si les types ne sont pas identiques, le compilateur est habituellement capable
de faire un meilleur travail à partir des listes d'initialisation qu'à partir des affectations.

L'autre façon (inefficace) de faire un constructeur est d'utiliser les affections

Fred::Fred() : x_()
{
x_ = n'importe quoi;
}

Dans ce cas, l'expression n'importe quoi provoque la création d'un objet temporaire, et cet objet temporaire est passé à
l'opérateur d'assignation de l'objet. Cet objet temporaire est ensuite détruit, ce qui est inefficace.

Comme si ce n'était pas suffisant, il y a une autre source d'inefficacité lors de l'utilisation de l'affectation dans le
constructeur, l'objet membre sera construit entièrement par le constructeur par défaut, ce qui peut par exemple allouer
de la mémoire, ouvrir des fichiers, par défaut. Tout ce travail pourrait être inutile si l'expression n'importe quoi faisait
fermer ces fichiers, désallouer cette mémoire (par exemple si la mémoire allouée par le constructeur par défaut n'était
pas suffisante, ou que ce ne soit pas le bon fichier.)

Conclusion : toutes choses restant égales par ailleurs, votre code tournera plus vite si vous utilisez les listes d'initialisation
plutôt que l'assignation.

Note : Il n'y a pas de différence de performance si le type de x_ est de base, comme int, ou char *, ou float. Mais même
dans ce cas, ma préférence personnelle est d'initialiser ses données dans la liste d'initialisation plutôt que par affectation
par soucis de cohérence. Un autre argument lié à la symétrie en faveur de l'utilisation des listes d'initialisation même
pour les types de base : la valeur des membres de données constantes non statiques ne peut pas être initialisée dans le
constructeur, donc pour conserver la symétrie, je recommande d'initialiser tout dans la liste d'initialisation.

Puis-je utiliser le pointeur this dans un constructeur ?


Auteurs : Marshall Cline , Aurélien Regat-Barrel ,
Certains pensent qu'on ne devrait pas utiliser le pointeur this dans un constructeur parce que l'objet n'est pas
complètement formé. Pourtant il est possible d'utiliser le pointeur this dans le corps du constructeur et même dans la
liste d'initialisation si l'on est prudent.

Voilà quelque chose qui fonctionne toujours : le corps du constructeur (ou d'une fonction appelée depuis le constructeur)
peut accéder aux données membres déclarées dans une classe de base et/ou à celles déclarées dans la classe elle-même en
toute sécurité. Ceci parce que le langage nous assure que toutes ces données membres ont été complètement construites
au moment où le corps du constructeur est exécuté.

Voilà quelque chose qui ne fonctionne jamais : le corps du constructeur (ou d'une fonction qu'il appelle) ne peut pas
descendre dans une classe dérivée en appelant une fonction membre virtual qui est redéfinie dans une classe dérivée.
Si votre but était d'exécuter le code de la fonction virtuelle, cela ne fonctionnera pas. Notez que vous n'obtiendrez pas
la version de la classe dérivée indépendamment de la manière d'appeler la fonction membre virtuelle : en utilisant
explicitement this (this->method()), ou implicitement sans utiliser le pointeur this (method()), ou même en appelant
quelque autre fonction qui appelle la fonction membre virtuelle en question a partir du pointeur this. Ceci parce que
l'appelant est en train de construire un objet d'un type dérivé, et donc que la construction de ce dernier n'est pas encore
terminée. Donc votre objet qui fait office de classe de base n'appartient pas encore à cette classe dérivée.

Voilà quelque chose qui fonctionne parfois : si vous passez n'importe quelle donnée membre de l'objet au constructeur
d'initialisation d'une autre donnée membre, vous devez vous assurer que l'autre donnée membre a déjà été initialisée.

- 57 -
Les sources présentées sur cette pages sont libres de droits, et vous pouvez les utiliser à votre convenance. Par contre la page de présentation de ces sources
constitue une oeuvre intellectuelle protégée par les droits d'auteurs. Copyright © 2008 Developpez LLC. Tout droits réservés Developpez LLC. Aucune
reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents et images sans l'autorisation expresse de Developpez
LLC. Sinon vous encourez selon la loi jusqu'à 3 ans de prison et jusqu'à 300 000 E de dommages et intérêts.
http://cpp.developpez.com/faq/cpp/
La bonne nouvelle est que vous pouvez déterminer si l'autre donnée membre a (ou non) déjà été initialisée en utilisant
des règles du langage indépendantes du compilateur que vous utilisez. La mauvaise nouvelle est qu'il vous faut connaître
ces règles. Les sous-objets de la classe de base sont initialisés en premier (vérifiez l'ordre si vous avez de l'héritage
multiple et/ou de l'héritage virtuel !), ensuite viennent les données membres définies dans la classe qui est initialisée
dans l'ordre dans lequel elles apparaissent dans la déclaration de la classe. Si vous ne connaissez pas ces règles alors ne
passez aucune donnée membre depuis l'objet this (cela ne dépend pas de l'utilisation explicite de this) vers l'initialiseur
d'une autre donnée membre ! Si vous connaissez ces règles, soyez tout de même très vigilants.

Puis-je appeler des fonctions virtuelles dans le constructeur (ou le destructeur) ?


Auteurs : JolyLoic , Laurent Gomila ,
Oui, c'est possible, mais attention, ça ne fait pas toujours ce qu'on pense.
La première approche consiste à comprendre que lors de l'appel du constructeur d'une classe de base, la classe dérivée
n'a pas encore été construite. Donc, c'est la méthode spécialisée à ce niveau qui est appelée.
Voyons en détail :
La règle est que le type dynamique d'une variable en cours de construction est celui du constructeur qui est en train
d'être exécuté. Pour bien comprendre ce qui se passe, il faut donc revenir sur la différence entre le type statique d'une
variable, et son type dynamique.

Prenons par exemple trois classes, C qui dérive de B qui dérive de A. Par exemple, dans :

A* a = new B();

La variable a possède comme type statique (son type déclaré dans le programme) A*. Par contre, son type dynamique
est B*. Une fonction virtuelle est simplement une fonction dont on va chercher le code en utilisant le type dynamique
de la variable, au lieu de son type statique, comme une fonction classique.

Maintenant, quand on crée un objet de type C, les choses se passent ainsi :

• On alloue assez de mémoire pour un objet de la taille de C.


• On initialise la sous partie correspondant à A de l'objet.
• On appelle le corps du constructeur de A. Pendant cet appel, l'objet crée a pour type dynamique A.
• On initialise la sous partie correspondant à B de l'objet.
• On appelle le corps du constructeur de B. Pendant cet appel, l'objet crée a pour type dynamique B.
• On initialise la sous partie correspondant à C de l'objet.
• On appelle le corps du constructeur de C. Pendant cet appel, l'objet crée a pour type dynamique C.

Donc, dans le corps du constructeur de la classe B, un appel d'une fonction virtuelle appellera la version de la fonction
définie dans la classe B (ou à défaut celle définie dans A si la fonction n'a pas été définie dans B), et non pas celle définie
dans la classe C.

D'ailleurs, si la fonction est virtuelle pure dans B, ça causera quelques problèmes, puisqu'on tentera alors d'appeler
une fonction qui n'existe pas. En général, le programme va planter, si on a de la chance, il affichera une message du
style "Pure function called".

La problèmatique est exactement la même pour les destructeurs, mais dans l'ordre inverse.

- 58 -
Les sources présentées sur cette pages sont libres de droits, et vous pouvez les utiliser à votre convenance. Par contre la page de présentation de ces sources
constitue une oeuvre intellectuelle protégée par les droits d'auteurs. Copyright © 2008 Developpez LLC. Tout droits réservés Developpez LLC. Aucune
reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents et images sans l'autorisation expresse de Developpez
LLC. Sinon vous encourez selon la loi jusqu'à 3 ans de prison et jusqu'à 300 000 E de dommages et intérêts.
http://cpp.developpez.com/faq/cpp/
Pourquoi cette règle ? Une fonction définie dans C a accès aux données membre de C. Or, on a vu que au moment où
on exécute l'appel au corps du constructeur de B, ces dernières ne sont pas encore créées. On a donc préféré jouer la
sécurité.

lien : Dans quel ordre sont construits les différents composants d'une classe ?

Qu'est-ce que l'idiome du constructeur nommé (Named Constructor) ?


Auteurs : Marshall Cline ,
Une technique qui fournit des exécutions plus intuitives et/ou plus sûres de construction pour des utilisateurs de votre
classe.

Le problème est que les constructeurs ont toujours le même nom que la classe. Par conséquent la seule façon de
les différencier se fait via la liste des paramètres. Mais s'il y a beaucoup de constructeurs, les différences entre les
constructeurs deviennent quelque peu subtiles et sujettes à erreur.

Avec l'idiome du constructeur nommé, vous déclarez les constructeurs de toute la classe dans l'une des sections private ou
protected. Vous fournissez des fonctions déclarées statiques dans la section public: qui renvoient un objet. Ces fonctions
statiques sont connues comme "constructeurs nommés". En général il y a une telle fonction statique pour chaque
manière différente de construire l'objet.

Par exemple, supposez que nous construisions une classe Point qui représente une position sur le plan X/Y. Il s'avère
qu'il y a deux façons d'indiquer une coordonnée dans un espace bidimensionnel : les coordonnées cartésiennes (X+Y)
et les coordonnées polaires (Distance+Angle). (Pour cet exemple, l'essentiel est de retenir qu'il y a plusieurs façons de
créer un point). Malheureusement les paramètres pour ces deux systèmes de coordonnées sont identiques : deux réels.
Ceci créerait une ambiguïté dans les constructeurs surchargés :

class Point
{
public:
Point(float x, float y); // Coordonnées rectangulaires
Point(float r, float a); // Coordonnées polaires (distance et angle)
// ERREUR : Surcharge ambiguë: Point::Point(float, float)
};

int main()
{
Point p = Point(5.7, 1.2); // Ambigu : De quel système de coordonnées parle-t-on ?
}

Une manière de résoudre cette ambiguïté est d'utiliser l'idiome du constructeur nommé :

#include <cmath> // Pour avoir sin() et cos()

class Point
{
public:
static Point rectangular(float x, float y); // Coords rectangulaires
static Point polar(float radius, float angle); // Coords polaires
// Ces fonctions static sont les "constructeurs nommés"
// ...
private:
Point(float x, float y); // coordonnées rectangulaires
float x_, y_;
};

inline Point::Point(float x, float y)

- 59 -
Les sources présentées sur cette pages sont libres de droits, et vous pouvez les utiliser à votre convenance. Par contre la page de présentation de ces sources
constitue une oeuvre intellectuelle protégée par les droits d'auteurs. Copyright © 2008 Developpez LLC. Tout droits réservés Developpez LLC. Aucune
reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents et images sans l'autorisation expresse de Developpez
LLC. Sinon vous encourez selon la loi jusqu'à 3 ans de prison et jusqu'à 300 000 E de dommages et intérêts.
http://cpp.developpez.com/faq/cpp/
: x_(x), y_(y)
{
}

inline Point Point::rectangular(float x, float y)


{
return Point(x, y);
}

inline Point Point::polar(float radius, float angle)


{
return Point( radius * cos(angle), radius * sin(angle) );
}

Maintenant les utilisateurs du point ont une syntaxe claire et non ambiguë pour créer des points dans l'un ou l'autre
système de coordonnées :

int main()
{
Point p1 = Point::rectangular(5.7, 1.2); // Evidemment rectangulaire
Point p2 = Point::polar(5.7, 1.2); // Evidemment polaire
}

Faites attention à déclarer vos constructeurs dans la section protected : si vous vous attendez à ce que Point ait des
classes dérivées.

L'idiome du constructeur nommé peut aussi être utilisé pour vous assurer que les objets d'une classe sont toujours créés
avec l'opérateur new.

Que faire en cas d'échec du constructeur ?


Auteurs : Marshall Cline ,
Dans ce cas, le mieux est de lancer une exception. Lire à ce sujet Peut-on lever des exceptions dans les constructeurs ?.

Qu'est-ce que "l'idiome des paramètres nommés" ?


Auteurs : Marshall Cline ,
C'est une méthode très utile pour exploiter le chaînage des fonctions.

Le principal problème solutionné par l'idiome des paramètres nommés est que le C++ ne supporte que les "paramètres
par position". Par exemple, une fonction appelante ne peut pas dire "Voici la valeur pour le paramètre xyz, et voici
autre chose pour le paramètre pqr". Tout ce que vous pouvez faire en C++ (ou en C ou en Java) est "voici le premier
paramètre, le second, etc...." L'alternative, appelée "paramètres nommés" et implémentée en Ada, est particulièrement
utile si une fonction prend un nombre important de paramètres dont la plupart supportent des valeurs par défaut.

Au cours des années, de nombreuses personnes ont mis au point des astuces pour contourner ce manque de paramètres
nommés en C et en C++. Une d'entre elles implique d'intégrer la valeur du paramètre dans une chaîne et de découper
cette chaîne à l'exécution. C'est ce qui se passe pour le second paramètre de la fonction fopen(), par exemple. Une autre
astuce est de combiner tous les paramètres booléens dans un champ de bits, et la fonction fait un ou logique pour obtenir
la valeur du paramètre désiré. C'est ce qui se passe avec le second paramètre de la fonction open(). Cette approche
fonctionne, mais la technique exposée ci-dessous produit un code plus simple à écrire et à lire, plus élégant.

- 60 -
Les sources présentées sur cette pages sont libres de droits, et vous pouvez les utiliser à votre convenance. Par contre la page de présentation de ces sources
constitue une oeuvre intellectuelle protégée par les droits d'auteurs. Copyright © 2008 Developpez LLC. Tout droits réservés Developpez LLC. Aucune
reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents et images sans l'autorisation expresse de Developpez
LLC. Sinon vous encourez selon la loi jusqu'à 3 ans de prison et jusqu'à 300 000 E de dommages et intérêts.
http://cpp.developpez.com/faq/cpp/
L'idée est de transformer les paramètres de la fonction en des fonctions membres d'une nouvelle classe, dans laquelle
toutes ces fonctions renvoient *this par référence. Vous renommez ensuite simplement la fonction principale en une
fonction sans paramètre de cette classe.

Prenons un exemple pour rendre les choses plus claires.

L'exemple sera pour le concept 'ouvrir un fichier'. Ce concept a besoin logiquement d'un paramètre pour le nom du
fichier, et éventuellement d'autres paramètres pour spécifier si le fichier doit être ouvert en lecture, en écriture, ou
encore en lecture/écriture ; si le fichier doit être créé s'il n'existe pas déjà ; s'il doit être ouvert en ajout (append) ou en
sur-écriture (overwrite) ; la taille des blocs à lire ou écrire ; si les entrées-sorties sont bufferisées ou non ; la taille du
buffer ; si le mode est partagé ou exclusif ; et probablement d'autres encore. Si nous implémentions ce concept via une
fonction classique avec des paramètres par position, l'appel de cette fonction serait assez désagréable à lire. Il y aurait
au moins 8 paramètres, ce qui est source d'erreur. Utilisons donc à la place l'idiome des paramètres nommés.

Avant de nous lancer dans l'implémentation, voici à quoi devrait ressembler le code appelant, en supposant que l'on
accepte toutes les valeurs par défaut des paramètres.

File f = OpenFile("foo.txt");

C'est le cas le plus simple. Maintenant, voyons ce que cela donne si nous voulons changer certains des paramètres.

File f = OpenFile("foo.txt").
readonly().
createIfNotExist().
appendWhenWriting().
blockSize(1024).
unbuffered().
exclusiveAccess();

Il est à noter comment les "paramètres", pour autant que l'on puisse encore les appeler comme cela, peuvent être
dans un ordre aléatoire (ils ne sont pas positionnés) et qu'ils sont nommés. Le programmeur n'a donc pas besoin de se
souvenir de l'ordre des paramètres ; de plus les noms sont évidents.

Voici comment l'implémenter. Nous créons d'abord une nouvelle classe OpenFile qui contient toutes les valeurs
des paramètres en tant que membres de données privées. Ensuite, toutes les fonctions membres (readonly(),
blockSize(unsigned), etc ....) renvoient *this (c'est-à-dire qu'elles renvoient une référence sur l'objet OpenFile, autorisant
ainsi le chaînage des appels des fonctions). Pour terminer, nous spécifions les paramètres requis (le nom du fichier dans
le cas présent) dans un paramètre normal (c'est-à-dire par position) passé au constructeur de OpenFile/

class File;

class OpenFile
{
public:
OpenFile(const string& filename); // initialise chaque donnée membre à sa valeur par défaut
OpenFile& readonly(); // change readonly_ à true
OpenFile& createIfNotExist();
OpenFile& blockSize( unsigned nbytes );
// ...
private:
friend File;
bool readonly_; // false par défaut [exemple]
unsigned blockSize_; // 4096 par défaut [exemple]
// ...
};

La seule autre chose à faire est que le constructeur de File accepte un objet OpenFile.

- 61 -
Les sources présentées sur cette pages sont libres de droits, et vous pouvez les utiliser à votre convenance. Par contre la page de présentation de ces sources
constitue une oeuvre intellectuelle protégée par les droits d'auteurs. Copyright © 2008 Developpez LLC. Tout droits réservés Developpez LLC. Aucune
reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents et images sans l'autorisation expresse de Developpez
LLC. Sinon vous encourez selon la loi jusqu'à 3 ans de prison et jusqu'à 300 000 E de dommages et intérêts.
http://cpp.developpez.com/faq/cpp/
class File
{
public:
File(const OpenFile& params);
// s'initialise à partir des paramètres de l'objet OpenFile reçu
// ...
};

A noter que OpenFile déclare File en tant que classe amie. De cette façon, OpenFile n'a pas besoin de définir une série
d'accesseurs.

Etant donné que chaque fonction membre de la chaîne renvoie une référence, il n'y a pas de copie d'objets et le
tout est très efficace. De plus, si les différentes fonctions membres sont déclarées inline, le code généré ressemblera
probablement à du code C qui positionne certains membres d'une structure. Bien sur, si les fonctions membres ne sont
pas déclarées inline, il risque d'y avoir une légère augmentation de la taille du code et une légère perte de performance
(mais uniquement si la construction se passe dans certaines circonstances, comme nous l'avons vu précédemment). Cela
peut donc, dans ce cas être un compromis qui rendra le code plus robuste.

Dans quel ordre sont construits les différents composants d'une classe ?
Auteurs : 3DArchi ,
Les constructeurs sont appelés dans l'ordre suivant :

1 le constructeur des classes de base héritées virtuellement en profondeur croissante et de gauche à droite ;
2 le constructeur des classes de base héritées non virtuellement en profondeur croissante et de gauche à droite ;
3 le constructeur des membres dans l'ordre de leur déclaration ;
4 le constructeur de la classe.

#include <iostream>
#include <string>

struct MembreA{
MembreA(){std::cout<<"MembreA"<<std::endl;}
};
struct A {
A(){std::cout<<"A"<<std::endl;}
MembreA m;
};
struct MembreB{
MembreB(){std::cout<<"MembreB"<<std::endl;}
};
struct B : A {
B(){std::cout<<"B"<<std::endl;}
MembreB m;
};
struct MembreC{
MembreC(){std::cout<<"MembreC"<<std::endl;}
};
struct C : A {
C(){std::cout<<"C"<<std::endl;}
MembreC m;
};
struct MembreD{
MembreD(){std::cout<<"MembreD"<<std::endl;}
};
struct D : B, C {D(){
std::cout<<"D"<<std::endl;}
MembreD m;

- 62 -
Les sources présentées sur cette pages sont libres de droits, et vous pouvez les utiliser à votre convenance. Par contre la page de présentation de ces sources
constitue une oeuvre intellectuelle protégée par les droits d'auteurs. Copyright © 2008 Developpez LLC. Tout droits réservés Developpez LLC. Aucune
reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents et images sans l'autorisation expresse de Developpez
LLC. Sinon vous encourez selon la loi jusqu'à 3 ans de prison et jusqu'à 300 000 E de dommages et intérêts.
http://cpp.developpez.com/faq/cpp/
};
struct MembreE{MembreE(){
std::cout<<"MembreE"<<std::endl;}
};
struct E : virtual A {E(){
std::cout<<"E"<<std::endl;}
MembreE m;
};
struct MembreF{MembreF(){
std::cout<<"MembreF"<<std::endl;}
};
struct F : virtual A {
F(){std::cout<<"F"<<std::endl;}
MembreF m;
};
struct MembreG{
MembreG(){std::cout<<"MembreG"<<std::endl;}
};
struct G {
G(){std::cout<<"G"<<std::endl;}
MembreG m;
};
struct MembreH{
MembreH(){std::cout<<"MembreH"<<std::endl;}
};
struct H : G, F {
H(){std::cout<<"H"<<std::endl;}
MembreH m;
};
struct MembreI{
MembreI(){std::cout<<"MembreI"<<std::endl;}
};
struct I : E, G, F {
I(){std::cout<<"I"<<std::endl;}
MembreI m;
};

template<class T> void Creation()


{
std::cout<<"Creation d'un "<<typeid(T).name()<<" : "<<std::endl;
T t;
}

int main()
{
Creation<A>();
Creation<B>();
Creation<C>();
Creation<D>();
Creation<E>();
Creation<F>();
Creation<G>();
Creation<H>();
Creation<I>();
return 0;
}

Ce code produit comme sortie :

Creation d'un struct A :


MembreA
A
Creation d'un struct B :
MembreA

- 63 -
Les sources présentées sur cette pages sont libres de droits, et vous pouvez les utiliser à votre convenance. Par contre la page de présentation de ces sources
constitue une oeuvre intellectuelle protégée par les droits d'auteurs. Copyright © 2008 Developpez LLC. Tout droits réservés Developpez LLC. Aucune
reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents et images sans l'autorisation expresse de Developpez
LLC. Sinon vous encourez selon la loi jusqu'à 3 ans de prison et jusqu'à 300 000 E de dommages et intérêts.
http://cpp.developpez.com/faq/cpp/
A
MembreB
B
Creation d'un struct C :
MembreA
A
MembreC
C
Creation d'un struct D :
MembreA
A
MembreB
B
MembreA
A
MembreC
C
MembreD
D
Creation d'un struct E :
MembreA
A
MembreE
E
Creation d'un struct F :
MembreA
A
MembreF
F
Creation d'un struct G :
MembreG
G
Creation d'un struct H :
MembreA
A
MembreG
G
MembreF
F
MembreH
H
Creation d'un struct I :
MembreA
A
MembreE
E
MembreG
G
MembreF
F
MembreI
I

Remarque 1 : La subtilité réside dans la primauté accordée à l'héritage virtuel sur l'héritage non virtuel.
Remarque 2 : L'ordre de construction est fixé par la norme et ne dépend pas des listes d'initialisation du code :

struct Membre1
{
Membre1(){std::cout<<"Membre1"<<std::endl;}
};
struct Membre2
{
Membre2(){std::cout<<"Membre2"<<std::endl;}
};

- 64 -
Les sources présentées sur cette pages sont libres de droits, et vous pouvez les utiliser à votre convenance. Par contre la page de présentation de ces sources
constitue une oeuvre intellectuelle protégée par les droits d'auteurs. Copyright © 2008 Developpez LLC. Tout droits réservés Developpez LLC. Aucune
reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents et images sans l'autorisation expresse de Developpez
LLC. Sinon vous encourez selon la loi jusqu'à 3 ans de prison et jusqu'à 300 000 E de dommages et intérêts.
http://cpp.developpez.com/faq/cpp/
struct A {
A(){std::cout<<"A"<<std::endl;}
};
struct B {
B(){std::cout<<"B"<<std::endl;}
};
struct C : A,B {
C()
:m2(),B(),m1(),A()
{std::cout<<"C"<<std::endl;}
Membre1 m1;
Membre2 m2;
};
int main()
{
C c;
return 0;
}

Ce code produit :

A
B
Membre1
Membre2
C

Certains compilateurs peuvent sortir un avertissement lorsque la liste d'initialisation ne suit pas l'ordre de déclaration
mais ce n'est pas toujours le cas. Pour les listes d'initialisation, une bonne pratique est de toujours suivre l'ordre défini
par la norme pour éviter tout risque de confusion.
Remarque 3 : Pour l'héritage virtuel, le constructeur appelé est celui spécifié par le type effectivement instancié et non
par celui spécifié par le type demandant l'héritage. Si le type instancié ne spécifie pas de constructeur, alors c'est celui
par défaut :

struct A
{
A(std::string appelant_="defaut")
{
std::cout<<"A construit par "<<appelant_<<std::endl;
}
};

struct B : virtual A
{
B()
:A("B")
{
}
};

struct C : B
{
C()
{
}
};

struct D : B
{
D()
:A("D")

- 65 -
Les sources présentées sur cette pages sont libres de droits, et vous pouvez les utiliser à votre convenance. Par contre la page de présentation de ces sources
constitue une oeuvre intellectuelle protégée par les droits d'auteurs. Copyright © 2008 Developpez LLC. Tout droits réservés Developpez LLC. Aucune
reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents et images sans l'autorisation expresse de Developpez
LLC. Sinon vous encourez selon la loi jusqu'à 3 ans de prison et jusqu'à 300 000 E de dommages et intérêts.
http://cpp.developpez.com/faq/cpp/
{
}
};

template<class T> void Creation()


{
std::cout<<"Creation d'un "<<typeid(T).name()<<" : "<<std::endl;
T t;
}

int main()
{
Creation<B>();
Creation<C>();
Creation<D>();
return 0;
}

Ce code produit :

Creation d'un struct B :


A construit par B
Creation d'un struct C :
A construit par defaut
Creation d'un struct D :
A construit par D

Conclusion :
Le constructeur d'une classe doit monter sa liste d'initialisation suivant cet ordre :

1 les constructeurs des classes héritées virtuellement dans tout l'arbre d'héritage en profondeur croissante et de
gauche à droite ;
2 les constructeurs des classes de base directement héritées dans l'ordre de gauche à droite ;
3 les membres dans l'ordre de leur déclaration.

Ceci a comme conséquences :

• Ce sont d'éventuelles contraintes dans l'ordre de construction qui imposeront l'ordre d'héritage (et non des
approches de type d'abord le public, puis le privé).
• Toute dépendance de construction entre les variables membres devra être explicitement commentée à défaut de
pouvoir être évitée. Par cette documentation, les lecteurs du code sont avertis qu'il s'agit d'un comportement
compris et maîtrisé par le développeur : la fiabilitié est accrue et la maintenance est facilitée.

- 66 -
Les sources présentées sur cette pages sont libres de droits, et vous pouvez les utiliser à votre convenance. Par contre la page de présentation de ces sources
constitue une oeuvre intellectuelle protégée par les droits d'auteurs. Copyright © 2008 Developpez LLC. Tout droits réservés Developpez LLC. Aucune
reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents et images sans l'autorisation expresse de Developpez
LLC. Sinon vous encourez selon la loi jusqu'à 3 ans de prison et jusqu'à 300 000 E de dommages et intérêts.
http://cpp.developpez.com/faq/cpp/
Sommaire > Les classes en C++ > Les destructeurs
Qu'est-ce qu'un destructeur ?
Auteurs : LFE ,
Un destructeur est ce qui détruit un objet, libère la mémoire allouée dans le constructeur ou ce qui n'a pas été libéré
durant la vie de l'objet.
Il porte le même nom que la classe précédé du signe ~.
Un destructeur ne prend aucun paramètre et ne renvoie aucune valeur de retour. Sa déclaration se fait de la façon
suivante :

class MaClasse
{
// ...
~MaClasse();
};

lien : Qu'est-ce qu'un constructeur ?

Est-il possible d'invoquer explicitement le destructeur d'une classe ?


Auteurs : LFE , Laurent Gomila , Aurélien Regat-Barrel ,
Oui, il est possible d'appeler explicitement le destructeur d'une classe, bien que ce soit une pratique à proscrire. La
raison en est simple : il est appelé automatiquement par le compilateur lorsque l'objet va être détruit, et ce, que vous
l'ayez appelé ou non. Autrement dit, dans le code suivant :

#include <iostream>

class Test
{
public:
~Test() { std::cout << "Destruction\n"; }
};

int main()
{
Test t;
t.~Test(); // appelle explicite du destructeur
} // ici, le compilateur appelle à nouveau le destructeur

le destructeur est appelé 2 fois. En fonction de ce qu'il fait, cela peut avoir des conséquences dramatiques. Mais surtout
cela ne sert à rien puisque c'est le travail du compilateur.
L'un des seuls cas où l'on doit appeler le destructeur explicitement est lorsqu'on a utilisé l'opérateur new de placement
pour créer le même objet. Dans ce cas, de même qu'on s'est substitué au compilateur pour gérer la vie de l'objet, il
faut faire le travail jusqu'au bout et gérer sa destruction. Voir à ce sujet Qu'est-ce que "placement new" et dans quels
cas l'utilise-t-on ?.
Il y a aussi des fois où l'on est obligé de le faire, comme quand on développe avec un ancien compilateur qui faillit à son
devoir et n'appelle pas le destructeur sur les objets statiques. Dans ce cas aussi l'appel explicite au destructeur est justifié.

Dans quel ordre les objets locaux sont-ils détruits ?


Auteurs : Marshall Cline ,
Dans l'ordre inverse de celui dans lequel ils ont été construits : le premier objet construit est le dernier détruit.

- 67 -
Les sources présentées sur cette pages sont libres de droits, et vous pouvez les utiliser à votre convenance. Par contre la page de présentation de ces sources
constitue une oeuvre intellectuelle protégée par les droits d'auteurs. Copyright © 2008 Developpez LLC. Tout droits réservés Developpez LLC. Aucune
reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents et images sans l'autorisation expresse de Developpez
LLC. Sinon vous encourez selon la loi jusqu'à 3 ans de prison et jusqu'à 300 000 E de dommages et intérêts.
http://cpp.developpez.com/faq/cpp/
Dans l'exemple ci-dessous, le destructeur de b sera exécuté en premier, suivi du destructeur de a :

void userCode()
{
Fred a;
Fred b;
// ...
}

Dans quel ordre les objets contenus dans un tableau sont-ils détruits ?
Auteurs : Marshall Cline ,
Dans l'ordre inverse de celui dans lequel ils ont été construits : le premier objet construit est le dernier détruit.

Dans l'exemple ci-dessous, l'ordre des destructions est a[9], a[8], ..., a[1], a[0] :

void userCode()
{
Fred a[10];
// ...
}

Doit-on détruire explicitement les objets locaux ?


Auteurs : Marshall Cline ,
Surtout pas !

Car le destructeur sera appelé une deuxième fois au niveau de l'accolade fermant le bloc dans lequel l'objet a été créé.
La norme C++ le garantit et vous ne pouvez rien faire pour empêcher que ça arrive ; c'est automatique. Et ça risque
de vraiment très mal se passer si le destructeur d'un objet est appelé deux fois de suite.

Et si on veut absolument qu'un objet local "meure" avant


l'accolade fermant le bloc dans lequel il a été créé ?
Auteurs : Marshall Cline ,
Il suffit de limiter la durée de vie de l'objet local en le plaçant dans un bloc { ... } artificiel :

void someCode()
{
{
File f;
// ... [Ici, le fichier est encore ouvert] ...
}
// ^? Ici, le destructeur de f est appelé automatiquement !
// ^# Ici, le destructeur de f est appelé automatiquement !

// ... [Le code ici s'exécutera après que f soit fermé] ...

- 68 -
Les sources présentées sur cette pages sont libres de droits, et vous pouvez les utiliser à votre convenance. Par contre la page de présentation de ces sources
constitue une oeuvre intellectuelle protégée par les droits d'auteurs. Copyright © 2008 Developpez LLC. Tout droits réservés Developpez LLC. Aucune
reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents et images sans l'autorisation expresse de Developpez
LLC. Sinon vous encourez selon la loi jusqu'à 3 ans de prison et jusqu'à 300 000 E de dommages et intérêts.
http://cpp.developpez.com/faq/cpp/
}

Et s'il n'est pas possible de placer l'objet local dans un bloc artificiel ?
Auteurs : Marshall Cline ,
Dans la plupart des cas, il est possible de limiter la durée de vie d'un objet local en le plaçant dans un bloc artificiel
({ ... }) . Si, pour une raison ou pour une autre, ce n'est pas possible, ajoutez à la classe une fonction membre qui a le
même effet que le destructeur. Mais n'appelez pas le destructeur vous-même !

Dans le cas de File, par exemple, vous pourriez ajouter à la classe une fonction membre close(). Le destructeur se
contenterait simplement d'appeler cette fonction. Notez que la fonction close() aura besoin de marquer l'objet File de
façon à ne pas tenter de fermer le fichier s'il l'est déjà, ce qui peut se produire si close() est appelée plusieurs fois. L'une
des solutions possibles est de donner à la donnée membre fileHandle_ une valeur qui n'a pas de sens, par exemple -1, et
de vérifier à l'entrée de la fonction que fileHandle_ n'est pas égale à cette valeur :

class File {
public:
void close();
~File();
// ...
private:
int fileHandle_; // fileHandle_ >= 0 seulement si le fichier est ouvert
};

File::~File()
{
close();
}

void File::close()
{
if (fileHandle_ >= 0) {
// ... [Utiliser les appels systèmes qui conviennent pour fermer le fichier] ...
fileHandle_ = -1;
}
}

Notez que les autres fonctions membres de la classe File peuvent elles aussi avoir besoin de vérifier que fileHandle_ n'est
pas égale à -1 (c'est-à-dire, de vérifier que le fichier n'est pas fermé).

Peut-on détruire explicitement un objet alloué par new ?


Auteurs : Marshall Cline ,
Dans la plupart des cas, NON.

À moins que vous ayez utilisé placement new, utilisez delete plutôt que d'appeler explicitement le destructeur de l'objet.
Imaginez par exemple que vous ayez alloué un objet grâce à une "new expression" classique :

Fred* p = new Fred();

Le destructeur Fred::~Fred() va être appelé automatiquement quand vous utiliserez delete

delete p; // p->~Fred() est appelé automatiquement

- 69 -
Les sources présentées sur cette pages sont libres de droits, et vous pouvez les utiliser à votre convenance. Par contre la page de présentation de ces sources
constitue une oeuvre intellectuelle protégée par les droits d'auteurs. Copyright © 2008 Developpez LLC. Tout droits réservés Developpez LLC. Aucune
reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents et images sans l'autorisation expresse de Developpez
LLC. Sinon vous encourez selon la loi jusqu'à 3 ans de prison et jusqu'à 300 000 E de dommages et intérêts.
http://cpp.developpez.com/faq/cpp/
N'appelez pas explicitement le destructeur, car cela ne libèrera pas la mémoire allouée pour l'objet Fred lui-même.
Gardez à l'esprit que delete p a deux effets : il appelle le destructeur et il désalloue la mémoire.

Dans le code d'un destructeur, doit-on détruire explicitement les objets membres ?
Auteurs : Marshall Cline ,
Non. Il n'est jamais nécessaire d'appeler explicitement un destructeur (sauf si l'objet a été créé avec un placement new).

Le destructeur d'une classe (il existe même si vous ne l'avez pas défini) appelle automatiquement les destructeurs des
objets membres. Ces objets sont détruits dans l'ordre inverse de celui dans lequel ils apparaissent dans la déclaration
de la classe.

class Member {
public:
~Member();
// ...
};

class Fred {
public:
~Fred();
// ...
private:
Member x_;
Member y_;
Member z_;
};

Fred::~Fred()
{
// Le compilateur appelle automatiquement z_.~Member()
// Le compilateur appelle automatiquement y_.~Member()
// Le compilateur appelle automatiquement x_.~Member()
}

Dans le code du destructeur d'une classe dérivée, doit-


on appeler explicitement le destructeur de la classe de base ?
Auteurs : Marshall Cline ,
Non. Il n'est jamais nécessaire d'appeler explicitement un destructeur (sauf si l'objet a été créé avec un placement new).

Le destructeur d'une classe dérivée (il existe même si vous ne l'avez pas défini) appelle automatiquement les destructeurs
des sous-objets des classes de base. Les classes de base sont détruites après les objets membres. Et dans le cas d'un
héritage multiple, les classes de base directes sont détruites dans l'ordre inverse de celui dans lequel elles apparaissent
dans la déclaration d'héritage.

class Member {
public:
~Member();
// ...
};

class Base {
public:
virtual ~Base(); // Un destructeur virtuel
// ...

- 70 -
Les sources présentées sur cette pages sont libres de droits, et vous pouvez les utiliser à votre convenance. Par contre la page de présentation de ces sources
constitue une oeuvre intellectuelle protégée par les droits d'auteurs. Copyright © 2008 Developpez LLC. Tout droits réservés Developpez LLC. Aucune
reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents et images sans l'autorisation expresse de Developpez
LLC. Sinon vous encourez selon la loi jusqu'à 3 ans de prison et jusqu'à 300 000 E de dommages et intérêts.
http://cpp.developpez.com/faq/cpp/
};

class Derived : public Base {


public:
~Derived();
// ...
private:
Member x_;
};

Derived::~Derived()
{
// Le compilateur appelle automatiquement x_.~Member()
// Le compilateur appelle automatiquement Base::~Base()
}

Note : l'ordre des destructions dans le cas d'un héritage virtuel est plus compliqué. Si vous voulez vous baser sur l'ordre
des destructions dans le cas d'un héritage virtuel, il va vous falloir plus d'informations que celles simplement contenues
dans cette FAQ.

Pourquoi et quand faut-il créer un destructeur virtuel ?


Auteurs : Aurélien Regat-Barrel ,
Il est nécessaire de rendre le destructeur d'une classe de base virtuel quand celle-ci est destinée à être détruite
polymorphiquement. Généralement dès qu'un objet commence à être utilisé polymorphiquement (c'est-à-dire utilisé
en place d'un objet de la classe mère), il est fréquent qu'il soit stocké et manipulé via un pointeur vers sa classe mère,
comme dans l'exemple suivant :

#include <iostream>

// classe de base A destinée à être dérivée


class A
{
public:
A() { std::cout << "Constructeur de A.\n"; }
~A() { std::cout << "Destructeur de A.\n"; }

virtual void PrintName() { std::cout << "Classe A.\n"; }


};

// B hérite de A
class B : public A
{
public:
B() { std::cout << "Constructeur de B.\n"; }
~B() { std::cout << "Destructeur de B.\n"; }

virtual void PrintName() { std::cout << "Classe B.\n"; }


};

int main()
{
// utilisation polymorphe de B
A * a = new B; // construction de A et B
a->PrintName(); // affiche "Classe B"
delete a; // destruction de A mais pas de B !
}

Si vous avez compris comment fonctionne les fonctions membres virtuelles (voir Que signifie le mot-clé virtual ?), vous
pouvez alors deviner ce que l'instruction delete a; provoque : la destruction d'un objet de type A, donc l'appel de

- 71 -
Les sources présentées sur cette pages sont libres de droits, et vous pouvez les utiliser à votre convenance. Par contre la page de présentation de ces sources
constitue une oeuvre intellectuelle protégée par les droits d'auteurs. Copyright © 2008 Developpez LLC. Tout droits réservés Developpez LLC. Aucune
reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents et images sans l'autorisation expresse de Developpez
LLC. Sinon vous encourez selon la loi jusqu'à 3 ans de prison et jusqu'à 300 000 E de dommages et intérêts.
http://cpp.developpez.com/faq/cpp/
A::~A(), et de rien d'autre. Or, le type réel de notre objet est B, et donc son destructeur n'est pas appelé ! Ceci produit
un comportement indéfini, qui se traduit souvent par des fuites de mémoires ou des problèmes encore plus graves (tout
dépend de ce que le destructeur de B était censé faire).
Le code précédent compilé avec Visual C++ 7.1 produit le résultat suivant :

Constructeur de A. Constructeur de B. Classe B. Destructeur de A.

Comme vous pouvez le constater, le destructeur de B n'est effectivement pas appelé. Ceci est résolu en rendant le
destructeur de A virtuel.

class A
{
public:
A() { std::cout << "Constructeur de A.\n"; }
virtual ~A() { std::cout << "Destructeur de A.\n"; }

virtual void PrintName() { std::cout << "Classe A.\n"; }


};

Dans un tel cas d'utilisation, il est donc important de ne pas oublier le destructeur virtuel dans la classe de base. Cela ne
signifie pas pour autant qu'il faille rendre tous les destructeurs virtuels. Tout d'abord une fonction membre virtuelle
engendre un surcoût lors de son appel ainsi qu'en termes de d'occupation mémoire. Mais aussi un destructeur virtuel
indique que la classe a été réalisée dans le but d'être dérivée.
Rendre un destructeur virtuel ou non ne se limite donc pas à l'ajout du mot-clé virtual mais doit être l'aboutissement
d'une réflexion menée sur l'utilisation de la classe. Une classe qui n'est pas destinée à être dérivée n'a pas à avoir de
destructeur virtuel.

- 72 -
Les sources présentées sur cette pages sont libres de droits, et vous pouvez les utiliser à votre convenance. Par contre la page de présentation de ces sources
constitue une oeuvre intellectuelle protégée par les droits d'auteurs. Copyright © 2008 Developpez LLC. Tout droits réservés Developpez LLC. Aucune
reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents et images sans l'autorisation expresse de Developpez
LLC. Sinon vous encourez selon la loi jusqu'à 3 ans de prison et jusqu'à 300 000 E de dommages et intérêts.
http://cpp.developpez.com/faq/cpp/
Sommaire > Les classes en C++ > Les amis (friend)
Que signifie 'friend' ?
Auteurs : Marshall Cline ,
Quelque chose qui permet à une classe d'offrir des droits d'accès privilégiés à une autre classe ou fonction.

Les amis (friends) peuvent être soit des fonctions, soit d'autres classes. Une classe offre des droits d'accès privilégiés à
ses amis. Le développeur d'une classe exerce en théorie un contrôle technique et politique aussi bien sur les friends que
sur les fonctions membres de la classe (si ce n'était pas le cas, il lui faudrait obtenir une autorisation de ceux qui ont
écrits des amis lorsqu'il souhaite modifier sa classe).

Les amis brisent-ils l'encapsulation ?


Auteurs : Marshall Cline ,
C'est tout le contraire : s'ils sont utilisés correctement, ils renforcent l'encapsulation.

Il est souvent nécessaire de séparer une classe en deux, par exemple quand les deux moitiés ne peuvent pas avoir le
même nombre d'instances ou quand elles n'ont pas la même durée de vie. Dans ce genre de cas, les deux moitiés ont
généralement besoin de pouvoir accéder directement l'une à l'autre (dans la mesure où ces deux moitiés appartenaient
à une unique classe, le nombre de lignes de code nécessitant un accès direct à la structure de données n'a pas augmenté ;
le code a simplement été redistribué entre deux classes plutôt que laissé dans une seule). La façon la plus sûre
d'implémenter cela est de rendre les deux moitiés amies l'une de l'autre.

En vous basant sur le modèle ci-dessus pour utiliser les amis, vous garantissez que les parties private restent private.
Les gens qui ne comprennent pas ce modèle font souvent des tentatives naïves pour éviter d'utiliser l'amitié dans des
situations se rapprochant de celle vue au-dessus. Ces tentatives vont en fait le plus souvent briser l'encapsulation. Soit ces
personnes utilisent des données public (c'est grotesque !), soit elles offrent un accès aux données à travers des fonctions
membres public de type get()/set(). Il n'y a pas de problème à avoir des fonctions membres public de type get()/set() qui
donnent accès à des données private, mais seulement quand accéder à ces données private "a un sens" du point de vue de
l'extérieur de la classe (du point de vue de l'utilisateur). Dans de nombreux cas, à utiliser des fonctions membres get()/
set() s'avère presque aussi mauvais que d'utiliser directement des données public : ces fonctions à membres cachent
(seulement) le nom de la donnée private, mais elles ne cachent pas son existence.

De façon similaire, utiliser des fonctions amies comme une alternative syntaxique aux fonctions d'accès public : de la
classe ne brise pas plus l'encapsulation que le font les fonctions membres de la classe. On pourrait dire que les amis
d'une classe ne brisent pas la barrière d'encapsulation : avec les fonctions membres de la classe, ils sont la barrière
d'encapsulation.

Quels avantages/désavantages y a-t-il à utiliser des fonctions friend ?


Auteurs : Marshall Cline ,
Elles offrent plus de possibilités dans la conception d'une interface.

Les fonctions membres et les fonctions friend ont des droits d'accès identiques (c'est 100% garanti). La différence
principale est qu'un appel à une fonction friend est de la forme f(x), alors qu'un appel à une fonction membre est de la
forme x.f(). Ainsi, la possibilité qu'a le concepteur de la classe de choisir entre les fonctions membres (x.f()) et les fonctions
friend (f(x)) lui permet de sélectionner la syntaxe qu'il estime la plus lisible, ce qui diminuera le coût de maintenance.

- 73 -
Les sources présentées sur cette pages sont libres de droits, et vous pouvez les utiliser à votre convenance. Par contre la page de présentation de ces sources
constitue une oeuvre intellectuelle protégée par les droits d'auteurs. Copyright © 2008 Developpez LLC. Tout droits réservés Developpez LLC. Aucune
reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents et images sans l'autorisation expresse de Developpez
LLC. Sinon vous encourez selon la loi jusqu'à 3 ans de prison et jusqu'à 300 000 E de dommages et intérêts.
http://cpp.developpez.com/faq/cpp/
Le désavantage principal des fonctions friend est qu'elles nécessitent une ligne de code supplémentaire pour obtenir une
liaison dynamique (dynamic binding). Pour simuler un virtual friend, il est nécessaire que la fonction friend appelle une
fonction membre virtual cachée (qui est habituellement protected:). C'est ce que l'on appelle l'Idiome de la Fonction
Friend Virtuelle. Voici un exemple :

class Base {
public:
friend void f(Base& b);
// ...
protected:
virtual void do_f();
// ...
};

inline void f(Base& b)


{
b.do_f();
}

class Derived : public Base {


public:
// ...
protected:
virtual void do_f(); // "Redéfinit" le comportement de f(Base& b)
// ...
};

void userCode(Base& b)
{
f(b);
}

L'instruction f(b) dans la fonction userCode(Base&) va invoquer b.do_f(), qui est virtual. Ce qui veut dire que
Derived::do_f() va être appelée si b est effectivement un objet de la classe Derived. Notez que Derived redéfinit le
comportement de la fonction membre protected: virtual do_f(); elle, n'a pas sa propre version de la fonction friend
f(Base&).

Que signifie "l'amitié n'est ni héritée ni transitive, ni réciproque" ?


Auteurs : Marshall Cline ,
Ce n'est pas parce que je vous donne accès à ma classe en tant qu'ami que j'autorise vos enfants à y accéder, ni à vos
amis, et cela ne me donne pas non plus automatiquement accès à votre classe.

Je ne fais pas forcément confiance aux enfants de mes amis Les privilèges de l'amitié ne sont pas hérités. Les classes
dérivées d'une classe amie ne sont pas forcément des amis. Si la classe Fred déclare que la classe Base est une amie, les
classes dérivées de Base n'ont pas à avoir automatiquement des droits d'accès particuliers aux objets de type Fred.

Je ne fais pas forcément confiance aux amis de mes amis Les privilèges de l'amitié ne sont pas transitifs. Un ami d'un
ami n'est pas forcément un ami. Si la classe Fred déclare que la classe Wilma est une amie, et que la classe Wilma
déclare que Betty est une amie, la classe Betty n'a pas à avoir automatiquement des droits d'accès particuliers aux
objets de type Fred.

- 74 -
Les sources présentées sur cette pages sont libres de droits, et vous pouvez les utiliser à votre convenance. Par contre la page de présentation de ces sources
constitue une oeuvre intellectuelle protégée par les droits d'auteurs. Copyright © 2008 Developpez LLC. Tout droits réservés Developpez LLC. Aucune
reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents et images sans l'autorisation expresse de Developpez
LLC. Sinon vous encourez selon la loi jusqu'à 3 ans de prison et jusqu'à 300 000 E de dommages et intérêts.
http://cpp.developpez.com/faq/cpp/
L'amitié n'est pas réciproque Vous ne me faites pas confiance simplement parce que je déclare que vous être mon ami.
Les privilèges de l'amitié ne sont pas réciproques. Si la classe Fred déclare que la classe Wilma est une amie, les objets
de type Fred n'ont pas à avoir automatiquement des droits d'accès particuliers aux objets de type Wilma.

Doit-on utiliser plutôt des fonctions membres ou plutôt des fonctions friend ?
Auteurs : Marshall Cline ,
Utilisez les membres quand vous le pouvez, et les friend quand vous le devez.

Les amis sont parfois un meilleur choix d'un point de vue syntaxique (comme par exemple quand une fonction amie
permet à un objet de type Fred d'être utilisé en tant que second paramètre de la fonction, tandis qu'une fonction membre
obligerait à ce que l'objet Fred soit en premier). Les opérateurs arithmétiques binaires infixes sont un autre cas où
l'utilisation des fonctions friend est appropriée. Par exemple, aComplex + aComplex doit être défini comme un ami
plutôt que comme un membre si vous voulez aussi pouvoir écrire aFloat + aComplex (les fonctions membres n'autorisent
pas la promotion de l'argument de gauche, la raison étant que cela changerait la classe de l'objet sur lequel on invoque
la fonction membre).

Dans les autres cas, utilisez une fonction membre plutôt qu'une fonction friend. De plus; il est préférable d'utiliser au
maximum des fonctions libres dans le même namespace

- 75 -
Les sources présentées sur cette pages sont libres de droits, et vous pouvez les utiliser à votre convenance. Par contre la page de présentation de ces sources
constitue une oeuvre intellectuelle protégée par les droits d'auteurs. Copyright © 2008 Developpez LLC. Tout droits réservés Developpez LLC. Aucune
reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents et images sans l'autorisation expresse de Developpez
LLC. Sinon vous encourez selon la loi jusqu'à 3 ans de prison et jusqu'à 300 000 E de dommages et intérêts.
http://cpp.developpez.com/faq/cpp/
Sommaire > Les classes en C++ > Les données et fonctions membres statiques
Que signifie la déclaration suivante : "static const int MAX = 10" ?
Auteurs : Musaran , Laurent Gomila ,
const signale que la variable ne peut pas changer de valeur, et que le compilateur refusera qu'on le fasse.

static (dans le cas de la classe) signifie que la variable n'existe qu'en un seul exemplaire, elle est globale à la classe en
quelque sorte. Autrement, chaque objet du type de la classe dispose de sa propre copie.

Une telle syntaxe (déclaration et définition en un seul coup) est possible en C++, mais seulement parce qu'elle vérifie
certaines conditions : il s'agit d'une variable constante, statique, et de type entier. En d'autres termes ces déclarations
ne compileraient pas :

class MaClasse
{
static int x = 0; // erreur : pas constant
const int y = 5; // erreur : pas statique
static const float z = 4.2f; // erreur : pas entier
};

Pour correctement définir les variables statiques qui ne sont pas entières ou constantes, voir Comment initialiser un
membre static ?.

A noter que certains vieux compilateurs n'accepteront peut-être pas cette syntaxe, dans ce cas on pourra utiliser ce
qu'on appelle l'enum hack :

class MaClasse
{
enum {MAX = 10};
};

Pourquoi déclarer un membre static dans une classe ?


Auteurs : LFE ,
Déclarer un membre static dans une classe permet de n'avoir qu'une instance de ce membre en mémoire. Toute
modification effectuée sur ce membre dans une instance de cette classe sera visible par les autres instances.
On peut voir cela comme une façon de mettre en place un mécanisme de 'variables globales' internes à la classe, par
exemple.

Comment initialiser un membre static ?


Auteurs : LFE , Aurélien Regat-Barrel ,
Le membre static doit être initialisé dans le fichier .cpp de la façon suivante.

// dans le fichier Exemple.h


#ifndef EXEMPLE_H
#define EXEMPLE_H

class Exemple
{

- 76 -
Les sources présentées sur cette pages sont libres de droits, et vous pouvez les utiliser à votre convenance. Par contre la page de présentation de ces sources
constitue une oeuvre intellectuelle protégée par les droits d'auteurs. Copyright © 2008 Developpez LLC. Tout droits réservés Developpez LLC. Aucune
reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents et images sans l'autorisation expresse de Developpez
LLC. Sinon vous encourez selon la loi jusqu'à 3 ans de prison et jusqu'à 300 000 E de dommages et intérêts.
http://cpp.developpez.com/faq/cpp/
public:
static int compteur;
};

#endif

// dans le fichier Exemple.cpp


#include "Exemple.h"

int Exemple::compteur = 0;

Ainsi il est donc impossible d'initialiser une donnée membre statique dans la liste d'initialisation (ce qui est tout à fait
logique, puisqu'une telle variable "appartient" à toute la classe et pas à une instance en particulier).

Exemple::Exemple() :
compteur(5) // ERREUR
{

Note : il existe un raccourci pour les variables statiques entières constantes, voir Que signifie la déclaration suivante :
"static const int MAX = 10" ?.

Pourquoi les classes avec des membres statiques me


donnent-elles des erreurs lors de l'édition des liens ?
Auteurs : Marshall Cline ,
Parce que les données membres statiques doivent être explicitement définies dans exactement une unité de compilation.
Si vous n'avez pas fait cela, vous avez certainement eu une erreur du type "undefined external" (référence externe
indéfinie) par l' éditeur de liens (linker).

Fred.h
class Fred {
public:
...
private:
static int j_; // Déclare la donnée membre static Fred::j_
...
};

L'éditeur de liens vous grondera "Fred::j_ is not defined" (Fred::j_ n'est pas défini) à moins que vous ne définissiez
(par opposition à déclariez) Fred::j_ dans (exactement) un de vos fichiers source

Fred.cpp
#include "Fred.h"

int Fred::j_ = some_expression_evaluating_to_an_int;

// A côté de ça, si vous souhaitez utiliser la valeur implicite des entiers static :
// int Fred::j_;

- 77 -
Les sources présentées sur cette pages sont libres de droits, et vous pouvez les utiliser à votre convenance. Par contre la page de présentation de ces sources
constitue une oeuvre intellectuelle protégée par les droits d'auteurs. Copyright © 2008 Developpez LLC. Tout droits réservés Developpez LLC. Aucune
reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents et images sans l'autorisation expresse de Developpez
LLC. Sinon vous encourez selon la loi jusqu'à 3 ans de prison et jusqu'à 300 000 E de dommages et intérêts.
http://cpp.developpez.com/faq/cpp/
La place habituelle pour définir une donnée membre static de la classe Fred est dans le fichier Fred.cpp

Qu'est-ce que le "fiasco dans l'ordre d'initialisation des variables statiques" ?


Auteurs : Marshall Cline ,
Un moyen subtil de planter votre programme.

Le fiasco dans l'ordre d'initialisation des variables statiques est un des aspects les plus subtils et habituellement mal
compris du C++. Malheureusement, il est très difficile à détecter étant donné que l'erreur se produit avant même le
début de l'exécution du main().

Supposons que l'on ait deux objets statiques x et y qui se trouvent dans des fichiers sources séparés (x.cpp et y.cpp)
Supposons ensuite que l'initialisation de l'objet y (typiquement le constructeur de l'objet y) appelle une fonction membre
de l'objet x.

C'est aussi simple que cela.

La tragédie est que vous avez 50% de chances de vous planter. S'il arrive que l'unité de compilation correspondant
à x.cpp soit initialisée avant celle correspondant à y.cpp, tout va bien. Mais si l'unité de compilation correspondant à
y.cpp est initialisée d'abord, alors le constructeur de y sera en route avant le constructeur de x, et vous êtes cuit. C'est-
à-dire que le constructeur de y appellera une fonction de l'objet x, alors que ce dernier n'est pas encore construit.

Si vous pensez que c'est "excitant" de jouer à la roulette russe avec la moitié du barillet chargé, vous pouvez vous
arrêter de lire ici. Si au contraire vous aimez augmenter vos chances de survie en prévenant les désastres de manière
systématique, vous serez probablement intéressé par la question suivante.

Note : le fiasco dans l'ordre d'initialisation des variables statiques peut aussi se produire, dans certains cas, avec les
types de base.

Comment puis-je éviter le "fiasco dans l'ordre d'initialisation des variables statiques" ?
Auteurs : Marshall Cline ,
Utilisez l'idiome de "construction à la première utilisation", qui consiste simplement à emballer (wrap) vos objets
statiques à l'intérieur d'une fonction.

Par exemple, supposez que vous ayez deux classes, Fred et Barney. Il y a un objet Fred global appelé x, et un objet
Barney global appelé y. Le constructeur de Barney invoque la fonction membre goBowling() (va jouer au bowling) de
l'objet x. Le fichier x.cpp définit l'objet x

x.cpp
#include "Fred.hpp"

Fred x;

Le fichier y.cpp définit l'objet y:

y.cpp
#include "Barney.hpp"

Barney y;

- 78 -
Les sources présentées sur cette pages sont libres de droits, et vous pouvez les utiliser à votre convenance. Par contre la page de présentation de ces sources
constitue une oeuvre intellectuelle protégée par les droits d'auteurs. Copyright © 2008 Developpez LLC. Tout droits réservés Developpez LLC. Aucune
reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents et images sans l'autorisation expresse de Developpez
LLC. Sinon vous encourez selon la loi jusqu'à 3 ans de prison et jusqu'à 300 000 E de dommages et intérêts.
http://cpp.developpez.com/faq/cpp/
Pour être complet, le constructeur de Barney pourrait ressembler à quelque chose comme :

Barney.cpp
#include "Barney.hpp"

Barney::Barney()
{
// ...
x.goBowling();
// ...
}

Comme décrit ci-dessus, le désastre intervient si y est construit avant x, ce qui arrive 50% du temps puisqu'ils sont dans
deux fichiers sources différents.

Il y a beaucoup de solutions à ce problème, mais une solution très simple et complètement portable est de remplacer
l'objet (de type Fred) global x, par une fonction globale x(), qui retourne par référence l'objet Fred.

x.cpp
#include "Fred.hpp"

Fred& x()
{
static Fred* ans = new Fred();
return *ans;
}

Puisque les objets locaux statiques sont construits la première fois (et seulement la première fois) où le flux de contrôle
passe sur la déclaration, l'instruction new Fred() sera non seulement exécutée une fois : la première fois que x() est
appelée, mais chaque appel suivant retournera le même objet de type Fred (celui pointé par ans). Tout ce qu'il reste
à faire est de changer x en x() :

Barney.cpp
#include "Barney.hpp"

Barney::Barney()
{
// ...
x().goBowling();
// ...
}

C'est ce qu'on appelle "Idiome de la construction à la première utilisation", parce que c'est exactement ce qu'il fait :
l'objet global Fred est créé lors de sa première utilisation.

Le défaut de cette approche est que l'objet Fred n'est jamais détruit. Il existe une seconde technique qui solutionne ce
problème mais il faut l'utiliser prudemment étant donné qu'il risque de créer un autre problème très sale.

- 79 -
Les sources présentées sur cette pages sont libres de droits, et vous pouvez les utiliser à votre convenance. Par contre la page de présentation de ces sources
constitue une oeuvre intellectuelle protégée par les droits d'auteurs. Copyright © 2008 Developpez LLC. Tout droits réservés Developpez LLC. Aucune
reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents et images sans l'autorisation expresse de Developpez
LLC. Sinon vous encourez selon la loi jusqu'à 3 ans de prison et jusqu'à 300 000 E de dommages et intérêts.
http://cpp.developpez.com/faq/cpp/
Note : le Qu'est-ce que le "fiasco dans l'ordre d'initialisation des variables statiques" ? peut aussi se produire, dans
certains cas, avec les types de base.

Pourquoi l'idiome de construction à la première utilisation


utilise-t-il un pointeur statique plutôt qu'un objet statique ?
Auteurs : Marshall Cline ,
La réponse courte : il est possible d'utiliser un objet statique plutôt qu'un pointeur statique, mais faire cela ouvre la
porte à un autre problème aussi subtil que pervers.

La réponse longue : parfois, les gens s'inquiètent des problèmes de fuite mémoire de la solution précédente. Dans la
plupart des cas, ce n'est pas un problème, mais par contre cela peut en être un dans d'autres circonstances. Note :
Même si l'objet pointé par ans dans la question précédente n'est jamais libéré, la mémoire n'est pas perdue quand
le programme se termine, étant donné que l'OS récupère automatiquement l'entièreté de la mémoire allouée au
programme quand celui-ci se termine. En d'autres mots, le seul moment ou vous devez vous en inquiéter est celui où le
destructeur de l'objet Fred effectue certaines actions importantes (par exemple, écrire quelque chose dans un fichier)
qui doit être effectué lorsque le programme se termine.

Dans ce cas, où l'objet construit à la première utilisation (Fred dans ce cas) a éventuellement besoin d'être détruit, vous
pouvez changer la fonction x() comme suit :

x.cpp
#include "Fred.hpp"

Fred& x()
{
static Fred ans; // au lieu de static Fred* ans = new Fred();
return ans; // au lieu de return *ans;
}

Cependant, il apparaît (ou du moins, il peut apparaître) un problème relativement subtil avec ce changement. Pour
comprendre ce problème potentiel, il faut se souvenir pourquoi nous faisons cela : Nous avons besoin d'être sûr à 100
% que notre objet statique

• est construit avant sa toute première utilisation


• n'a pas besoin d'être détruit après sa dernière utilisation

Il est évident qu'il serait désastreux qu'un objet statique soit utilisé avant sa construction ou après sa destruction. L'idée
ici est que vous devez vous inquiéter de deux situations et non pas simplement d'une des deux.

En changeant la déclaration

static Fred* ans = new Fred();

en

static Fred ans;

nous gérons toujours correctement l'initialisation, mais nous ne gérons plus la libération. Par exemple, s'il y a 3 objets
statiques, a, b et c, qui utilisent ans dans leur destructeur, la seule façon d'éviter un désastre à la libération et que ans
soit détruit après la destruction du dernier des 3 objets.

- 80 -
Les sources présentées sur cette pages sont libres de droits, et vous pouvez les utiliser à votre convenance. Par contre la page de présentation de ces sources
constitue une oeuvre intellectuelle protégée par les droits d'auteurs. Copyright © 2008 Developpez LLC. Tout droits réservés Developpez LLC. Aucune
reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents et images sans l'autorisation expresse de Developpez
LLC. Sinon vous encourez selon la loi jusqu'à 3 ans de prison et jusqu'à 300 000 E de dommages et intérêts.
http://cpp.developpez.com/faq/cpp/
La situation est simple : s'il existe un autre objet statique qui utilise ans dans son destructeur alors que ce dernier a été
détruit, vous êtes mort. Si le constructeur de a,b et c utilisent ans, tout devrait être correct vu que le système d'exécution
détruira ans après que le dernier des 3 objets ait été détruit. Cependant, a et/ou b et/ou c n'arrive pas à utiliser ans
dans leur constructeur et/ou un bout de code quelque part prend l'adresse de ans et le passe à un autre objet statique,
soyez très très prudent.

Il y a une troisième approche qui gère aussi bien l'initialisation que la libération, mais elle a d'autres influences non
triviales. Mais je n'ai pas le courage de la décrire ici, je vous renvoie donc vers un bon livre de C++.

Comment puis-je éviter le "fiasco dans l'ordre d'initialisation


des variables statiques" pour les données membres statiques ?
Auteurs : Marshall Cline ,
Simplement en utilisant la méthode décrite juste avant, mais cette fois, en utilisant une fonction membre statique plutôt
qu'une fonction globale.

Supposons que l'on ait une classe X qui a un objet Fred statique

x.hpp
class X
{
public:
// ...

private:
static Fred x_;
};

Naturellement, le membre statique est initialisé séparément :

x.cpp
#include "X.hpp"

Fred X::x_;

L'objet Fred va être utilisé dans une ou plusieurs fonctions membres de X :

void X::someMethod()
{
x_.goBowling();
}

Maintenant, le scénario catastrophe se présentera si quelqu'un appelle cette fonction avant que l'objet Fred ne soit
complètement construit. Par exemple, si quelqu'un d'autre crée un objet X statique et invoque sa fonction membre
someMethod() pendant l'initialisation statique, nous voila à la merci du compilateur, suivant qu'il construise X::x_ avant
ou après que someMethod() ait été appelée. (Il est à noter que le comité ANSI/ISO C++ travaille sur ce point, mais les
compilateurs actuels ne gèrent pas ce cas, ce sera probablement une mise à jour future.)

Quoi qu'il en soit, il est portable et prudent de transformer la donnée membre statique X::x_ en une fonction membre
statique

x.hpp
class X

- 81 -
Les sources présentées sur cette pages sont libres de droits, et vous pouvez les utiliser à votre convenance. Par contre la page de présentation de ces sources
constitue une oeuvre intellectuelle protégée par les droits d'auteurs. Copyright © 2008 Developpez LLC. Tout droits réservés Developpez LLC. Aucune
reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents et images sans l'autorisation expresse de Developpez
LLC. Sinon vous encourez selon la loi jusqu'à 3 ans de prison et jusqu'à 300 000 E de dommages et intérêts.
http://cpp.developpez.com/faq/cpp/
x.hpp
{
public:
// ...

private:
static Fred& x();
};

Naturellement, ce membre statique est initialisé séparément.

x.cpp
#include "X.hpp"

Fred& X::x()
{
static Fred* ans = new Fred();
return *ans;
}

Il suffit ensuite simplement de remplacer toutes les utilisations de x_ par x()

void X::someMethod()
{
x().goBowling();
}

Si les performances sont très importantes à vos yeux et que l'appel d'une fonction supplémentaire à chaque invocation
de X::someMethod() vous est insupportable, vous pouvez toujours prévoir un static Fred&. Comme les données statiques
locales ne sont jamais initialisées qu'une seule fois (la première fois que leur déclaration est rencontrée), X::x() n'est
appelé qu'une fois, lors du premier appel de X::someMethod().

void X::someMethod()
{
static Fred& x = X::x();
x.goBowling();
}

Note : le Qu'est-ce que le "fiasco dans l'ordre d'initialisation des variables statiques" ? peut aussi se produire, dans
certains cas, avec les types de base.

Dois-je me préoccuper du "fiasco dans l'ordre d'initialisation


des variables statiques" pour les types de base ?
Auteurs : Marshall Cline ,
Oui.

Si vous initialisez les types de base en utilisant une fonction, le Qu'est-ce que le "fiasco dans l'ordre d'initialisation des
variables statiques" ? peut vous causer des problèmes aussi bien qu'avec des classes définies par vous.

Le code suivant expose le problème

#include <iostream>

int f(); // déclaration anticipée

- 82 -
Les sources présentées sur cette pages sont libres de droits, et vous pouvez les utiliser à votre convenance. Par contre la page de présentation de ces sources
constitue une oeuvre intellectuelle protégée par les droits d'auteurs. Copyright © 2008 Developpez LLC. Tout droits réservés Developpez LLC. Aucune
reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents et images sans l'autorisation expresse de Developpez
LLC. Sinon vous encourez selon la loi jusqu'à 3 ans de prison et jusqu'à 300 000 E de dommages et intérêts.
http://cpp.developpez.com/faq/cpp/
int g(); // déclaration anticipée

int x = f();
int y = g();

int f()
{
std::cout << "using 'y' (which is " << y << ")\n";
return 3*y + 7;
}

int g()
{
std::cout << "initializing 'y'\n";
return 5;
}

La sortie de ce petit programme montre qu'il utilise y avant qu'il ait été initialisé. La solution, Comment puis-je éviter
le "fiasco dans l'ordre d'initialisation des variables statiques" ? , est d'utiliser l'idiome de la construction à la première
utilisation.

#include <iostream>

int f(); // déclaration anticipée


int g(); // déclaration anticipée

int& x()
{
static int ans = f();
return ans;
}

int& y()
{
static int ans = g();
return ans;
}

int f()
{
std::cout << "using 'y' (which is " << y() << ")\n";
return 3*y() + 7;
}

int g()
{
std::cout << "initializing 'y'\n";
return 5;
}

il est possible de simplifier ce code en déplaçant le code d'initialisation pour x et y dans leur fonction respective

#include <iostream>

int& y(); // déclaration anticipée

int& x()
{
static int ans;

static bool firstTime = true;


if (firstTime) {
firstTime = false;
std::cout << "using 'y' (which is " << y() << ")\n";
ans = 3*y() + 7;

- 83 -
Les sources présentées sur cette pages sont libres de droits, et vous pouvez les utiliser à votre convenance. Par contre la page de présentation de ces sources
constitue une oeuvre intellectuelle protégée par les droits d'auteurs. Copyright © 2008 Developpez LLC. Tout droits réservés Developpez LLC. Aucune
reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents et images sans l'autorisation expresse de Developpez
LLC. Sinon vous encourez selon la loi jusqu'à 3 ans de prison et jusqu'à 300 000 E de dommages et intérêts.
http://cpp.developpez.com/faq/cpp/
}

return ans;
}

int& y()
{
static int ans;

static bool firstTime = true;


if (firstTime) {
firstTime = false;
std::cout << "initializing 'y'\n";
ans = 5;
}

return ans;
}

Et en supprimant les affichages, nous pouvons simplifier le code en quelque chose de très simple

int& y(); // déclaration anticipée

int& x()
{
static int ans = 3*y() + 7;
return ans;
}

int& y()
{
static int ans = 5;
return ans;
}

De plus, étant donné que y est initialisé via une expression constante, elle n'a plus besoin de sa fonction d'enrobage
(wrapper), et est donc redevenue une simple variable.

- 84 -
Les sources présentées sur cette pages sont libres de droits, et vous pouvez les utiliser à votre convenance. Par contre la page de présentation de ces sources
constitue une oeuvre intellectuelle protégée par les droits d'auteurs. Copyright © 2008 Developpez LLC. Tout droits réservés Developpez LLC. Aucune
reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents et images sans l'autorisation expresse de Developpez
LLC. Sinon vous encourez selon la loi jusqu'à 3 ans de prison et jusqu'à 300 000 E de dommages et intérêts.
http://cpp.developpez.com/faq/cpp/
Sommaire > Les classes en C++ > Sémantique de copie
Qu'est-ce qu'une classe copiable ?
Auteurs : 3DArchi ,
Une classe copiable permet à ses instances d'être copiées à l'identique d'un contenant vers un autre. En C++,
syntaxiquement cette propriété est vérifiée dès qu'une classe possède un constructeur par copie ou un opérateur
d'affectation.

copyable a;
copyable b = a; // ou copyable b(a);

Sémantiquement, il faut néanmoins prendre garde à ce que la copie d'un objet n'entraîne pas des effets indésirables :

class copy_with_problems
{
public:
copy_with_problems()
:m_probleme(new Type())
{}
copy_with_problems(copy_with_problems const & copie_)
:m_probleme(copie_.m_probleme)
{}

copy_with_problems& operator=(copy_with_problems const & rhs_)


{
delete m_probleme;
m_probleme = copie_.m_probleme;
return *this;
}

~copy_with_problems()
{
delete m_probleme;
}

private:
Type *m_probleme;
};
int main()
{
copy_with_problems my_object;
{
copy_with_problems a_copy(my_object);
}// au sortir on détruit le pointeur de a_copy qui est le même que celui
// de my_object
// ici my_object.m_probleme pointe sur une zone invalide !
}

lien : Je n'ai pas de constructeur par copie ni d'opérateur d'affectation (operator=) : ma classe est-elle copiable ?
lien : Toute classe doit-elle être copiable ?
lien : Comment rendre une classe non copiable ?
lien : Que se passe-t-il pour les classes dérivées d'une classe non copiable ?
lien : Quelle solution préférer (héritage ou redéfinition) pour rendre une classe non copiable ?
lien : Comment rendre une classe non copiable en C++0x ?

- 85 -
Les sources présentées sur cette pages sont libres de droits, et vous pouvez les utiliser à votre convenance. Par contre la page de présentation de ces sources
constitue une oeuvre intellectuelle protégée par les droits d'auteurs. Copyright © 2008 Developpez LLC. Tout droits réservés Developpez LLC. Aucune
reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents et images sans l'autorisation expresse de Developpez
LLC. Sinon vous encourez selon la loi jusqu'à 3 ans de prison et jusqu'à 300 000 E de dommages et intérêts.
http://cpp.developpez.com/faq/cpp/
lien : Quel est l'équivalent de boost::noncopyable en C++0x ?

Je n'ai pas de constructeur par copie ni d'opérateur


d'affectation (operator=) : ma classe est-elle copiable ?
Auteurs : 3DArchi ,
Le compilateur génère des versions par défaut du constructeur par copie et de l'opérateur=.
Le constructeur par copie généré par le compilateur correspond à un appel du constructeur par copie des classes
parentes et du constructeur par copie des membres.
Le comportement est le même pour l'opérateur = par défaut.
Notons toutefois que l'opérateur d'affectation ne peut être généré par le compilateur si la classe possède un membre
constant ou un membre référence. Le constructeur par copie implicite est bien généré mais pas l'opérateur =.
De façon analogue, si la classe possède un membre non copiable alors ni le constructeur par copie ni l'opérateur
d'affectation implicites ne peuvent être générés par le compilateur.
Peut-on autant dire que notre classe est copiable ? Non. Certes syntaxiquement un objet de cette classe peut être copié.
Mais il faut encore vérifier si sémantiquement cela a une cohérence et ne risque pas de poser de problème. Une syntaxe
correcte vous garantit que votre code compile, une sémantique bien définie assure une exécution maîtrisée.

lien : Toute classe doit-elle être copiable ?

Toute classe doit-elle être copiable ?


Auteurs : 3DArchi ,
Non. Par exemple, une classe à Quand est-ce qu'une classe a une sémantique de d'entité ? ne doit pas être recopiée.
De même certaines enveloppes RAII ( Comment gérer proprement des allocations / désallocations de ressources ? Le
RAII !) peuvent avoir une politique interdisant la copie (par exemple un ScopedMutex). Citons encore les singletons.

Comment rendre une classe non copiable ?


Auteurs : 3DArchi ,
Pour rendre une classe non copiable, il faut déclarer son constructeur par copie et son opérateur d'affectation en privé
sans les définir :

class non_copyable
{
public:
non_copyable(){}
//...
private:
non_copyable(non_copyable const &);// pas de définition !
non_copyable& operator=(non_copyable const &);// pas de définition !
};

Les rendre private empêche de pouvoir les appeler à l'extérieur de la classe. Le compilateur génère une erreur si on
tente de les appeler.

- 86 -
Les sources présentées sur cette pages sont libres de droits, et vous pouvez les utiliser à votre convenance. Par contre la page de présentation de ces sources
constitue une oeuvre intellectuelle protégée par les droits d'auteurs. Copyright © 2008 Developpez LLC. Tout droits réservés Developpez LLC. Aucune
reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents et images sans l'autorisation expresse de Developpez
LLC. Sinon vous encourez selon la loi jusqu'à 3 ans de prison et jusqu'à 300 000 E de dommages et intérêts.
http://cpp.developpez.com/faq/cpp/
Ne pas les définir empêche de les appeler à l'intérieur de la classe. L'erreur apparaît alors à l'édition de lien.

Que se passe-t-il pour les classes dérivées d'une classe non copiable ?
Auteurs : 3DArchi ,
Si la classe dérivée ne redéfinit pas son constructeur par copie et/ou son opérateur d'affectation, la propriété non
copiable est conservée :

class derived : private non_copyable


{
public:
derived(){}
};

Cependant, la propriété peut se perdre si vous redéfinissez le constructeur par copie ou l'opérateur d'affectation sans
faire appel à la méthode parente :

class derived : private non_copyable


{
public:
derived(){}
derived(derived const &){}
derived& operator=(derived const &)
{return *this;}
};

derived a;
derived b(a); // OK
derived c;
c = a;// OK

Le constructeur non_copyable appelé dans derived (derived const &) est ici le constructeur par défaut non_copyable().

Boost propose une classe boost::noncopyable avec un constructeur par copie et un opérateur d'affectation privés non
définis :

#include <boost/noncopyable.hpp>

class my_class_non_copyable : private boost::noncopyable


{
public:
my_class_non_copyable(){}
};

L'héritage boost::noncopyable doit être privé. Une première raison est que boost::noncopyable ne possède pas de
Pourquoi le destructeur d'une classe de base doit être public et virtuel ou protégé et non virtuel ? ! Ensuite, l'utilisation
polymorphe de boost::noncopyable n'a pas de sens. Enfin, boost::noncopyable ne proposant pas de membres utilitaires
qui peuvent être appelés par les classes dérivées, un héritage protected ne se justifie pas.

Quelle solution préférer (héritage ou redéfinition) pour rendre une classe non copiable ?
Auteurs : Luc Hermitte , 3DArchi ,
Nous avons donc deux solutions pour rendre une classe non copiable : déclarer son constructeur par copie et son
opérateur d'affectation sans les définir ou hériter d'une classe non copiable (par exemple, boost::noncopyable). La

- 87 -
Les sources présentées sur cette pages sont libres de droits, et vous pouvez les utiliser à votre convenance. Par contre la page de présentation de ces sources
constitue une oeuvre intellectuelle protégée par les droits d'auteurs. Copyright © 2008 Developpez LLC. Tout droits réservés Developpez LLC. Aucune
reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents et images sans l'autorisation expresse de Developpez
LLC. Sinon vous encourez selon la loi jusqu'à 3 ans de prison et jusqu'à 300 000 E de dommages et intérêts.
http://cpp.developpez.com/faq/cpp/
seconde approche est préférable, en particulier car les compilateurs peuvent avoir des comportements différents sur
des aspects particuliers. Par exemple, le SFINAE ( Qu'est-ce que SFINAE ?) dans le code suivant ne se comporte pas
de façon identique sur visual et gcc :

#include <iostream>

template <typename T1, typename T2>


struct CanConvert {
typedef char True;
class False { char dummy[2]; };
static True canConvert( T2 );
static False canConvert( ... );
static T1 makeT1( );
enum { r = sizeof(canConvert( makeT1( ) )) == sizeof(True) };
};

struct non_copyable {
non_copyable() {}
private:
non_copyable(non_copyable const&);
non_copyable& operator=(non_copyable const&);
};

struct S1
{
S1(int) {}
private:
S1(S1 const& );
S1& operator=(S1 const&);
};

struct S2
: private non_copyable
{
S2(int) {}
private:
};

int main (int argc, char **argv)


{
std::cout << CanConvert<char, S1>::r << std::endl;
std::cout << CanConvert<int*, S1>::r << std::endl;

std::cout << CanConvert<char, S2>::r << std::endl;


std::cout << CanConvert<int*, S2>::r << std::endl;

Comment rendre une classe non copiable en C++0x ?


Auteurs : Arzar ,
Le C++0x introduit un nouveau mécanisme facilitant l'écriture de classe non copiable : la suppression de fonction grâce
à la syntaxe "=delete".
Cette syntaxe permet de déclarer une fonction tout en supprimant sa définition. Elle peut s'appliquer aux fonctions
membres spéciales (constructeurs, constructeur par copie, destructeur, opérateurs), ainsi qu'aux fonctions membres et
aux fonctions libres # bref, à toutes les fonctions.
Pour rendre une classe non copiable, il faut supprimer son constructeur par copie et son opérateur d'affectation :

class non_copyable
{
public:

- 88 -
Les sources présentées sur cette pages sont libres de droits, et vous pouvez les utiliser à votre convenance. Par contre la page de présentation de ces sources
constitue une oeuvre intellectuelle protégée par les droits d'auteurs. Copyright © 2008 Developpez LLC. Tout droits réservés Developpez LLC. Aucune
reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents et images sans l'autorisation expresse de Developpez
LLC. Sinon vous encourez selon la loi jusqu'à 3 ans de prison et jusqu'à 300 000 E de dommages et intérêts.
http://cpp.developpez.com/faq/cpp/
// force le compilateur à générer la version par défaut du constructeur.
non_copyable() = default;

// suppression du constructeur par copie et de l'opérateur d'affectation


non_copyable(const non_copyable &) = delete;
non_copyable & operator= (const non_copyable &) = delete;
};

Cette approche possède quelques avantages par rapport à l'astuce utilisée en C++03 :

• La syntaxe pour supprimer une fonction est claire et non-ambigüe.


• L'appel à une fonction supprimée échoue à la compilation, jamais à l'édition des liens.
• Le message d'erreur est plus explicite et pointe directement sur la fonction supprimée.

Notez également l'utilisation de la syntaxe "=defaut" pour récupérer un constructeur par défaut généré
automatiquement par le compilateur.

Quel est l'équivalent de boost::noncopyable en C++0x ?


Auteurs : Arzar ,
A l'heure ou nous rédigeons cette FAQ, une proposition pour ajouter un utilitaire similaire à boost::noncopyable est
encore en cours de discussion dans le comité C++ ISO définissant le nouveau standard. Si cette proposition est avalisée,
il sera alors possible d'obtenir une classe non copiable en dérivant depuis std::noncopyable :

#include <utility> // pour bénificier de std::noncopyable


class my_class_non_copyable : private std::noncopyable
{
//...
};

Si cette proposition n'est pas votée, voici comment recréer cet utilitaire dans votre code :

struct noncopyable
{
noncopyable() = default;
noncopyable(const noncopyable&) = delete;
noncopyable& operator= (const noncopyable&) = delete;
};

Comme en C++03, il faut toutefois noter que la non copiabilité peut se perdre si vous redéfinissez le constructeur par
copie ou l'opérateur d'affectation dans la classe dérivée sans faire appel à la méthode parent.

- 89 -
Les sources présentées sur cette pages sont libres de droits, et vous pouvez les utiliser à votre convenance. Par contre la page de présentation de ces sources
constitue une oeuvre intellectuelle protégée par les droits d'auteurs. Copyright © 2008 Developpez LLC. Tout droits réservés Developpez LLC. Aucune
reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents et images sans l'autorisation expresse de Developpez
LLC. Sinon vous encourez selon la loi jusqu'à 3 ans de prison et jusqu'à 300 000 E de dommages et intérêts.
http://cpp.developpez.com/faq/cpp/
Sommaire > L'héritage
Pourquoi mettre en #uvre un héritage ?
Auteurs : 3DArchi ,
On trouve 2 sémantiques liées à l'héritage :

• EST-IMPLEMENTE-EN-TERMES-DE (IS-IMPLEMENTED-IN-TERM-OF) :
Ce type d'héritage permet à la classe dérivée de tirer profit de l'implémentation de la classe de base. Si B dérive de
A avec cette sémantique, alors toutes les fonctions de B peuvent appeler les fonctions de A. Le service apporté par
la classe A est disponible dans la classe B. Une alternative à l'héritage 'EST-IMPLEMENTE-EN-TERMES-DE'
est la composition. B possède un membre de type A et invoque ses fonctions au besoin. On peut préférer l'héritage
lorsque la classe de base est vide (sans attribut) et que l'on souhaite bénéficier de l'optimisation des classes de base
vides, ou lorsqu'il est nécessaire de redéfinir une fonction virtuelle de la classe de base. La composition permet
de varier l'implémentation plus facilement.
• EST-UN (IS-A) :
La sémantique de cet héritage découle de la définition du sous-type par Liskov et est donc intrinsèquement lié au
principe Qu'est-ce que le LSP ?.
Si le qualificatif 'EST-UN' est assez explicite, comprendre ses implications est parfois moins évident. "B dérive de
A" avec cette sémantique a diverses conséquences. D'abord, on dit que B est un sous-type de A. Ensuite, tout objet
de type A dans une expression valide peut être remplacé par un objet de type B : B doit garantir que l'expression
reste valide ET qu'elle possède la même sémantique. Enfin, définir un sous-type n'est pas définir un type plus
restrictif : B doit respecter tout ce que respecte A mais peut faire des choses en plus ou différemment.

A noter que les deux types d'héritage ne sont pas mutuellement exclusifs : B peut dériver de A à la fois car il EST-UN
A et à la fois car il EST-IMPLEMENTE-EN-TERMES-DE A.

lien : Qu'est-ce que le LSP ?

Quand dois-je faire un héritage public ? protégé ? privé ?


Auteurs : 3DArchi ,
• public : uniquement si l'héritage porte une sémantique EST-UN ;
• privé : lorsque l'héritage porte une sémantique EST-IMPLEMENTE-EN-TERMES-DE et ne supporte pas la
sémantique EST-UN ;
• protégé : si vous avez une bonne raison, la rédaction sera curieuse de la connaître.

L'héritage public est le plus problématique car il doit respecter le LSP et notamment ses implications en termes de
contrat.

lien : Qu'est-ce que le LSP ?


lien : Pourquoi mettre en #uvre un héritage ?

Qu'est-ce que le LSP ?


Auteurs : Davidbrcz ,
Le LSP (pour Liskov substitution principle) est un principe général de programmation s'énonçant de la façon suivante :

Partout où un objet x de type T est attendu, on doit pouvoir passer un objet y type U, avec U
héritant de T.

- 90 -
Les sources présentées sur cette pages sont libres de droits, et vous pouvez les utiliser à votre convenance. Par contre la page de présentation de ces sources
constitue une oeuvre intellectuelle protégée par les droits d'auteurs. Copyright © 2008 Developpez LLC. Tout droits réservés Developpez LLC. Aucune
reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents et images sans l'autorisation expresse de Developpez
LLC. Sinon vous encourez selon la loi jusqu'à 3 ans de prison et jusqu'à 300 000 E de dommages et intérêts.
http://cpp.developpez.com/faq/cpp/
En reformulant en français cette proposition, cela veut dire que l'on doit pouvoir remplacer les objets d'une classe T
par n'importe quel objet d'une sous-classe de T.
Comment cela se traduit-il sur le contrat de la classe ?
Sur les invariants des fonctions membres, les préconditions doivent être plus faibles et les postconditions doivent être
plus fortes.
En effet, l'héritage est l'exposition d'une interface que les sous-classes vont affiner. Dès lors, toutes les fonctions membres
d'une sous-classe doivent pouvoir travailler sur des objets acceptés selon l'interface de la classe parente et fournir un
résultat au moins aussi sûr que celui de la classe parente.
On peut faire une analogie avec un sous-traitant. Un sous-traitant doit accepter tous les travaux que vous acceptez (et
même plus s'il le veut) et doit rendre un travail au moins aussi sûr que le votre et même plus s'il le veut.
Sur les invariants de la classe, cela se taduit par le fait qu'une classe dérivée ne peut qu'ajouter des invariants à sa
définition.
En effet, l'héritage public est une relation EST-UN. Quand Y dérive de X, Y EST-UN X. Dès lors, il doit vérifier tous
les invariants de X plus ceux qui lui sont propres.
A ce titre examinons le code suivant :

class rectangle
{
protected:
double width;
double height;

public:
virtual void set_width(double x){width=x;}
virtual void set_height(double x){height=x;}
double area() const {return width*height;}

};

class square : public rectangle


{
/*L'invariant d'un carré est que à tout moment, width=height*/
void INVARIANT() {assert(width==height);}
public:
void set_width(double x){
INVARIANT();
rectangle::set_width(x);
rectangle::set_height(x);
INVARIANT();
}
void set_height(double x)
{
INVARIANT();
rectangle::set_width(x);
rectangle::set_height(x);
INVARIANT();
}
};

void foo(rectange& r)
{
r.set_height(4);
r.set_width(5);
assert(r.area() == 20);
}

Si nous passons un carré à foo, l'assertion est fausse, le contrat est rompu, le LSP est bafoué. Que s'est-il passé ?
Un carré est bien est un rectangle d'un point de vue mathématique mais pas sur le plan du comportement logiciel. Et
c'est ce qui importe dans la programmation. Le comportement d'un carré N'EST PAS identique à celui d'un rectangle.
On peut supposer que dans un rectangle, longeur et largeur vont varier indépendamment l'une de l'autre. Changer
l'une ne doit pas changer l'autre, ce qui est intrinsèquement faux pour un carré.
Le carré n'est pas substituable au rectangle, il ne devrait donc pas hériter de la classe rectangle.

- 91 -
Les sources présentées sur cette pages sont libres de droits, et vous pouvez les utiliser à votre convenance. Par contre la page de présentation de ces sources
constitue une oeuvre intellectuelle protégée par les droits d'auteurs. Copyright © 2008 Developpez LLC. Tout droits réservés Developpez LLC. Aucune
reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents et images sans l'autorisation expresse de Developpez
LLC. Sinon vous encourez selon la loi jusqu'à 3 ans de prison et jusqu'à 300 000 E de dommages et intérêts.
http://cpp.developpez.com/faq/cpp/
Il se passe le même problème entre une liste et une liste ordonée. Les deux classes n'ont pas les mêmes invariants pour
l'insertion.
Dans une liste, on peut insérer un objet où l'on veut, pas dans une liste triée. Des assertions valides pour la liste ne le
sont plus pour une liste triée.

lien : Pourquoi mettre en #uvre un héritage ?

Héritage EST-UN et programmation par contrat.


Auteurs : 3DArchi ,
La réponse est donnée par le Qu'est-ce que le LSP ? !

lien : Qu'est-ce que le LSP ?

Pourquoi le destructeur d'une classe de base doit être public et virtuel ou protégé et non virtuel ?
Auteurs : 3DArchi ,
C'est un peu la question symétrique de la sémantique d'héritage, vue de la classe de base.
Avoir un destructeur public signifie qu'un objet de type statique A peut être détruit alors que son type dynamique est
B, un sous-type de A :

class A
{// Déclaration de A...
};
class B : public A
{// Déclaration de B...
};

//...
// Quelque part dans le code :
{
std::auto_ptr<A> ptr_a(new B);
}// Destruction à partir du type statique A avec un type dynamique B !

Alors pour que cette Pourquoi et quand faut-il créer un destructeur virtuel ?, le destructeur de A doit être virtuel. A
partir du moment où une classe peut être dérivée et que vous ne savez pas à l'avance comment elle sera ensuite utilisée,
vous devez traiter ce problème. Or il n'existe que deux solutions :

• le destructeur est public et virtuel : vous autorisez sans risque qu'un objet dérivé soit détruit à partir d'une
variable de type statique de l'objet de base.
• le destructeur est protégé et non virtuel : le destructeur d'un objet de type de base ne peut être appelé que par
le destructeur du type dérivé. Plus de risque qu'un objet de type statique A tente de détruire un objet de type
dynamique B.

lien : Pourquoi et quand faut-il créer un destructeur virtuel ?

Qu'est-ce qu'une classe abstraite ?


Auteurs : LFE , Aurélien Regat-Barrel ,
Une classe abstraite est une classe qui possède au moins une fonction membre virtuelle pure (lire Qu'est-ce qu'une
fonction virtuelle pure ?). Cette fonction devant être supplantée, ce type de classe ne peut pas être instancié, et est donc

- 92 -
Les sources présentées sur cette pages sont libres de droits, et vous pouvez les utiliser à votre convenance. Par contre la page de présentation de ces sources
constitue une oeuvre intellectuelle protégée par les droits d'auteurs. Copyright © 2008 Developpez LLC. Tout droits réservés Developpez LLC. Aucune
reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents et images sans l'autorisation expresse de Developpez
LLC. Sinon vous encourez selon la loi jusqu'à 3 ans de prison et jusqu'à 300 000 E de dommages et intérêts.
http://cpp.developpez.com/faq/cpp/
destiné à être dérivé pour être spécialisé. La ou les classes filles doivent supplanter l'ensemble des fonctions virtuelles
pures de leurs parents. On dit alors que les classes filles concrétisent la classe abstraite.

class Bienvenue // classe abstraite


{
public:
// le "= 0" à la fin indique que c'est
// une fonction virtuelle pure
virtual void Message() = 0;
};

class BienvenueEnFrancais : public Bienvenue


{
public:
void Message()
{
std::cout << "Bienvenue !\n";
}
};

class BienvenueEnAnglais : public Bienvenue


{
public:
void Message()
{
std::cout << "Welcome !\n";
}
};

lien : Qu'est-ce qu'une fonction virtuelle pure ?

Qu'est-ce que l'héritage virtuel et quelle est son utilité ?


Auteurs : Davidbrcz ,
Le C++ est un langage qui autorise l'héritage multiple, c'est-à-dire qu'une classe peut avoir plus d'un parent. Exemple :

class A{};
class B{};
class C: public A,public B{};

Les classes parentes de C sont A et B.


Imaginons le cas suivant :

class V { protected:int i;/* ... */ };


class A : public V { /* ... */ };
class B : public V { /* ... */ };
class C : public A, public B { /* ... */ };

Le schéma d'héritage est le suivant :

- 93 -
Les sources présentées sur cette pages sont libres de droits, et vous pouvez les utiliser à votre convenance. Par contre la page de présentation de ces sources
constitue une oeuvre intellectuelle protégée par les droits d'auteurs. Copyright © 2008 Developpez LLC. Tout droits réservés Developpez LLC. Aucune
reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents et images sans l'autorisation expresse de Developpez
LLC. Sinon vous encourez selon la loi jusqu'à 3 ans de prison et jusqu'à 300 000 E de dommages et intérêts.
http://cpp.developpez.com/faq/cpp/
La classe A contient une variable membre i. Il en va de même pour la classe B. En ce qui concerne la classe C, elle
possède 2 variables membres i, l'une par l'héritage par A, l'autre selon l'héritage de B, c'est ce qui provoque une erreur
si on tente d'accéder à i dans C, le compilateur ne sait pas s'il doit regarder A::i ou B::i.
Pour vous convaincre de la présence de deux variables i dans la classe C, compilez le code suivant :

void C::f(){std::cout<<&(A::i)<<"/"<<&(B::i)<<std::endl;}

Dans bon nombre de cas, il va être génant (selon le point de vue de l'utilisation mémoire) d'avoir tous les membres
dédoublés.
La solution que propose le C++ est alors l'héritage virtuel. A hérite virtuellement de V et pour B il en va de même.
Exemple :

class V
{
protected:int i;
/* ... */
};
class A : virtual public V
{ /* ... */ };
class B : virtual public V
{ /* ... */ };
class C : public A, public B
{ /* ... */ };

Le fonctionnement de A ou B n'est pas changé, ils héritent toujours de V mais il n'y a plus qu'un seul objet V dans C.
Pour preuvre :

void C::f(){std::cout<<&(A::i)<<"/"<<&(B::i)<<std::endl;}

affiche deux fois la même chose et il n'y a plus d'ambiguité sur i. L'héritage est alors (on parle d'héritage en losange
ou en diamant):

- 94 -
Les sources présentées sur cette pages sont libres de droits, et vous pouvez les utiliser à votre convenance. Par contre la page de présentation de ces sources
constitue une oeuvre intellectuelle protégée par les droits d'auteurs. Copyright © 2008 Developpez LLC. Tout droits réservés Developpez LLC. Aucune
reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents et images sans l'autorisation expresse de Developpez
LLC. Sinon vous encourez selon la loi jusqu'à 3 ans de prison et jusqu'à 300 000 E de dommages et intérêts.
http://cpp.developpez.com/faq/cpp/
Notez par contre qu'on peut toujours introduire un nouvel objet V dans C de la façon suivante :

class C : public A, public B, public V


{ /* ... */ };

Dans ce cas, la structure de l'héritage est la suivant :

Dans quel ordre sont construits les différents composants d'une classe ?
Auteurs : 3DArchi ,
Les constructeurs sont appelés dans l'ordre suivant :

1 le constructeur des classes de base héritées virtuellement en profondeur croissante et de gauche à droite ;
2 le constructeur des classes de base héritées non virtuellement en profondeur croissante et de gauche à droite ;
3 le constructeur des membres dans l'ordre de leur déclaration ;
4 le constructeur de la classe.

#include <iostream>
#include <string>

struct MembreA{
MembreA(){std::cout<<"MembreA"<<std::endl;}
};

- 95 -
Les sources présentées sur cette pages sont libres de droits, et vous pouvez les utiliser à votre convenance. Par contre la page de présentation de ces sources
constitue une oeuvre intellectuelle protégée par les droits d'auteurs. Copyright © 2008 Developpez LLC. Tout droits réservés Developpez LLC. Aucune
reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents et images sans l'autorisation expresse de Developpez
LLC. Sinon vous encourez selon la loi jusqu'à 3 ans de prison et jusqu'à 300 000 E de dommages et intérêts.
http://cpp.developpez.com/faq/cpp/
struct A {
A(){std::cout<<"A"<<std::endl;}
MembreA m;
};
struct MembreB{
MembreB(){std::cout<<"MembreB"<<std::endl;}
};
struct B : A {
B(){std::cout<<"B"<<std::endl;}
MembreB m;
};
struct MembreC{
MembreC(){std::cout<<"MembreC"<<std::endl;}
};
struct C : A {
C(){std::cout<<"C"<<std::endl;}
MembreC m;
};
struct MembreD{
MembreD(){std::cout<<"MembreD"<<std::endl;}
};
struct D : B, C {D(){
std::cout<<"D"<<std::endl;}
MembreD m;
};
struct MembreE{MembreE(){
std::cout<<"MembreE"<<std::endl;}
};
struct E : virtual A {E(){
std::cout<<"E"<<std::endl;}
MembreE m;
};
struct MembreF{MembreF(){
std::cout<<"MembreF"<<std::endl;}
};
struct F : virtual A {
F(){std::cout<<"F"<<std::endl;}
MembreF m;
};
struct MembreG{
MembreG(){std::cout<<"MembreG"<<std::endl;}
};
struct G {
G(){std::cout<<"G"<<std::endl;}
MembreG m;
};
struct MembreH{
MembreH(){std::cout<<"MembreH"<<std::endl;}
};
struct H : G, F {
H(){std::cout<<"H"<<std::endl;}
MembreH m;
};
struct MembreI{
MembreI(){std::cout<<"MembreI"<<std::endl;}
};
struct I : E, G, F {
I(){std::cout<<"I"<<std::endl;}
MembreI m;
};

template<class T> void Creation()


{
std::cout<<"Creation d'un "<<typeid(T).name()<<" : "<<std::endl;
T t;
}

- 96 -
Les sources présentées sur cette pages sont libres de droits, et vous pouvez les utiliser à votre convenance. Par contre la page de présentation de ces sources
constitue une oeuvre intellectuelle protégée par les droits d'auteurs. Copyright © 2008 Developpez LLC. Tout droits réservés Developpez LLC. Aucune
reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents et images sans l'autorisation expresse de Developpez
LLC. Sinon vous encourez selon la loi jusqu'à 3 ans de prison et jusqu'à 300 000 E de dommages et intérêts.
http://cpp.developpez.com/faq/cpp/
int main()
{
Creation<A>();
Creation<B>();
Creation<C>();
Creation<D>();
Creation<E>();
Creation<F>();
Creation<G>();
Creation<H>();
Creation<I>();
return 0;
}

Ce code produit comme sortie :

Creation d'un struct A :


MembreA
A
Creation d'un struct B :
MembreA
A
MembreB
B
Creation d'un struct C :
MembreA
A
MembreC
C
Creation d'un struct D :
MembreA
A
MembreB
B
MembreA
A
MembreC
C
MembreD
D
Creation d'un struct E :
MembreA
A
MembreE
E
Creation d'un struct F :
MembreA
A
MembreF
F
Creation d'un struct G :
MembreG
G
Creation d'un struct H :
MembreA
A
MembreG
G
MembreF
F
MembreH
H
Creation d'un struct I :
MembreA
A

- 97 -
Les sources présentées sur cette pages sont libres de droits, et vous pouvez les utiliser à votre convenance. Par contre la page de présentation de ces sources
constitue une oeuvre intellectuelle protégée par les droits d'auteurs. Copyright © 2008 Developpez LLC. Tout droits réservés Developpez LLC. Aucune
reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents et images sans l'autorisation expresse de Developpez
LLC. Sinon vous encourez selon la loi jusqu'à 3 ans de prison et jusqu'à 300 000 E de dommages et intérêts.
http://cpp.developpez.com/faq/cpp/
MembreE
E
MembreG
G
MembreF
F
MembreI
I

Remarque 1 : La subtilité réside dans la primauté accordée à l'héritage virtuel sur l'héritage non virtuel.
Remarque 2 : L'ordre de construction est fixé par la norme et ne dépend pas des listes d'initialisation du code :

struct Membre1
{
Membre1(){std::cout<<"Membre1"<<std::endl;}
};
struct Membre2
{
Membre2(){std::cout<<"Membre2"<<std::endl;}
};
struct A {
A(){std::cout<<"A"<<std::endl;}
};
struct B {
B(){std::cout<<"B"<<std::endl;}
};
struct C : A,B {
C()
:m2(),B(),m1(),A()
{std::cout<<"C"<<std::endl;}
Membre1 m1;
Membre2 m2;
};
int main()
{
C c;
return 0;
}

Ce code produit :

A
B
Membre1
Membre2
C

Certains compilateurs peuvent sortir un avertissement lorsque la liste d'initialisation ne suit pas l'ordre de déclaration
mais ce n'est pas toujours le cas. Pour les listes d'initialisation, une bonne pratique est de toujours suivre l'ordre défini
par la norme pour éviter tout risque de confusion.
Remarque 3 : Pour l'héritage virtuel, le constructeur appelé est celui spécifié par le type effectivement instancié et non
par celui spécifié par le type demandant l'héritage. Si le type instancié ne spécifie pas de constructeur, alors c'est celui
par défaut :

struct A
{
A(std::string appelant_="defaut")
{

- 98 -
Les sources présentées sur cette pages sont libres de droits, et vous pouvez les utiliser à votre convenance. Par contre la page de présentation de ces sources
constitue une oeuvre intellectuelle protégée par les droits d'auteurs. Copyright © 2008 Developpez LLC. Tout droits réservés Developpez LLC. Aucune
reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents et images sans l'autorisation expresse de Developpez
LLC. Sinon vous encourez selon la loi jusqu'à 3 ans de prison et jusqu'à 300 000 E de dommages et intérêts.
http://cpp.developpez.com/faq/cpp/
std::cout<<"A construit par "<<appelant_<<std::endl;
}
};

struct B : virtual A
{
B()
:A("B")
{
}
};

struct C : B
{
C()
{
}
};

struct D : B
{
D()
:A("D")
{
}
};

template<class T> void Creation()


{
std::cout<<"Creation d'un "<<typeid(T).name()<<" : "<<std::endl;
T t;
}

int main()
{
Creation<B>();
Creation<C>();
Creation<D>();
return 0;
}

Ce code produit :

Creation d'un struct B :


A construit par B
Creation d'un struct C :
A construit par defaut
Creation d'un struct D :
A construit par D

Conclusion :
Le constructeur d'une classe doit monter sa liste d'initialisation suivant cet ordre :

1 les constructeurs des classes héritées virtuellement dans tout l'arbre d'héritage en profondeur croissante et de
gauche à droite ;
2 les constructeurs des classes de base directement héritées dans l'ordre de gauche à droite ;
3 les membres dans l'ordre de leur déclaration.

Ceci a comme conséquences :

• Ce sont d'éventuelles contraintes dans l'ordre de construction qui imposeront l'ordre d'héritage (et non des
approches de type d'abord le public, puis le privé).

- 99 -
Les sources présentées sur cette pages sont libres de droits, et vous pouvez les utiliser à votre convenance. Par contre la page de présentation de ces sources
constitue une oeuvre intellectuelle protégée par les droits d'auteurs. Copyright © 2008 Developpez LLC. Tout droits réservés Developpez LLC. Aucune
reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents et images sans l'autorisation expresse de Developpez
LLC. Sinon vous encourez selon la loi jusqu'à 3 ans de prison et jusqu'à 300 000 E de dommages et intérêts.
http://cpp.developpez.com/faq/cpp/
• Toute dépendance de construction entre les variables membres devra être explicitement commentée à défaut de
pouvoir être évitée. Par cette documentation, les lecteurs du code sont avertis qu'il s'agit d'un comportement
compris et maîtrisé par le développeur : la fiabilitié est accrue et la maintenance est facilitée.

Qu'est-ce que le polymorphisme ?


Auteurs : Jean-Marc.Bourguet , 3DArchi ,
Le polymorphisme, c'est la capacité d'une expression à être valide quand les valeurs présentes ont des types différents.
On trouve différents types de polymorphismes :

• ad-hoc : surcharge et coercition ;


• universel (ou non ad-hoc) : paramétrique et d'inclusion.

Ces deux distinctions segmentent le polymorphisme suivant l'axe de réutilisabilité face à un nouveau type : le
polymorphisme ad-hoc nécessite une nouvelle définition pour chaque nouveau type alors que le polymorphisme
universel recouvre un ensemble potentiellement illimité de types.

lien :
lien :
lien :
lien : Qu'est-ce que la coercition ?
lien : Qu'est-ce que la surcharge ?
lien : Qu'est-ce que le polymorphisme paramétrique ?
lien : Qu'est-ce que le polymorphisme d'inclusion ?

Mes fonctions virtuelles doivent-elles être publiques, protégées, ou privées ? Le pattern NVI.
Auteurs : 3DArchi ,
Vous avez sans doute déjà croisé des bibliothèques - ou en avez fait vous-même - proposant une interface à base de
fonctions virtuelles :

class IInterface
{
public :
virtual void Action(); // éventuellement pure (=0)
};

A charge pour le client de proposer une classe concrète spécialisant l'interface en redéfinissant ces fonctions virtuelles.
Le pattern NVI - Non Virtual Interface - propose une autre approche pour la définition de telles interfaces dont le
principe est : l'interface est proposée en fonction non virtuelle publique; la variabilité est encapsulée dans les fonctions
virtuelles privées :

class IInterface
{
public :
void Action();

private:

- 100 -
Les sources présentées sur cette pages sont libres de droits, et vous pouvez les utiliser à votre convenance. Par contre la page de présentation de ces sources
constitue une oeuvre intellectuelle protégée par les droits d'auteurs. Copyright © 2008 Developpez LLC. Tout droits réservés Developpez LLC. Aucune
reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents et images sans l'autorisation expresse de Developpez
LLC. Sinon vous encourez selon la loi jusqu'à 3 ans de prison et jusqu'à 300 000 E de dommages et intérêts.
http://cpp.developpez.com/faq/cpp/
virtual void DoAction(); // =0
};

Action appelle DoAction à un moment pour rendre le service attendu.


Une fonction virtuelle privée ne peut être appelée par les classes dérivées. En revanche, une classe dérivée peut redéfinir
la fonction pour adapter le comportement. La nécessité par la classe dérivée d'appeler l'implémentation de la classe
parente est de part le pattern assez exceptionnelle. C'est pourquoi la fonction est privée et non protégée. Dans les cas
exceptionnels où la classe de base peut proposer un comportement intéressant pour les classes dérivées, DoAction peut
être protected.
La réponse à notre question première, "Mes fonctions virtuelles doivent-elles être publiques, protégées, ou privées ?",
devient avec ce pattern :

• publique : jamais ;
• protégées : exceptionnellement ;
• privées : par défaut.

Ceci ne concerne pas le destructeur dont la problématique est envisagée dans une autre question (cf Pourquoi le
destructeur d'une classe de base doit être public et virtuel ou protégé et non virtuel ?).
"Quel est l'intérêt ?" est la première question qui se pose !
D'abord, il faut comprendre que la définition d'une interface s'adresse en fait à deux interlocuteurs bien distincts : la
classe client qui utilise IInterface pour son service Action et la classe concrète qui dérive de IInterface et réalise Action
en la redéfinissant. On voit donc par là qu'avec la première approche, Action se voit conférer deux rôles distincts.
Séparer les deux fonctions permet ainsi d'indiquer clairement à chacun des deux interlocuteurs sa responsabilité.
Il faut se souvenir qu'une bonne conception ne donne qu'une responsabilité bien définie à un élément. Octroyer de
multiples responsabilités est souvent source de rigidité (problème de réutilisabilité), de confusion (pensez toujours à la
maintenance) et de bugs (maîtrise de la complexité).
Dans une approche par contrat, IInterface passe un contrat avec le client sur la fonction Action : le client assure les
préconditions s'il veut obtenir les postconditions. Et réciproquement, IInterface assure les préconditions de DoAction
pour obtenir les postconditions :

void IInterface::Action()
{
// mise en place des préconditions de DoAction
// éventuellement en mode debug, tests des préconditions
DoAction();
// éventuellement en mode debug, tests des postconditions
// traitements supplémentaires si besoin pour garantir les postconditions de Action()
}

En fait, ce découpage assure que les préconditions et postconditions pour Action ne peuvent être dévoyées par la classe
dérivée. En effet, le client s'adresse toujours à la fonction de la classe de base, et a donc comme contrat celui proposé
par IInterface. La classe dérivée ne passe contrat qu'avec la classe mère via DoAction. Le développeur d'une classe
dérivée ne peut pas modifier ces pré/postconditions offertes au client puisqu'elles restent garanties par la classe de base
IInterface::Action.
En assignant à chacun sa responsabilité, cette séparation accroit la souplesse de l'interface face aux évolutions :

• Le service offert au client via Action peut évoluer de façon plus lâche par rapport à l'implémentation proposée
par la classe concrète via DoAction.
• L'implémentation de DoAction dans la classe concrète peut évoluer en réduisant les impacts sur les clients de
Action. Les détails d'implémentation peuvent évoluer par la spécialisation ou par la mise en oeuvre d'autres
mécanismes (pimpl idiom, patron de conception pont, etc.) sans que cela n'affecte le client.

Cette séparation crée aussi un endroit idéal pour l'instrumentation d'un code. La fonction non virtuelle Action peut
accueillir les demandes de trace, peut surveiller les performances, etc. En mode debug, Action peut aussi tester les
invariants, s'assurer des préconditions et garantir les postconditions.

- 101 -
Les sources présentées sur cette pages sont libres de droits, et vous pouvez les utiliser à votre convenance. Par contre la page de présentation de ces sources
constitue une oeuvre intellectuelle protégée par les droits d'auteurs. Copyright © 2008 Developpez LLC. Tout droits réservés Developpez LLC. Aucune
reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents et images sans l'autorisation expresse de Developpez
LLC. Sinon vous encourez selon la loi jusqu'à 3 ans de prison et jusqu'à 300 000 E de dommages et intérêts.
http://cpp.developpez.com/faq/cpp/
void IInterface::Action()
{
Log::Report("Entrée IInterface::Action");
Duree temps(Duree::maintenant);
DoAction1();
temps.Stop();
Log::Report("Temps d'exécution : ", temps.LireDuree());
}

De façon connexe, le patron de conception Patron de méthode (design pattern template) s'appuie sur les fonctions
virtuelles pour paramétrer un comportement. De façon encore plus évidente que pour une interface, les fonctions
virtuelles doivent se trouver en zone privée. Ici, ces fonctions ne relèvent pas du contrat de la classe avec son client mais
bien des détails d'implémentation.

void IInterface::Action()
{
// Première partie de traitements
DoAction1();
// Traitements suivants
DoAction2();
// Traitements suivants..
DoAction3();
// fin des traitements
}

avec alors :

class IInterface
{
public :
void Action();

private:
virtual void DoAction1(); // =0
virtual void DoAction2(); // =0
virtual void DoAction3(); // =0
};

lien : Virtuality, de Herb Sutter (gotw)


lien : Pourquoi le destructeur d'une classe de base doit être public et virtuel ou protégé et non virtuel ?

Comment varier le comportement au moment de l'exécution par le polymorphisme d'inclusion ?


Auteurs : Alp Mestan , 3DArchi ,
En C++, on utilise souvent l'héritage pour ce faire. En effet, imaginez que nous soyons en présence d'une hiérarchie
de composants graphiques, dont la classe de base serait Widget. On aurait ainsi Button et Textfield qui hériteraient de
Widget par exemple. Enfin, chacun possèderait une méthode show() qui permet d'afficher le composant en question.
Bien entendu, un Button et un Textfield étant de natures différentes, leur affichage le serait aussi. C'est grâce au
polymorphisme d'héritage, mis en oeuvre en C++ grâce au mot clé virtual, que l'on peut réaliser cela dynamiquement :
à l'exécution du programme, il sera choisi d'utiliser la méthode Button::show() ou la méthode Textfield::show() selon
le type réel de l'objet sur lequel on appelle show(). Voici un exemple minimal illustrant cela.

class Widget
{
public:
virtual ~Widget() { /* ... */ }

- 102 -
Les sources présentées sur cette pages sont libres de droits, et vous pouvez les utiliser à votre convenance. Par contre la page de présentation de ces sources
constitue une oeuvre intellectuelle protégée par les droits d'auteurs. Copyright © 2008 Developpez LLC. Tout droits réservés Developpez LLC. Aucune
reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents et images sans l'autorisation expresse de Developpez
LLC. Sinon vous encourez selon la loi jusqu'à 3 ans de prison et jusqu'à 300 000 E de dommages et intérêts.
http://cpp.developpez.com/faq/cpp/
void show()
{
// ...
do_show();
// ...
}
// ...

private :
virtual void do_show()=0; // fonction virtuelle pure
};

class Button : public Widget


{
private :
virtual void do_show() { std::cout << "Button" << std::endl; }
// ...
};

class Textfield : public Widget


{
private :
virtual void do_show() { std::cout << "Textfield" << std::endl; }
// ...
};

void show_widget(Widget& w)
{
w.show();
}

// ...

Button b;
Textfield t;

show_widget(b); // affiche "Button"


show_widget(t); // affiche "Textfield"

Dans ce cas, rien à redire, vous avez fait un choix correct.

Puis-je appeler des fonctions virtuelles dans le constructeur (ou le destructeur) ?


Auteurs : JolyLoic , Laurent Gomila ,
Oui, c'est possible, mais attention, ça ne fait pas toujours ce qu'on pense.
La première approche consiste à comprendre que lors de l'appel du constructeur d'une classe de base, la classe dérivée
n'a pas encore été construite. Donc, c'est la méthode spécialisée à ce niveau qui est appelée.
Voyons en détail :
La règle est que le type dynamique d'une variable en cours de construction est celui du constructeur qui est en train
d'être exécuté. Pour bien comprendre ce qui se passe, il faut donc revenir sur la différence entre le type statique d'une
variable, et son type dynamique.

Prenons par exemple trois classes, C qui dérive de B qui dérive de A. Par exemple, dans :

A* a = new B();

La variable a possède comme type statique (son type déclaré dans le programme) A*. Par contre, son type dynamique
est B*. Une fonction virtuelle est simplement une fonction dont on va chercher le code en utilisant le type dynamique
de la variable, au lieu de son type statique, comme une fonction classique.

- 103 -
Les sources présentées sur cette pages sont libres de droits, et vous pouvez les utiliser à votre convenance. Par contre la page de présentation de ces sources
constitue une oeuvre intellectuelle protégée par les droits d'auteurs. Copyright © 2008 Developpez LLC. Tout droits réservés Developpez LLC. Aucune
reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents et images sans l'autorisation expresse de Developpez
LLC. Sinon vous encourez selon la loi jusqu'à 3 ans de prison et jusqu'à 300 000 E de dommages et intérêts.
http://cpp.developpez.com/faq/cpp/
Maintenant, quand on crée un objet de type C, les choses se passent ainsi :

• On alloue assez de mémoire pour un objet de la taille de C.


• On initialise la sous partie correspondant à A de l'objet.
• On appelle le corps du constructeur de A. Pendant cet appel, l'objet crée a pour type dynamique A.
• On initialise la sous partie correspondant à B de l'objet.
• On appelle le corps du constructeur de B. Pendant cet appel, l'objet crée a pour type dynamique B.
• On initialise la sous partie correspondant à C de l'objet.
• On appelle le corps du constructeur de C. Pendant cet appel, l'objet crée a pour type dynamique C.

Donc, dans le corps du constructeur de la classe B, un appel d'une fonction virtuelle appellera la version de la fonction
définie dans la classe B (ou à défaut celle définie dans A si la fonction n'a pas été définie dans B), et non pas celle définie
dans la classe C.

D'ailleurs, si la fonction est virtuelle pure dans B, ça causera quelques problèmes, puisqu'on tentera alors d'appeler
une fonction qui n'existe pas. En général, le programme va planter, si on a de la chance, il affichera une message du
style "Pure function called".

La problèmatique est exactement la même pour les destructeurs, mais dans l'ordre inverse.

Pourquoi cette règle ? Une fonction définie dans C a accès aux données membre de C. Or, on a vu que au moment où
on exécute l'appel au corps du constructeur de B, ces dernières ne sont pas encore créées. On a donc préféré jouer la
sécurité.

lien : Dans quel ordre sont construits les différents composants d'une classe ?

Que sont le typage statique et le typage dynamique ?


Question subsidiaire : qu'est-ce que l'inférence de type ?
Auteurs : Davidbrcz ,
Le C++ est un langage typé, c'est-à-dire que toute variable possède au moment de sa définition un type connu par le
compilateur : c'est le typage statique. Mais le type réel de l'objet peut être différent de son type statique. C'est ce qui
se passe lors du polymorphisme dynamique.

class A{};
class B:public A {};

A* ptr= new B; // le type statique l'objet est A mais son type dynamique est B

Le type statique est l'interface par laquelle on manipule l'objet réellement derrière ce type. Pour déterminer ce type,
vous pouvez regarder du coté de typeid et typeinfo, bien que souvent, avoir besoin de connaître le type réel de l'objet
est signe d'une conception bancale.
Regardons la différence entre le typage statique/dynamique et le typage faible qui est présent dans bien des langages.

x = 10;
x = "Hello World";

La première instruction va assigner la valeur 10 à la variable x. La pensée de l'interpréteur est simpliste : "La variable
contient un numérique, elle doit donc se comporter comme un nombre numérique". La seconde va lui assigner la chaîne
"Hello World", x va donc se comporter comme une chaîne de caractères.

- 104 -
Les sources présentées sur cette pages sont libres de droits, et vous pouvez les utiliser à votre convenance. Par contre la page de présentation de ces sources
constitue une oeuvre intellectuelle protégée par les droits d'auteurs. Copyright © 2008 Developpez LLC. Tout droits réservés Developpez LLC. Aucune
reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents et images sans l'autorisation expresse de Developpez
LLC. Sinon vous encourez selon la loi jusqu'à 3 ans de prison et jusqu'à 300 000 E de dommages et intérêts.
http://cpp.developpez.com/faq/cpp/
Dans les deux cas, x n'est ni de type numérique ni de type chaîne de caractères, c'est une juste variable qui contient une
valeur se comportant d'une certaine façon. C'est le typage faible.
L'inférence de type est autre chose. Elle consiste à détecter automatiquement le type statique d'une variable, sans que
celui-ci ne soit explicité dans le code source via le mot clé auto.
Néanmoins, cette détection possède ses propres limites, le type assigné va au plus simple possible :

auto
s="blabla"; //ici s est de type const char* alors qu'on aurait voulu l'avoir de type std::string

lien : Qu'est-ce que le LSP ?

- 105 -
Les sources présentées sur cette pages sont libres de droits, et vous pouvez les utiliser à votre convenance. Par contre la page de présentation de ces sources
constitue une oeuvre intellectuelle protégée par les droits d'auteurs. Copyright © 2008 Developpez LLC. Tout droits réservés Developpez LLC. Aucune
reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents et images sans l'autorisation expresse de Developpez
LLC. Sinon vous encourez selon la loi jusqu'à 3 ans de prison et jusqu'à 300 000 E de dommages et intérêts.
http://cpp.developpez.com/faq/cpp/
Sommaire > Les fonctions
Pourquoi certaines fonctions membres possèdent le mot clé const après leur nom ?
Auteurs : Aurélien Regat-Barrel ,
Quand une fonction membre (non statique) d'une classe ne modifie pas cette dernière, il est judicieux en C++ de la
rendre constante en ajoutant le mot-clé const à la fin de son prototype. Cela rappelle que cette fonction ne modifie et
ne doit pas modifier l'objet ce qui permet de l'utiliser sur des objets constants en plus d'aider le compilateur à effectuer
des optimisations.

class Test
{
public:
std::string F() const
{
return "F() const";
}

std::string F()
{
return "F()";
}
};

int main()
{
Test t1;
cout << t1.F() << '\n'; // affiche "F()"

const Test & t2 = t1;


cout << t2.F() << '\n'; // affiche "F() const"
}

Dans l'exemple précédent, si la fonction membre F() const n'existait pas, on n'aurait pas pu appeler F() sur l'objet t2.
Notez que le fait d'avoir rajouté le mot clé const a provoqué une surcharge de la fonction F() au même titre qu'une
surcharge effectuée avec un nombre de paramètres différents.

Quelle est la particularité d'une fonction membre static ?


Auteurs : LFE , Aurélien Regat-Barrel ,
Une fonction membre déclarée static a la particularité de pouvoir être appelée sans devoir instancier la classe.
Elle ne peut utiliser que des variables et des fonctions membres static elles aussi, c'est-à-dire qui ont une existence en
dehors de toute instance.

class A
{
public:
// variable et fonction non statiques
int var1;
void f1() {};
// variable et fonction statiques
static int var2;
static void f2() {};
};

// IMPORTANT : il faut définir la variable static


int A::var2 = 0;

int main()

- 106 -
Les sources présentées sur cette pages sont libres de droits, et vous pouvez les utiliser à votre convenance. Par contre la page de présentation de ces sources
constitue une oeuvre intellectuelle protégée par les droits d'auteurs. Copyright © 2008 Developpez LLC. Tout droits réservés Developpez LLC. Aucune
reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents et images sans l'autorisation expresse de Developpez
LLC. Sinon vous encourez selon la loi jusqu'à 3 ans de prison et jusqu'à 300 000 E de dommages et intérêts.
http://cpp.developpez.com/faq/cpp/
{
A a; // instance de A
// var1 et f1 nécessitent une instance de A
a.var1 = 1;
a.f1();

// var2 et f2 sont static et n'ont pas besoin d'instance


A::var2 = 1;
A::f2();
}

Qu'est-ce que le masquage de fonction ?


Auteurs : Laurent Gomila ,
On parle de masquage de fonction lorsqu'on définit dans une classe dérivée une fonction de même nom qu'une fonction
d'une classe de base, mais avec un prototype différent. Voici un exemple qui illustre ce problème :

#include <iostream>
#include <string>

struct Base
{
void F(int);
};

struct Derivee : Base


{
void F(std::string);
};

Derivee d;
d.F("salut"); // Ok : appelle Derivee::F
d.F(5); // Erreur : aucune fonction "F" prenant un int

Dans cet exemple, la fonction F de la classe de base n'est non pas surchargée mais masquée, ce qui signifie qu'elle n'est
plus accessible dans la classe dérivée. Pour palier ce problème il suffit d'utiliser la directive using pour réimporter la
fonction masquée dans la portée de la classe dérivée :

struct Derivee : Base


{
using Base::F;

void F(std::string s);


};

Derivee d;
d.F("salut"); // Ok : appelle Derivee::F
d.F(5); // Ok : appelle Base::F

On peut également régler le problème en spécifiant explicitement lors de l'appel d'où vient la fonction que l'on souhaite
utiliser :

Derivee d;

- 107 -
Les sources présentées sur cette pages sont libres de droits, et vous pouvez les utiliser à votre convenance. Par contre la page de présentation de ces sources
constitue une oeuvre intellectuelle protégée par les droits d'auteurs. Copyright © 2008 Developpez LLC. Tout droits réservés Developpez LLC. Aucune
reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents et images sans l'autorisation expresse de Developpez
LLC. Sinon vous encourez selon la loi jusqu'à 3 ans de prison et jusqu'à 300 000 E de dommages et intérêts.
http://cpp.developpez.com/faq/cpp/
d.Base::F(5); // Ok : appelle Base::F

Pourquoi ne peut-on pas passer une fonction membre là où on attend un pointeur de fonction ?
Auteurs : Laurent Gomila ,
En C++, il est possible de passer des pointeurs de fonctions en paramètre d'autres fonctions. Mais peut-être aurez-vous
remarqué que le compilateur râle parfois lorsque vous essayez de passer un pointeur sur fonction membre. Voici un
exemple courant, la création de threads (sous Windows) :

DWORD WINAPI Fonction1( void *Param ) // Fonction globale


{
return 0;
}

DWORD WINAPI MaClasse::Fonction2( void *Param ) // Fonction membre de MaClasse


{
return 0;
}

MaClasse Param;
CreateThread( NULL, 0, Fonction1, &Param, 0, NULL ); // OK
CreateThread( NULL, 0, &MaClasse::Fonction2, &Param, 0, NULL ); // Erreur !

Pourquoi ce code ne compile pas avec une fonction membre ? Parce que le type de Fonction1 et MaClasse::Fonction2
n'est pas le même. La fonction globale Fonction1 a pour type DWORD (*)(void*).
La fonction membre Fonction2 a pour type DWORD (MaClasse::*)(void*).
On comprend facilement cette différence, étant donné que Fonction2 aura besoin d'une instance de MaClasse pour être
appelée, au contraire de Fonction1 qui pourra être appelée "librement". A noter que le type des fonctions membres
statiques peut être assimilé à celui des fonctions globales, puisque celles-ci peuvent être également appelées sans instance
de la classe. Ainsi pour contourner le problème, il faudrait (par exemple) procéder ainsi :

class MaClasse
{
public :

static DWORD WINAPI StaticThreadFunc( void *Param )


{
MaClasse* Obj = reinterpret_cast<MaClasse*>( Param );
return Obj->ThreadFunc();
}

private :

DWORD ThreadFunc()
{
// ...
return 0;
}
};

MaClasse Param;
CreateThread( NULL, 0, &MaClasse::StaticThreadFunc, &Param, 0, NULL );

- 108 -
Les sources présentées sur cette pages sont libres de droits, et vous pouvez les utiliser à votre convenance. Par contre la page de présentation de ces sources
constitue une oeuvre intellectuelle protégée par les droits d'auteurs. Copyright © 2008 Developpez LLC. Tout droits réservés Developpez LLC. Aucune
reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents et images sans l'autorisation expresse de Developpez
LLC. Sinon vous encourez selon la loi jusqu'à 3 ans de prison et jusqu'à 300 000 E de dommages et intérêts.
http://cpp.developpez.com/faq/cpp/
A noter qu'on peut tout à fait demander à une fonction de recevoir comme paramètre un pointeur sur fonction membre,
il suffit d'indiquer le bon type, comme expliqué ci-dessus.

Quel est l'équivalent C++ des paramètres variables ?


Auteurs : Laurent Gomila ,
En C, il est possible de déclarer une fonction acceptant un nombre de paramètres variables via "..." (c'est ce qu'on
appelle l'ellipse). L'exemple le plus connu est celui de la fonction d'affichage printf.

En C++ il est bien entendu toujours possible d'utiliser cette méthode mais il y a mieux : le chaînage d'appels. Les
avantages sont multiples :

• Typage beaucoup plus fort.


• Pas besoin de manipuler des macros bizarroïdes pour récupérer les paramètres.
• Pas besoin d'indication supplémentaire pour marquer le nombre et le type des paramètres.
• Beaucoup plus simple à écrire, et plus flexible.

Cette méthode est intensivement utilisée par exemple pour manipuler les flux standards :

#include <iostream>

std::cout << x << y << z;

On peut également imaginer d'autres formes de chaînages pour d'autres applications :

#include <vector>

class Polygon
{
pulic :

Polygon& Add(int x, int y)


{
Points_.push_back(Point(x, y));

return *this;
}

private :

std::vector<Point> Points_;
};

Polygon Poly;
Poly.Add(1, 2)
.Add(5, 8)
.Add(15, 19)
.Add(0, 54);

#include <sstream>
#include <string>

class StringBuilder
{
pulic :

- 109 -
Les sources présentées sur cette pages sont libres de droits, et vous pouvez les utiliser à votre convenance. Par contre la page de présentation de ces sources
constitue une oeuvre intellectuelle protégée par les droits d'auteurs. Copyright © 2008 Developpez LLC. Tout droits réservés Developpez LLC. Aucune
reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents et images sans l'autorisation expresse de Developpez
LLC. Sinon vous encourez selon la loi jusqu'à 3 ans de prison et jusqu'à 300 000 E de dommages et intérêts.
http://cpp.developpez.com/faq/cpp/
template <typename T>
StringBuilder& operator ()(const T& t)
{
std::ostringstream oss;
oss << t;
String_ += oss.str();

return *this;
}

private :

std::string String_;
};

StringBuilder s;
s("salut j'ai ")(24)(" ans ");

Comme vous le voyez, ce qui rend possible le chaînage est le renvoi de l'instance courante par la fonction. Ainsi,
Poly.Add(x, y) renvoie Poly, sur lequel on peut de nouveau appeler Add etc...

Comment passer correctement des paramètres à ma fonction ?


Auteurs : Laurent Gomila ,
Par défaut les paramètres de fonctions sont passés par valeur, c'est-à-dire que c'est une copie du paramètre passé qui
est manipulée par la fonction et non l'original. Cela peut paraître anodin lorsqu'il s'agit de passer un type de base, mais
cela devient vite pénalisant lorsqu'il s'agit d'une instance de classe dont la copie peut s'avérer coûteuse (par exemple un
vector de string). Cela peut également être un problème si l'on souhaite passer en paramètre une classe qui n'est tout
simplement pas copiable (par exemple un flux standard). Pour régler le problème, on utilise ainsi ce qu'on appelle le
passage par référence ou par référence constante. En passant une référence, on s'assure que c'est l'objet initial qui est
manipulé dans la fonction et donc qu'aucune recopie indésirable n'est effectuée. En passant une référence constante,
on s'assure également que notre paramètre ne pourra pas être modifié par la fonction. Une bonne habitude est donc de
prendre tout paramètre non modifiable par référence constante, excepté les types primitifs. D'autant plus que cela n'a
strictement aucune autre conséquence, ni au niveau de la fonction ni au niveau de l'appelant.

#include <string>
#include <vector>

void FonctionPasOptimisee(std::vector<std::string> Tab)


{
// ...
}

void FonctionOptimisee(const std::vector<std::string>& Tab)


{
// ...
}

std::vector<std::string> v(5000, std::string(1000, '-')); // Tableau de 5000 chaînes de 1000 caractères

FonctionPasOptimisee(v); // recopie inutilement nos 5 millions de caractères


FonctionOptimisee(v); // ne recopie rien du tout

Attention à ne pas oublier le const si le paramètre n'est pas modifié dans la fonction : cela permet en effet de passer
ce que l'on appelle des temporaires non nommés.

- 110 -
Les sources présentées sur cette pages sont libres de droits, et vous pouvez les utiliser à votre convenance. Par contre la page de présentation de ces sources
constitue une oeuvre intellectuelle protégée par les droits d'auteurs. Copyright © 2008 Developpez LLC. Tout droits réservés Developpez LLC. Aucune
reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents et images sans l'autorisation expresse de Developpez
LLC. Sinon vous encourez selon la loi jusqu'à 3 ans de prison et jusqu'à 300 000 E de dommages et intérêts.
http://cpp.developpez.com/faq/cpp/
#include <string>
#include <vector>

void FonctionIncorrecte(std::string& s)
{
// ...
}

void FonctionCorrecte(const std::string& s)


{
// ...
}

std::string s1 = "salut";
std::string s2 = "hello";

FonctionIncorrecte(s1); // Ok
FonctionCorrecte(s1); // Ok

FonctionIncorrecte(s1 + s2); // Erreur : s1 + s2 est un temporaire


FonctionCorrecte(s1 + s2); // Ok

FonctionIncorrecte("bonjour"); // Erreur : "bonjour" est un temporaire


FonctionCorrecte("bonjour"); // Ok

Cette remarque vaut également pour les pointeurs :

void FonctionIncorrecte(char* s)
{
// ...
}

void FonctionCorrecte(const char* s)


{
// ...
}

FonctionIncorrecte("bonjour"); // Erreur : "bonjour" est un temporaire


FonctionCorrecte("bonjour"); // Ok

La fonction de ma classe entre en conflit avec une fonction standard, que faire ?
Auteurs : LFE ,
Définir dans une classe une fonction membre qui a le même nom qu'une fonction standard est possible, mais risque
de poser problème lors de l'utilisation de cette fonction membre à l'intérieur de la classe. La fonction membre masque
la fonction standard.
Il reste toutefois possible d'utiliser la fonction standard en faisant précéder son nom de l'opérateur de résolution de
portée ::.

class MaClasse
{
int abs(int x); // masque la fonction standard abs
}

int MaClasse::abs(int x)
{
return ::abs(x); // fait appel à la fonction standard abs()

- 111 -
Les sources présentées sur cette pages sont libres de droits, et vous pouvez les utiliser à votre convenance. Par contre la page de présentation de ces sources
constitue une oeuvre intellectuelle protégée par les droits d'auteurs. Copyright © 2008 Developpez LLC. Tout droits réservés Developpez LLC. Aucune
reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents et images sans l'autorisation expresse de Developpez
LLC. Sinon vous encourez selon la loi jusqu'à 3 ans de prison et jusqu'à 300 000 E de dommages et intérêts.
http://cpp.developpez.com/faq/cpp/
}

Quelles précautions faut-il prendre avec les fonctions callback ?


Auteurs : Aurélien Regat-Barrel ,
En C++, en général, on évite d'utiliser les fonctions callback au profit d'alternatives un peu plus "objet" (tel les Qu'est-
ce qu'un foncteur ? par exemple). Cependant, on est parfois obligé d'y recourir, typiquement pour s'interfacer avec
une autre bibliothèque écrite en C. Si tel est votre cas, il vous faut alors être prudent et veiller à ce que votre fonction
callback C++ passée à la bibliothèque C ne lève pas d'exception, surtout si vous l'utilisez déja compilée (dll). La raison
est que les exceptions C++ ne sont pas supportée en C, et les conséquences d'une exception levée dans votre callback C+
+ et remontant jusqu'au code C appelant peuvent étre fâcheuses. Et d'une manière plus générale, les exceptions posent
problème dès qu'il s'agit de franchir les limites d'un module compilé (telle une dll), même entre differents modules
développés en C++ (ABI incompatibles).

On peut toutefois préciser que sous Windows, un système d'exceptions (Structured Exception Handling, ou SEH) est
integré au sein meme du système. Certains compilateurs l'exploitent, y compris en langage C (au moyen de mots clés
spécifiques), ce qui permet à du code C d'être traversé sans problème par des exceptions lancées depuis un code C+
+, si celles-ci ont été émises sous forme de SEH. Certains compilateurs s'appuient sur SEH pour implementer leurs
exceptions C++ (c'est le cas de Visual C++ par exemple), les rendant ainsi compatibles avec n'importe quel autre code
compilé. Consultez la documentation de votre compilateur pour plus de détails.

Pourquoi ne faut-il qu'un seul return par fonction ?


Auteurs : white_tentacle ,
Parce qu'on vous a menti !
Cette pratique provient d'anciens langages de programmation, qui ne disposaient pas de mécanismes élégants pour la
gestion de la mémoire. Ainsi, on n'avait coutume d'écrire des choses qui ressemblent à ça :

int f()
{
char * my_string = (char*) malloc(20 * sizeof(char) );
int result;
/* début du traitement complexe */
if(error)
{
result = -1;
goto end_f;
}
/* fin du traitement complexe */
:end_f
free(my_string);
return result;
}

Ceci permettait de gérer toute la libération mémoire dans un seul bloc de code. Mais pour passer dans ce bloc de code
à coup sûr, il était nécessaire d'utiliser, soit des imbrications de if à n'en plus finir, soit des gotos. Les return multiples
étaient sources de nombreuses fuites mémoires, difficiles à diagnostiquer.
Heureusement, C++ possède un mécanisme très puissant, le destructeur. Ainsi, le code précédent s'écrira :

int f()
{

- 112 -
Les sources présentées sur cette pages sont libres de droits, et vous pouvez les utiliser à votre convenance. Par contre la page de présentation de ces sources
constitue une oeuvre intellectuelle protégée par les droits d'auteurs. Copyright © 2008 Developpez LLC. Tout droits réservés Developpez LLC. Aucune
reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents et images sans l'autorisation expresse de Developpez
LLC. Sinon vous encourez selon la loi jusqu'à 3 ans de prison et jusqu'à 300 000 E de dommages et intérêts.
http://cpp.developpez.com/faq/cpp/
std::string my_string;
/* début du traitement complexe */
if(error)
return -1;
/* fin du traitement complexe, qui contient maintenant le return */
}

Plus besoin de gérer l'allocation mémoire, elle sera libérée par le destructeur de std::string. Et si l'on est obligé d'allouer
avec new, C++ nous offre là encore un moyen simple de gérer celà, std::auto_ptr :

int f()
{
std::auto_ptr<Obj> local_object = new CMyClass();
// reste de la fonction
}

local_object sera libéré (appel de delete) dès que l'on sort de la portée de la variable.
Et pour les objets alloués avec new[] ? C++ ne fournit pas en standard de moyen... sauf d'utiliser plutôt std::vector ! Et
comme souvent, si ce n'est pas possible, Boost vient à la rescousse avec boost::scoped_array.
Et pour les objets dont l'allocation est faite par une fonction d'une bibliothèque, à libérer en appelant une autre fonction
de cette bibliothèque ? C++ nous offre la possibilité d'utiliser des scope_guard, qui appelleront automatiquement la
fonction de libération lors de leur destruction.
Il n'y a donc de nos jours plus aucune raison de se priver de la possibilité d'utiliser plusieurs instructions return, lorsque
cela apporte plus de clarté et de lisibilité au code.

lien : Comment créer et utiliser un tableau avec std::vector ?


lien : Qu'est-ce qu'un pointeur intelligent ?
lien : Comment gérer proprement des allocations / désallocations de ressources ? Le RAII !
lien : Boost.SmartPtr : les pointeurs intelligents de Boost, par Matthieu Brucher

- 113 -
Les sources présentées sur cette pages sont libres de droits, et vous pouvez les utiliser à votre convenance. Par contre la page de présentation de ces sources
constitue une oeuvre intellectuelle protégée par les droits d'auteurs. Copyright © 2008 Developpez LLC. Tout droits réservés Developpez LLC. Aucune
reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents et images sans l'autorisation expresse de Developpez
LLC. Sinon vous encourez selon la loi jusqu'à 3 ans de prison et jusqu'à 300 000 E de dommages et intérêts.
http://cpp.developpez.com/faq/cpp/
Sommaire > Les fonctions > Les fonctions membres virtuelles
Que signifie le mot-clé virtual ?
Auteurs : Anomaly , Aurélien Regat-Barrel , Luc Hermitte ,
Le mot-clé virtual permet de supplanter une fonction membre d'une classe parent depuis une classe dérivée (à condition
qu'elle ait la même signature).

class A
{
public:
void F1() { cout << "A::F1()\n"; }
virtual void F2() { cout << "A::F2()\n"; }
};

class B : public A
{
public:
void F1() { cout << "B::F1()\n"; }
void F2() { cout << "B::F2()\n"; }
};

int main()
{
A a;
a.F1(); // affiche "A::F1()"
a.F2(); // affiche "A::F2()"

B b;
b.F1(); // affiche "B::F1()"
b.F2(); // affiche "B::F2()"

// copie non polymorphe


a = b;
a.F1(); // affiche "A::F1()"
a.F2(); // affiche "A::F2()"

// utilisation polymorphe de B (par pointeur)


A * pa = &b;
pa->F1(); // affiche "A::F1()"
pa->F2(); // affiche "B::F2()" <-- grace à virtual

// utilisation polymorphe de B (par référence)


A & ra = b;
ra.F1(); // affiche "A::F1()"
ra.F2(); // affiche "B::F2()" <-- grace à virtual
}

Dans cet exemple, F1() est redéfinie statiquement par B, c'est-à-dire que le compilateur utilise le type officiel de la
variable pour savoir quelle fonction appeler. Ainsi, si on appelle F1() depuis un objet de type A, ce sera toujours A::F1()
qui sera appelé, et si l'objet est de type B ce sera B::F1() indépendamment du fait qu'il peut s'agir d'un pointeur ou
d'une référence sur A qui désigne en réalité un objet de type B (cas d'utilisation polymorphe de B).
L'appel à une fonction membre virtuelle n'est au contraire pas déterminé à la compilation mais lors de l'exécution. Le
fait que A::F2() soit déclarée virtual et supplantée par B::F2() signifie qu'à chaque appel de F2() le compilateur va tester
le type réel de l'objet afin d'appeler B::F2() s'il le peut. Sinon il appellera A::F2(). On parle alors de liaison dynamique
(dynamic binding en anglais) par opposition à la liaison statique faite lors de l'édition de liens.
La virtualité implique l'utilisation de pointeurs ou de références. Ceci est illustré par le 3° exemple du code ci-dessus
qui effectue une recopie non polymorphe d'un objet B vers un objet A. Dans ce cas l'objet B est "tronqué" (pour éviter
ce problème il faut passer par une copie polymorphe, voir Comment effectuer la copie d'objets polymorphes ?) et on
obtient un objet de type A malgré que l'on soit parti d'un objet de type B.

- 114 -
Les sources présentées sur cette pages sont libres de droits, et vous pouvez les utiliser à votre convenance. Par contre la page de présentation de ces sources
constitue une oeuvre intellectuelle protégée par les droits d'auteurs. Copyright © 2008 Developpez LLC. Tout droits réservés Developpez LLC. Aucune
reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents et images sans l'autorisation expresse de Developpez
LLC. Sinon vous encourez selon la loi jusqu'à 3 ans de prison et jusqu'à 300 000 E de dommages et intérêts.
http://cpp.developpez.com/faq/cpp/
Ce n'est pas le cas avec l'utilisation de pointeurs ou références, qui bien que déclarés comme étant des pointeurs /
références sur des objets de types A peuvent désigner des objets de type B comme dans les deux derniers exemples du
code précédent.
Le type statique de pa est A* mais son type dynamique est B*. De même, le type dynamique de ra est B, ce qui explique
que pa->F2() et ra.F2() provoquent l'appel de B::F2() alors que statiquement c'est A::F2() qui aurait du être appelé.
Notez que cet exemple n'inclut pas de destructeur virtuel par souci de simplification, mais ceci serait nécessaire. Pour
plus d'explications, lire la question Pourquoi et quand faut-il créer un destructeur virtuel ?.

Pouvez-vous me donner une raison simple pour laquelle la virtualité est si importante ?
Auteurs : Marshall Cline ,
L'appel dynamique permet d'augmenter la réutilisabilité en autorisant le 'vieux' code à appeler du nouveau code.

Avant l'apparition de l'orientation objet, la réutilisation du code se faisait en appelant du vieux code à partir du nouveau
code. Par exemple, un programmeur peut écrire du code appelant du code réutilisable comme printf(), ....

Avec l'orientation objet, la réutilisation peut aussi être accomplie via l'appel de nouveau code par de l'ancien. Par
exemple, un programmeur peut écrire du code qui est appelé par un framework qui a été écrit par son arrière grand-
père. Il n'y a pas besoin de modifier le code écrit par l'arrière grand-père. En fait, il n'a même pas besoin d'être
recompilé. Et si jamais il ne restait que le fichier objet, et que le code écrit par l'arrière grand-père ait été perdu depuis
25 ans, cet ancien fichier objet appellera le code avec les nouvelles fonctionnalités sans rien changer d'autre.

C'est cela l'extensibilité, et c'est cela l'orientation objet.

Les fonctions virtuelles sont-elles un mécanisme important en C++ ?


Auteurs : Marshall Cline ,
OUI

Sans les fonctions virtuelles, le C++ ne serait pas un langage orienté objet. La surcharge d'opérateur et les fonctions
membres non virtuelles sont très pratiques, mais ne sont, finalement qu'une variante syntaxique de la notion beaucoup
plus classique de passage de pointeur sur une structure à une fonction. La bibliothèque standard contient de nombreux
templates illustrant les techniques de "programmation générique", qui sont aussi très pratiques, mais les fonctions
virtuelles sont le coeur même de la programmation orientée objet.

D'un point de vue 'business', il y a très peu de raison de passer du C pur au C++ sans les fonctions virtuelles (pour
le moment, nous ferons abstraction de la programmation générique et de la bibliothèque standard). Les spécialistes
pensent souvent qu'il a une grande différence entre le C et le C++ non orienté objet ; mais sans l'orientation objet, la
différence n'est pas suffisante pour justifier le coût de formation des développeurs, des nouveaux outils, ....
En d'autres termes, si je devais conseiller un gestionnaire concernant le passage du C au C++ sans orientation objet
(c'est-à-dire changer le langage sans changer de paradigme), je le découragerais probablement, à moins qu'il y ait des
contraintes liées aux outils utilisés. D'un point de vue gestion, la programmation orientée objet permet de concevoir
des systèmes extensibles et adaptables, mais la syntaxe seule sans l'orientation objet ne réduira jamais le coût de
maintenance, mais augmentera probablement les coûts de formation de façon significative.

Nota : le C++ sans virtualité n'est pas orienté objet. Programmer avec des classes mais sans liaison dynamique est une
programmation basée sur des objets, mais n'est pas de la programmation objet. Ignorer la virtualité est équivalent
à ignorer l'orientation objet. Tout ce qui reste est une programmation basée sur des objets, tout comme la version

- 115 -
Les sources présentées sur cette pages sont libres de droits, et vous pouvez les utiliser à votre convenance. Par contre la page de présentation de ces sources
constitue une oeuvre intellectuelle protégée par les droits d'auteurs. Copyright © 2008 Developpez LLC. Tout droits réservés Developpez LLC. Aucune
reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents et images sans l'autorisation expresse de Developpez
LLC. Sinon vous encourez selon la loi jusqu'à 3 ans de prison et jusqu'à 300 000 E de dommages et intérêts.
http://cpp.developpez.com/faq/cpp/
originale d'ADA. (Soit dit en passant, le nouvel ADA supporte la véritable orientation objet, et non plus simplement
la programmation basée sur les objets).

Qu'est-ce qu'une fonction virtuelle pure ?


Auteurs : LFE , Aurélien Regat-Barrel , Luc Hermitte ,
Syntaxiquement, une fonction virtuelle pure est une fonction virtuelle suivie de "= 0" dans sa déclaration :

class Test
{
public:
virtual void F() = 0; // = 0 signifie "virtuelle pure"
};

Une fonction virtuelle signifie qu'elle peut être supplantée par une fonction d'une classe fille.
Une fonction virtuelle pure signifie qu'elle doit être supplantée par une fonction d'une classe fille.
La classe qui déclare une fonction virtuelle pure n'est alors pas instanciable car elle possède au moins une fonction qui
doit être supplantée. On dit alors que c'est une classe abstraite (lire Qu'est-ce qu'une classe abstraite ?).
Notez que virtuelle pure veut simplement dire que la fonction doit être supplantée par les classes filles que l'on veut
instanciables, et non que la fonction n'est pas implémentée. Le C++ autorise une fonction virtuelle pure à disposer d'un
corps. Une telle pratique sert généralement à forcer une classe à être abstraite (en rendant son destructeur virtuel pur)
ou à proposer une implémentation type pour la fonction virtuelle pure.

class A
{
public:
virtual void f() = 0; // virtuelle pure
};

void A::f()
{
// implémentation par défaut
}

// B se contente de l'implémentation par défaut de f()


class B : public A
{
public:
void f()
{
A::f();
}
};

Qu'est-ce qu'un type de retour covariant ?


Auteurs : Aurélien Regat-Barrel ,
Lors de la réimplémentation d'une fonction membre virtuelle dans une classe dérivée, il est possible de ne pas tout à
fait respecter le prototype de la fonction virtuelle en renvoyant un type dérivé du type originel :

class A {};
class B : public A {};

class Base
{

- 116 -
Les sources présentées sur cette pages sont libres de droits, et vous pouvez les utiliser à votre convenance. Par contre la page de présentation de ces sources
constitue une oeuvre intellectuelle protégée par les droits d'auteurs. Copyright © 2008 Developpez LLC. Tout droits réservés Developpez LLC. Aucune
reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents et images sans l'autorisation expresse de Developpez
LLC. Sinon vous encourez selon la loi jusqu'à 3 ans de prison et jusqu'à 300 000 E de dommages et intérêts.
http://cpp.developpez.com/faq/cpp/
public:
virtual A* Test()
{
cout << "Base::Test\n";
return new A;
}
};

class Derived : public Base


{
public:
virtual B* Test() // le type de retour est différent de Base::Test
{
cout << "Derived::Test\n";
return new B;
}
};

int main()
{
Base *b = new Derived;
A *a = b->Test();
}

Si votre compilateur supporte les retours covariants, alors le message "Derived::Test" devrait s'afficher, et a devrait
pointer vers une instance de B. Si "Base::Test" s'affiche, c'est que le compilateur a considéré que le prototype de
Derived::Test ne correspondait pas à celui de Base::Test et qu'il s'agissait donc d'une nouvelle fonction membre propre
à Derived et non pas d'une réimplémentation de Base::Test (voir Qu'est-ce que le masquage de fonction ?). Dans ce cas,
ou en cas de refus de compilation, votre compilateur ne supporte pas les retours covariants (cas de VC++ 6).

Cette possibilité est en particulier utilisée dans le clonage de classes polymorphes. Les retours covariants permettent
en effet de transformer le code suivant :

class Clonable
{
public:
virtual Clonable* Clone() const = 0;
};

class A : public Clonable


{
public:
virtual Clonable* Clone() const
{
return new A( *this );
}
};

int main()
{
A *a1 = new A;
// faire une copie
A *a2 = static_cast<A*>( a1->Clone() ); // cast obligatoire !
}

En une version plus élégante sans cast :

class Clonable
{
public:
virtual Clonable* Clone() const = 0;

- 117 -
Les sources présentées sur cette pages sont libres de droits, et vous pouvez les utiliser à votre convenance. Par contre la page de présentation de ces sources
constitue une oeuvre intellectuelle protégée par les droits d'auteurs. Copyright © 2008 Developpez LLC. Tout droits réservés Developpez LLC. Aucune
reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents et images sans l'autorisation expresse de Developpez
LLC. Sinon vous encourez selon la loi jusqu'à 3 ans de prison et jusqu'à 300 000 E de dommages et intérêts.
http://cpp.developpez.com/faq/cpp/
};

class A : public Clonable


{
public:
virtual A* Clone() const // retour covariant
{
return new A( *this );
}
};

int main()
{
A *a1 = new A;
// faire une copie
A *a2 = a1->Clone(); // plus de cast
}

Pour plus de détails à ce sujet, lire Comment effectuer la copie d'objets polymorphes ?.

Puis-je appeler des fonctions virtuelles dans le constructeur (ou le destructeur) ?


Auteurs : JolyLoic , Laurent Gomila ,
Oui, c'est possible, mais attention, ça ne fait pas toujours ce qu'on pense.
La première approche consiste à comprendre que lors de l'appel du constructeur d'une classe de base, la classe dérivée
n'a pas encore été construite. Donc, c'est la méthode spécialisée à ce niveau qui est appelée.
Voyons en détail :
La règle est que le type dynamique d'une variable en cours de construction est celui du constructeur qui est en train
d'être exécuté. Pour bien comprendre ce qui se passe, il faut donc revenir sur la différence entre le type statique d'une
variable, et son type dynamique.

Prenons par exemple trois classes, C qui dérive de B qui dérive de A. Par exemple, dans :

A* a = new B();

La variable a possède comme type statique (son type déclaré dans le programme) A*. Par contre, son type dynamique
est B*. Une fonction virtuelle est simplement une fonction dont on va chercher le code en utilisant le type dynamique
de la variable, au lieu de son type statique, comme une fonction classique.

Maintenant, quand on crée un objet de type C, les choses se passent ainsi :

• On alloue assez de mémoire pour un objet de la taille de C.


• On initialise la sous partie correspondant à A de l'objet.
• On appelle le corps du constructeur de A. Pendant cet appel, l'objet crée a pour type dynamique A.
• On initialise la sous partie correspondant à B de l'objet.
• On appelle le corps du constructeur de B. Pendant cet appel, l'objet crée a pour type dynamique B.
• On initialise la sous partie correspondant à C de l'objet.
• On appelle le corps du constructeur de C. Pendant cet appel, l'objet crée a pour type dynamique C.

Donc, dans le corps du constructeur de la classe B, un appel d'une fonction virtuelle appellera la version de la fonction
définie dans la classe B (ou à défaut celle définie dans A si la fonction n'a pas été définie dans B), et non pas celle définie
dans la classe C.

- 118 -
Les sources présentées sur cette pages sont libres de droits, et vous pouvez les utiliser à votre convenance. Par contre la page de présentation de ces sources
constitue une oeuvre intellectuelle protégée par les droits d'auteurs. Copyright © 2008 Developpez LLC. Tout droits réservés Developpez LLC. Aucune
reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents et images sans l'autorisation expresse de Developpez
LLC. Sinon vous encourez selon la loi jusqu'à 3 ans de prison et jusqu'à 300 000 E de dommages et intérêts.
http://cpp.developpez.com/faq/cpp/
D'ailleurs, si la fonction est virtuelle pure dans B, ça causera quelques problèmes, puisqu'on tentera alors d'appeler
une fonction qui n'existe pas. En général, le programme va planter, si on a de la chance, il affichera une message du
style "Pure function called".

La problèmatique est exactement la même pour les destructeurs, mais dans l'ordre inverse.

Pourquoi cette règle ? Une fonction définie dans C a accès aux données membre de C. Or, on a vu que au moment où
on exécute l'appel au corps du constructeur de B, ces dernières ne sont pas encore créées. On a donc préféré jouer la
sécurité.

lien : Dans quel ordre sont construits les différents composants d'une classe ?

- 119 -
Les sources présentées sur cette pages sont libres de droits, et vous pouvez les utiliser à votre convenance. Par contre la page de présentation de ces sources
constitue une oeuvre intellectuelle protégée par les droits d'auteurs. Copyright © 2008 Developpez LLC. Tout droits réservés Developpez LLC. Aucune
reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents et images sans l'autorisation expresse de Developpez
LLC. Sinon vous encourez selon la loi jusqu'à 3 ans de prison et jusqu'à 300 000 E de dommages et intérêts.
http://cpp.developpez.com/faq/cpp/
Sommaire > Les fonctions > Les fonctions inline
Que se passe-t-il avec les fonctions inline ?
Auteurs : Marshall Cline ,
Quand le compilateur évalue l'appel d'une fonction inline, le code complet de cette fonction est inséré dans le code de
l'appelant (c'est le même principe que ce qui se passe avec un #define). Cela peut, parmi beaucoup d'autres choses,
améliorer les performances, étant donné que l'optimiseur peut intégrer directement le code appelé, voire optimiser le
code appelé en fonction du code appelant.

Il y a plusieurs façons d'indiquer qu'une fonction est inline, certaines impliquant le mot-clé inline, d'autres non. Peu
importe comment une fonction est déclarée inline, c'est une demande que le compilateur est autorisé à ignorer : il
peut en évaluer certaines, toutes ou même aucune. (Ne soyez pas découragés si cela semble désespérément vague. La
flexibilité de ce qui précède est un avantage important : le compilateur peut gérer de grosses fonctions différemment
des petites, de plus, cela permet au compilateur de générer du code facile à déboguer si vous spécifiez les bonnes options
de compilation.)

Un exemple simple d'intégration


Auteurs : Marshall Cline ,
Supposons l'appel suivant à une fonction g() :

void f()
{
int x = /*...*/;
int y = /*...*/;
int z = /*...*/;
...code that uses x, y and z...
g(x, y, z);
...more code that uses x, y and z...
}

En supposant que l'on ait une implémentation typique de C++, possédant des registres et une pile, les registres et les
paramètres sont déposés sur la pile juste avant l'appel de g(). Les paramètres sont ensuite retirés de la pile lors de
l'entrée dans g(), redéposés lors de la sortie de g() et finalement relus dans f(). Cela fait un certain nombre de lectures
et écritures inutiles, spécialement dans le cas ou le compilateur a la possibilité d'utiliser les registres pour les variables
x, y et z : chaque variable pourrait être écrite deux fois (en tant que registre et en tant que paramètre) et lue deux fois
aussi (lors de son utilisation dans g() et pour restaurer les registres au retour dans f()).

void g(int x, int y, int z)


{
...code that uses x, y and z...
}

Si le compilateur évalue l'appel de g() en tant qu'inline, toutes ces lectures et écritures disparaissent. Les registres
n'auront pas besoin d'être écrits deux fois et les paramètres n'auront pas besoin d'être empilés ni désempilés, étant
donné que l'optimiseur saura qu'ils sont déjà dans les registres.

- 120 -
Les sources présentées sur cette pages sont libres de droits, et vous pouvez les utiliser à votre convenance. Par contre la page de présentation de ces sources
constitue une oeuvre intellectuelle protégée par les droits d'auteurs. Copyright © 2008 Developpez LLC. Tout droits réservés Developpez LLC. Aucune
reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents et images sans l'autorisation expresse de Developpez
LLC. Sinon vous encourez selon la loi jusqu'à 3 ans de prison et jusqu'à 300 000 E de dommages et intérêts.
http://cpp.developpez.com/faq/cpp/
Bien entendu, votre configuration particulière sera différente, et il existe de nombreux cas qui sont en dehors du cas de
figure présenté ici, mais l'exemple ci-dessus sert à illustrer ce qui peut se passer lors de l'intégration inline.

Les fonctions inline améliorent-elles les performances ?


Auteurs : Marshall Cline ,
Cela dépend.

Il n'y a pas de réponse simple. Les fonctions inline peuvent rendre le code plus rapide, mais elles peuvent aussi le rendre
plus lent. Elles peuvent rendre le code plus gros ou le rendre plus petit. Elles peuvent ralentir considérablement le
programme ou le rendre plus performant. Elles peuvent aussi n'avoir aucun impact sur la rapidité du code.

Les fonctions inline peuvent rendre le code plus rapide Comme vu précédemment, l'intégration du code peut supprimer
une poignée d'instructions inutiles, ce qui peut rendre le code plus rapide.

Les fonctions inline peuvent rendre le code plus lent Un excès de fonctions inline peur rendre le code 'indigeste', ce qui
peut provoquer un excès d'accès à la mémoire virtuelle sur certains systèmes. En d'autres mots, si la taille de l'exécutable
est trop importante, le système risque de passer beaucoup de temps à faire de la pagination sur disque pour accéder
à la suite du code.

Les fonctions inline peuvent rendre le code plus gros C'est la notion de code 'indigeste', décrite ci-dessus. Par exemple,
si un programme a 100 fonctions inline qui augmenteront à chaque fois la taille de l'exécutable de 100 bytes et qui
sont appelées 100 fois chacune, l'augmentation de taille de l'exécutable sera proche de 1 MB. Est-ce que ce Mo posera
problème ? Qui sait, mais ce Mo risque d'être celui qui fera faire de la pagination au système et donc le ralentir.

Les fonctions inline peuvent rendre le code plus petit Les compilateurs génèrent souvent plus de code pour empiler /
désempiler les paramètres que l'inclusion du code ne le ferait. C'est ce qui arrive avec de très petites fonctions, et cela
peut même arriver avec de grosses fonctions quand l'optimiseur arrive à supprimer le code redondant via l'inclusion
du code - l'optimiseur peut donc transformer de grosses fonctions en petites.

Les fonctions inline peuvent augmenter la pagination Le code généré peut devenir très gros, ce qui risque de causer
un ralentissement considérable.

Les fonctions inline peuvent réduire la pagination Le nombre de pages qui doivent se trouver en mémoire au même
moment peut se réduire, alors que la taille de l'exécutable augmente. Quand f() appelle g(), le code peut très bien se
trouver dans deux pages différentes, alors que lorsque le compilateur inclut le code de g() dans f(), le code a plus de
probabilité de se trouver dans la même page.

Les fonctions inline peuvent n'avoir aucune influence sur les performances Les performances ne sont pas liées qu'au
CPU. Par contre, les entrées/sorties, les accès aux bases de données, l'accès au réseau peuvent représenter un sérieux
goulot d'étranglement. A moins que votre CPU ne soit utilisé à 100% la plupart du temps, l'utilisation des fonctions
inline n'améliorera pas les performances générales du système (et même si le goulot d'étranglement se situe au niveau
du CPU, les fonctions inline ne seront utiles que dans le code posant problème, ce qui représente souvent de très petits
morceaux de code).

Il n'y a pas de réponse simple : vous devez essayer et voir ce qui est le mieux. Ne vous limitez pas à des réponses
simplistes telles que "Ne jamais utiliser les fonctions inline" ou "Toujours utiliser les fonctions inline" ou "N'utiliser

- 121 -
Les sources présentées sur cette pages sont libres de droits, et vous pouvez les utiliser à votre convenance. Par contre la page de présentation de ces sources
constitue une oeuvre intellectuelle protégée par les droits d'auteurs. Copyright © 2008 Developpez LLC. Tout droits réservés Developpez LLC. Aucune
reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents et images sans l'autorisation expresse de Developpez
LLC. Sinon vous encourez selon la loi jusqu'à 3 ans de prison et jusqu'à 300 000 E de dommages et intérêts.
http://cpp.developpez.com/faq/cpp/
les fonctions inline que si le code fait moins de ... lignes de code". Ces règles arbitraires sont peut-être faciles à écrire,
mais les résultats sont plus que décevants.

Comment les fonctions inline peuvent-elles influer sur le compromis vitesse/sécurité ?


Auteurs : Marshall Cline ,
En C pur, vous pouvez réaliser des "structs (structures) encapsulées" en mettant un void * dans un struct, où le void
* pointe sur les vraies données qui sont inconnues aux utilisateurs du struct. Ainsi ces même utilisateurs du struct ne
peuvent pas interpréter les données pointées par void *, mais les fonctions d'accès peuvent elles convertir du void * vers
le type de données caché. Ce qui donne une forme de l'encapsulation.

Malheureusement ceci impose de renoncer à la sûreté de type, et impose également un appel de fonction pour accéder
même aux zones insignifiantes de la structure (si vous permettiez l'accès direct au champs de la structure, chacun
pourrait accéder directement à toute la structure puisqu'il connaîtrait nécessairement les données pointées par void *,
et il deviendrait difficile de changer la structure de données sous jacente).

Le temps d'appel de fonction est court, mais il s'ajoute à chaque appel. Les classes C++ permettent à ces appels de
fonction d'être insérés inline. Ceci vous laisse la sûreté de l'encapsulation avec en plus la vitesse des accès directs. En
outre, les types des paramètres de ces fonctions inline sont contrôlés par le compilateur, ce qui est une amélioration par
rapport aux macros #define du C.

Pourquoi utiliser une fonction inline au lieu d'une macro #define ?


Auteurs : Marshall Cline ,
Parce que les #define sont mauvais. Mais parfois il faut les utiliser.

Contrairement aux #define, les fonctions inline évitent des erreurs très difficiles à tracer, étant donné que les fonctions
inline évaluent toujours chaque argument une et une seule fois. En d'autres mots, l'invocation d'une fonction inline est
sémantiquement identique à l'invocation d'une fonction classique, avec la seule différence de la rapidité.

// Macro qui renvoie la valeur absolue de i


#define unsafe(i) \
( (i) >= 0 ? (i) : -(i) )

// Fonction inline qui renvoie la valeur absolue de i


inline
int safe(int i)
{
return i >= 0 ? i : -i;
}

int f();

void userCode(int x)
{
int ans;

ans = unsafe(x++); // Erreur ! x est incrémenté deux fois


ans = unsafe(f()); // Danger ! f() est appelé deux fois

ans = safe(x++); // Correct ! x est incrémenté une seule fois


ans = safe(f()); // Correct ! f() est appelé une seule fois
}

- 122 -
Les sources présentées sur cette pages sont libres de droits, et vous pouvez les utiliser à votre convenance. Par contre la page de présentation de ces sources
constitue une oeuvre intellectuelle protégée par les droits d'auteurs. Copyright © 2008 Developpez LLC. Tout droits réservés Developpez LLC. Aucune
reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents et images sans l'autorisation expresse de Developpez
LLC. Sinon vous encourez selon la loi jusqu'à 3 ans de prison et jusqu'à 300 000 E de dommages et intérêts.
http://cpp.developpez.com/faq/cpp/
Une autre différence est que les types des paramètres sont vérifiés, et les conversions nécessaires effectuées.

Les macros sont à proscrire, ne les utilisez que si vous n'avez pas d'autre alternative.

Comment signaler au compilateur de mettre une fonction non membre inline ?


Auteurs : Marshall Cline ,
Quand vous déclarez une fonction inline, elle a exactement l'aspect d'une fonction normale :

void f(int i, char c);

Mais quand vous définissez une fonction inline, vous ajoutez au début de la définition de la fonction le mot-clé inline,
et vous mettez la définition dans le fichier d'en-tête :

inline void f(int i, char c)


{
// ...
}

Comment signaler au compilateur de mettre une fonction membre inline ?


Auteurs : Marshall Cline ,
Quand vous déclarez inline une fonction membre, elle a exactement l'aspect d'une fonction membre normale :

class Fred {
public:
void f(int i, char c);
};

Mais quand vous définissez une fonction membre inline, vous ajoutez au début de la définition de la fonction membre
le mot-clé inline, et vous mettez la définition dans un fichier d'en-tête :

inline void Fred::f(int i, char c)


{
// ...
}

Il est habituellement impératif que la définition de la fonction (la partie entre {... }) soit placée dans un fichier d'en-tête.
Si vous mettiez inline la définition d'une fonction dans un fichier d'implémentation cpp, et si cette fonction était appelée
d'un autre fichier cpp, vous obtiendriez "une erreur externe" (fonction non définie) au moment de l'édition de liens.

Y a-t-il un autre moyen de spécifier une fonction membre inline ?


Auteurs : Marshall Cline ,
Oui : définissez la fonction de membre dans le corps de classe elle-même :

class Fred
{
public:
void f(int i, char c)

- 123 -
Les sources présentées sur cette pages sont libres de droits, et vous pouvez les utiliser à votre convenance. Par contre la page de présentation de ces sources
constitue une oeuvre intellectuelle protégée par les droits d'auteurs. Copyright © 2008 Developpez LLC. Tout droits réservés Developpez LLC. Aucune
reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents et images sans l'autorisation expresse de Developpez
LLC. Sinon vous encourez selon la loi jusqu'à 3 ans de prison et jusqu'à 300 000 E de dommages et intérêts.
http://cpp.developpez.com/faq/cpp/
{
// ...
}
};

Bien que ce soit plus facile pour la personne qui écrit la classe, c'est aussi plus dur pour le lecteur puisqu'on mélange "ce
que fait" la classe avec "comment elle le fait". En raison de ce mélange, on préfère normalement définir des fonctions
membres en dehors du corps de classe avec le mot-clé inline. Comprenez que dans un monde orienté objet et réutilisation,
il y a beaucoup de gens qui utilisent la classe, mais une seule personne qui la crée (vous même). C'est pourquoi vous
devriez faire les choses en faveur du plus grand nombre plutôt que pour le plus petit.

- 124 -
Les sources présentées sur cette pages sont libres de droits, et vous pouvez les utiliser à votre convenance. Par contre la page de présentation de ces sources
constitue une oeuvre intellectuelle protégée par les droits d'auteurs. Copyright © 2008 Developpez LLC. Tout droits réservés Developpez LLC. Aucune
reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents et images sans l'autorisation expresse de Developpez
LLC. Sinon vous encourez selon la loi jusqu'à 3 ans de prison et jusqu'à 300 000 E de dommages et intérêts.
http://cpp.developpez.com/faq/cpp/
Sommaire > Les références
Qu'est-ce qu'une référence ?
Auteurs : Marshall Cline , Aurélien Regat-Barrel ,
Une référence est un alias, un nom alternatif pour un objet. Le principe à retenir est que tout se passe comme si c'était
l'objet lui-même et non une référence sur lui.

Les références sont souvent utilisées lors du passage de paramètres, en particulier avec le mot-clé const (référence
constante : donc le paramètre passé est non modifiable) afin de rendre l'appel à la fonction plus performant sur des objets
volumineux. Sans le mot-clé const l'usage d'une référence indique alors que le paramètre est modifié par la fonction.

// fonction qui échange i et j


void swap(int& i, int& j)
{
int tmp = i;
i = j;
j = tmp;
}

int main()
{
int x = 1;
int y = 2;
swap( x, y );
// x = 2, y = 1
}

Dans cet exemple, i et j sont des alias pour x et y du main. En d'autres mots, i est x (pas un pointeur sur x, ni une copie,
mais x lui-même). Tout ce qui est fait à x est fait à i et inversement.

Bon, maintenant, pensons aux références du point de vue du programmeur. Au risque de provoquer la confusion en
donnant une autre perspective voici comment les références sont implémentées en pratique. Au fond, une référence i
vers un objet x est habituellement son adresse. Mais quand le programmeur fait un i++, le compilateur génère du code
qui incrémente x. Typiquement, les bits d'adressage que le compilateur utilise pour accéder à x sont inchangés. Un
programmeur C pensera qu'il s'agit du passage d'un pointeur, avec les variantes syntaxiques suivantes :

• Déplacer le & de l'appelant à l'appelé


• Supprimer les notations *s

En d'autres mots, un programmeur le considérera comme une macro pour (*p), où p est un pointeur sur x (par ex., le
compilateur déréférencerait automatiquement le pointeur : i++ serait transformé en (*p)++).

Note : même si une référence est souvent implémentée en utilisant une adresse dans le langage d'assemblage généré,
ne considérez pas les références comme un pointeur "marrant" sur un objet. Une référence est l'objet. Ce n'est pas un
pointeur sur l'objet, ni une copie de l'objet. C'est l'objet.

Que se passe-t-il si on assigne une autre valeur à la référence ?


Auteurs : Marshall Cline ,
L'état du référent (le référent est l'objet auquel la référence se rapporte) est modifié.

- 125 -
Les sources présentées sur cette pages sont libres de droits, et vous pouvez les utiliser à votre convenance. Par contre la page de présentation de ces sources
constitue une oeuvre intellectuelle protégée par les droits d'auteurs. Copyright © 2008 Developpez LLC. Tout droits réservés Developpez LLC. Aucune
reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents et images sans l'autorisation expresse de Developpez
LLC. Sinon vous encourez selon la loi jusqu'à 3 ans de prison et jusqu'à 300 000 E de dommages et intérêts.
http://cpp.developpez.com/faq/cpp/
Le référent est la référence, donc toute modification faite à la référence est faite au référent. Au niveau du compilateur,
une référence est une "lvalue", c'est-à-dire qu'il peut apparaître à la gauche d'un opérateur d'affectation.

Que se passe-t-il en cas de retour d'une référence lors de l'appel d'une fonction ?
Auteurs : Marshall Cline ,
L'appel de fonction peut se trouver à la gauche d'un opérateur d'assignation.

Cette possibilité peut sembler étrange au premier abord. Par exemple, personne ne penserait que l'expression

f() = 7;

ait un sens. Par contre, si a est un objet de la classe Array, la plupart des gens trouveront que

a[i] = 7;

a un sens, même si a n'est qu'un appel de fonction caché (c'est en fait l'appel de l'opérateur Array::operator [](int), qui
est l'opérateur d'indexation de la classe Array)

class Array {
public:
int size() const;
float& operator[] (int index);
...
};

int main()
{
Array a;
for (int i = 0; i < a.size(); ++i)
a[i] = 7; // cette ligne appelle Array::operator[](int)
...
}

Comment faire pour modifier une référence de façon qu'elle désigne un autre objet ?
Auteurs : Marshall Cline ,
Ce n'est pas possible.

On ne peut pas séparer le référé de sa référence.

Contrairement aux pointeurs, lorsqu'une référence est liée à un objet, elle ne peut pas être réaffectée à un autre objet. La
référence en elle-même n'est pas un objet (elle n'a pas d'identité : prendre l'adresse d'une référence retourne l'adresse
du référent).

De ce point de vue, une référence est similaire à un pointeur constant

int* const p

par opposition à un pointeur sur une constante

- 126 -
Les sources présentées sur cette pages sont libres de droits, et vous pouvez les utiliser à votre convenance. Par contre la page de présentation de ces sources
constitue une oeuvre intellectuelle protégée par les droits d'auteurs. Copyright © 2008 Developpez LLC. Tout droits réservés Developpez LLC. Aucune
reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents et images sans l'autorisation expresse de Developpez
LLC. Sinon vous encourez selon la loi jusqu'à 3 ans de prison et jusqu'à 300 000 E de dommages et intérêts.
http://cpp.developpez.com/faq/cpp/
const int* p

En dépit d'une certaine ressemblance, ne confondez pas les références et les pointeurs ; ce n'est pas du tout la même
chose.

Quand utiliser des références et quand utiliser des pointeurs ?


Auteurs : Marshall Cline ,
Utilisez les références aussi souvent que possible, et les pointeurs quand vous le devez.

Les références sont habituellement préférables aux pointeurs, à moins que vous ne deviez la réallouer à un moment
donné. Cela veut dire que les références sont plus utiles dans les interfaces publiques des classes. Les références
apparaissent typiquement dans les interfaces, alors que les pointeurs sont utilisés à l'intérieur.

Exception à ce qui vient d'être dit : lorsqu'un paramètre ou la valeur de retour d'une fonction a besoin d'une référence
"sentinelle". Ceci est habituellement mieux fait en retournant ou recevant un pointeur et en donnant à NULL cette
signification particulière. Les références devraient toujours désigner un objet et jamais un pointeur NULL déréférencé).

Note : les vieux programmeurs C n'apprécient pas d'utiliser des références étant donné qu'elles attribuent une
sémantique à la référence qui n'est pas claire dans le code appelant. Mais, après une certaine pratique du C++, certains
réalisent qu'il s'agit d'une forme de masquage d'information, ce qui est plus un bien qu'un défaut.
Par ex. : les programmeurs devraient écrire le code dans le langage du problème plutôt que dans le langage de la
machine.

Qu'est-ce qu'un handle sur un objet ? une référence ? un pointeur ? un pointeur sur un pointeur ?
Auteurs : Marshall Cline ,
le terme handle est utilisé pour désigner n'importe quelle technique qui permet de manipuler un autre objet (un genre
de pseudo pointeur généralisé). Ce terme est (volontairement) ambigu et peu précis.

L'ambiguïté est un avantage dans certains cas. Par exemple, au tout début du design vous ne serez peut-être pas prêt
à adopter une représentation spécifique pour désigner les handles. Vous ne serez peut-être pas sûr du choix à faire
entre les simples pointeurs, les références, les pointeurs de pointeurs, les références de pointeurs, ou encore des tableaux
indicés, ou des tables de hachage, ou des bases de données ou n'importe quelle autre technique. Si vous savez que vous
aurez besoin de quelque chose qui identifiera de façon unique un objet, appelez cette chose un handle.

Si votre but final est de permettre à une portion de code d'identifier/rechercher un objet spécifique d'une classe d'un
certain type (par ex. Fred), vous devrez passer un handle sur Fred à cette portion de code. Le handle peut être une
chaîne qui peut-être utilisée comme une clé dans une table de recherche bien connue. Par exemple, une clé dans

std::map<std::string,Fred>

ou

std::map<std::string,Fred*>

ou encore un entier qui sera un indice dans un tableau du genre

Fred* array = new Fred[maxNumFreds]

- 127 -
Les sources présentées sur cette pages sont libres de droits, et vous pouvez les utiliser à votre convenance. Par contre la page de présentation de ces sources
constitue une oeuvre intellectuelle protégée par les droits d'auteurs. Copyright © 2008 Developpez LLC. Tout droits réservés Developpez LLC. Aucune
reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents et images sans l'autorisation expresse de Developpez
LLC. Sinon vous encourez selon la loi jusqu'à 3 ans de prison et jusqu'à 300 000 E de dommages et intérêts.
http://cpp.developpez.com/faq/cpp/
ou tout simplement un pointeur sur Fred, ou n'importe quoi d'autre.

Les débutants pensent souvent en termes de pointeurs, mais en réalité, ils prennent un risque. Que se passe-t-il si l'objet
Fred doit être déplacé ? Comment savoir quand il est sans risque d'effacer l'objet Fred ? Que se passe-t-il si l'objet doit
être sérialisé ? .... La plupart du temps, on aura tendance à ajouter de plus en plus de couches d'indirections pour gérer
ces cas de figure. Par exemple, le handle sur Fred devrait être un Fred**, où le pointeur pointant sur Fred* est supposé
ne jamais être déplacé, mais à un moment le pointeur doit être déplacé, on met seulement à jour le pointeur sur Fred*.
Ou vous décidez que le handle devient un entier désignant l'objet Fred dans une table, etc.....

Le fait est que nous utilisons le mot handle tant que nous ne savons pas le détail de ce que nous allons faire.

Une autre circonstance dans laquelle nous utilisons le mot handle est quand on préfère rester vague au sujet de ce que
nous avons déjà fait (on utilise parfois le terme 'cookie' pour cela, par ex. "Le programme passe un cookie qui est utilisé
pour identifier de façon unique l'objet Fred adéquat"). La raison pour laquelle nous voulons (parfois) rester vague est
de minimiser les effets de bord si les détails d'implémentations devaient changer. Par exemple, si quelqu'un change le
handle qui était une chaîne qui servait à faire une recherche dans une liste de hachage en un entier qui sert à indicer
une table, cela pourrait causer le changement de dizaines de milliers de lignes de code.

Pour faciliter la maintenance quand les détails de représentation d'un handle changent (ou tout simplement pour rendre
le code plus lisible), nous encapsulerons le handle dans une classe. Cette classe surchargera souvent les opérateurs -> et
* (comme le handle agit comme un pointeur, il semble logique qu'il ressemble à un pointeur).

- 128 -
Les sources présentées sur cette pages sont libres de droits, et vous pouvez les utiliser à votre convenance. Par contre la page de présentation de ces sources
constitue une oeuvre intellectuelle protégée par les droits d'auteurs. Copyright © 2008 Developpez LLC. Tout droits réservés Developpez LLC. Aucune
reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents et images sans l'autorisation expresse de Developpez
LLC. Sinon vous encourez selon la loi jusqu'à 3 ans de prison et jusqu'à 300 000 E de dommages et intérêts.
http://cpp.developpez.com/faq/cpp/
Sommaire > Les opérateurs

- 129 -
Les sources présentées sur cette pages sont libres de droits, et vous pouvez les utiliser à votre convenance. Par contre la page de présentation de ces sources
constitue une oeuvre intellectuelle protégée par les droits d'auteurs. Copyright © 2008 Developpez LLC. Tout droits réservés Developpez LLC. Aucune
reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents et images sans l'autorisation expresse de Developpez
LLC. Sinon vous encourez selon la loi jusqu'à 3 ans de prison et jusqu'à 300 000 E de dommages et intérêts.
http://cpp.developpez.com/faq/cpp/
Sommaire > Les opérateurs > Les conversions de types
Comment effectuer une conversion de type explicite (cast) ?
Auteurs : Aurélien Regat-Barrel , Laurent Gomila ,
Contrairement au C, les cast C++ sont effectués au moyen d'opérateurs spécifiques (mots-clés réservés). Le choix de ces
opérateurs dépend du type de cast, car le C++ différencie 4 types de conversion explicite. Tout d'abord, 3 opérateurs
sont dédiés aux conversions statiques (effectuées à la compilation) :

* static_cast : entre types de même famille. Il s'agit la plupart du temps d'expliciter des conversions qui auraient pu
être effectuées de manière implicite, mais souvent avec un avertissement du compilateur.

// conversion de int en char


int i = 100;
char c = static_cast<char>( i );

// conversion de float en int


float f = 100.0f;
i = static_cast<int>( f );

// conversion classes dérivée -> classe parent


class A {};
class B : public A {};

B *b = new B;
A *a = static_cast<A*>( b );

* reinterpret_cast : entre types de familles différentes. Il s'agit simplement de dire au compilateur "je sais que je
manipule une donnée de type X, mais on va faire comme si elle était de type Y". De ce fait aucune donnée n'est
physiquement modifiée, cet opérateur de conversion n'est qu'une indication pour le compilateur.

// conversion de int en pointeur de char


int i;
char * ptr = reinterpret_cast<char*>( i );

// conversion de int en référence sur char


char & ref = reinterpret_cast<char&>( i );

// conversion pointeur de char -> pointeur de double


double * ptr2 = reinterpret_cast<double*>( ptr );

* const_cast : entre un type donné constant et le même type avec / sans les qualificateurs const ou volatile. Cet opérateur
est rarement utilisé, car :

• La conversion non-const -> const est implicite.


• La conversion const -> non-const relève en fait bien souvent d'une erreur de conception.
• Le qualificateur volatile est rarement utilisé.

class A {};

// conversion de const A * en A *
const A * cptr;
A * ptr = const_cast<A*>( cptr );

// conversion de const A & en A &


A a;
const A & cref = a;

- 130 -
Les sources présentées sur cette pages sont libres de droits, et vous pouvez les utiliser à votre convenance. Par contre la page de présentation de ces sources
constitue une oeuvre intellectuelle protégée par les droits d'auteurs. Copyright © 2008 Developpez LLC. Tout droits réservés Developpez LLC. Aucune
reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents et images sans l'autorisation expresse de Developpez
LLC. Sinon vous encourez selon la loi jusqu'à 3 ans de prison et jusqu'à 300 000 E de dommages et intérêts.
http://cpp.developpez.com/faq/cpp/
A & ref = const_cast<A&>( cref );

Le C++ supporte aussi un autre type de conversion : depuis une classe parent vers une classe enfant (downcasting) ou
depuis une classe parent vers une autre classe parent (crosscasting). La classe parent doit être à usage polymorphe,
autrement dit elle doit obligatoirement comporter au moins une fonction membre virtuelle, et être manipulée au moyen
d'un pointeur ou d'une référence (la virtualité implique l'utilisation de pointeurs ou de références, voir Que signifie le
mot-clé virtual ?). Ce type de cast étant dynamique (effectué à l'exécution), il est susceptible d'échouer et de lever une
exception std::bad_cast dans le cas d'une conversion de références, ou de renvoyer NULL dans le cas d'une conversion
de pointeurs.

L'opérateur associé à ce cast est dynamic_cast :

#include <iostream>
#include <string>

class A
{
public:
virtual std::string get_type() = 0;
};

class B : public A
{
public:
virtual std::string get_type() { return "B"; }
};

class C : public A
{
public:
virtual std::string get_type() { return "C"; };
};

A * create_B_or_C()
{
static int nb = 0;
// si nb est pair, on crée un B, sinon un C
++nb;
if ( nb % 2 == 0 ) { return new B; }
return new C;
}

int main()
{
for ( int i = 0; i < 5; ++i )
{
A *a = create_B_or_C();
std::cout << "Test sur un " << a->get_type() << " : ";

B *b = dynamic_cast<B*>( a );
if ( b == 0 )
{
// échec du cast en B, il doit s'agir d'un C
// lève std::bad_cast s'il ne s'agit pas d'un C
try
{
C & c = dynamic_cast<C&>( *a );
std::cout << "il s'agit d'un C.\n";
}
catch ( const std::bad_cast & )
{
std::cout << "Oups!\n";
}

- 131 -
Les sources présentées sur cette pages sont libres de droits, et vous pouvez les utiliser à votre convenance. Par contre la page de présentation de ces sources
constitue une oeuvre intellectuelle protégée par les droits d'auteurs. Copyright © 2008 Developpez LLC. Tout droits réservés Developpez LLC. Aucune
reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents et images sans l'autorisation expresse de Developpez
LLC. Sinon vous encourez selon la loi jusqu'à 3 ans de prison et jusqu'à 300 000 E de dommages et intérêts.
http://cpp.developpez.com/faq/cpp/
}
else
{
std::cout << "il s'agit d'un B.\n";
}
}
}

dynamic_cast identifie à l'exécution le type réel de l'expression reçue au moyen des informations de type à l'exécution
(RTTI). Cette fonctionnalité doit donc être activée dans votre compilateur pour pouvoir utiliser dynamic_cast, ce qui
n'est pas le cas par défaut avec certains compilateurs (tel que VC++, voir l'option /GR).

Dernière remarque concernant dynamic_cast : celui-ci est souvent utilisé à tort, surtout par les débutants. Voir Pourquoi
l'utilisation du downcasting est-il souvent une pratique à éviter ?. Voir également Qu'est-ce que le cross-casting ?.

Si vous souhaitez tester votre maîtrise des casts C++, nous vous renvoyons à l'item n° 17 des GOTW (Guru Of The
Week) ( http://www.gotw.ca/gotw/017.htm).

Pourquoi l'utilisation du downcasting est-il souvent une pratique à éviter ?


Auteurs : Laurent Gomila , Aurélien Regat-Barrel ,
On est souvent tenté d'utiliser l'opérateur de conversion dynamic_cast pour effectuer un downcasting (voir Comment
effectuer une conversion de type explicite (cast) ?), mais il s'agit fréquemment d'une erreur. Pourquoi ? Car
conceptuellement parlant, on a en réalité rarement besoin de connaître le type réel d'un objet que l'on manipule via sa
classe de base. Si l'on se retrouve dans une telle situation, c'est probablement que :

• La fonction que l'on écrit ne travaille en fait pas sur la classe de base, mais seulement sur certaines classes
dérivées bien identifiées.
• On n'a pas exploité un éventuel polymorphisme dynamique (utilisation des fonctions virtuelles, voir Que signifie
le mot-clé virtual ?).
• On n'a pas exploité un éventuel polymorphisme statique (utilisation des templates et des surcharges).
• On a mal conçu notre hiérarchie.

Le downcasting est donc à utiliser avec parcimonie, lorsque l'on n'a pas le choix ou que l'on sait exactement ce que l'on
fait. Par exemple dans le cas des plugin, ou encore lorsque l'on travaille avec un code, une bibliothèque ou une API qui
ne connaîtrait (et donc ne pourrait manipuler) que la classe de base d'une hiérarchie. Les classes dérivées étant écrites
par le client, le downcasting est donc une solution simple pour communiquer avec la bibliothèque en question.
On peut également citer des implémentations de double-dispatching utilisant le downcasting (notamment dans la
bibliothèque Loki).

Il faut donc toujours s'interroger sur l'utilisation que l'on fait de dynamic_cast : est-ce une nécessité, ou seulement un
moyen pratique de contourner une erreur de conception ?

Qu'est-ce que le cross-casting ?


Auteurs : Aurélien Regat-Barrel ,
Le cross casting est une possibilité offerte par le C++ grâce à son support de l'héritage multiple. Dans l'exemple suivant :

// A B
// \ /

- 132 -
Les sources présentées sur cette pages sont libres de droits, et vous pouvez les utiliser à votre convenance. Par contre la page de présentation de ces sources
constitue une oeuvre intellectuelle protégée par les droits d'auteurs. Copyright © 2008 Developpez LLC. Tout droits réservés Developpez LLC. Aucune
reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents et images sans l'autorisation expresse de Developpez
LLC. Sinon vous encourez selon la loi jusqu'à 3 ans de prison et jusqu'à 300 000 E de dommages et intérêts.
http://cpp.developpez.com/faq/cpp/
// C

class A
{
public:
virtual ~A();
};

class B
{
public:
virtual ~B();
};

class C : public A, public B


{
};

un cast classique permet, à partir de C de convertir en A ou en B. Le cross casting consiste par exemple à obtenir une
instance de B à partir d'une instance de... A !

A *a = new C;
B *b = dynamic_cast<B*>( a );

cet exemple fonctionne car il revient à faire :

A *a = new C;
C *c = dynamic_cast<C*>( a ); // downcasting de A en C
B *b = static_cast<B*>( c ); // cast de C en B

Cette décomposition illustre pourquoi le recours à dynamic_cast est obligatoire (pour le downcasting). Nous avons donc
fait une conversion au travers de la hiérarchie d'héritage, d'où ce terme de cross casting.

Pour plus de précisions sur le cross-casting et ses applications, vous pouvez lire ce document :
Cross Casting: The Capsule Pattern.

- 133 -
Les sources présentées sur cette pages sont libres de droits, et vous pouvez les utiliser à votre convenance. Par contre la page de présentation de ces sources
constitue une oeuvre intellectuelle protégée par les droits d'auteurs. Copyright © 2008 Developpez LLC. Tout droits réservés Developpez LLC. Aucune
reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents et images sans l'autorisation expresse de Developpez
LLC. Sinon vous encourez selon la loi jusqu'à 3 ans de prison et jusqu'à 300 000 E de dommages et intérêts.
http://cpp.developpez.com/faq/cpp/
Sommaire > Les opérateurs > La surcharge d'opérateurs
Qu'est-ce que la surcharge d'opérateur ?
Auteurs : Marshall Cline ,
Cela permet de fournir une façon intuitive d'utiliser les interfaces de vos classes aux utilisateurs. De plus, cela permet
aux templates de travailler de la même façon avec les classes et les types de base.

La surcharge d'opérateur permet aux opérateurs du C++ d'avoir une signification spécifique quand ils sont appliqués
à des types spécifiques. Les opérateurs surchargés sont un "sucre syntaxique" pour l'appel des fonctions :

class Fred
{
public:
// ...
};

#if 0

// Sans surcharge d'opérateur


Fred add(const Fred& x, const Fred& y);
Fred mul(const Fred& x, const Fred& y);

Fred f(const Fred& a, const Fred& b, const Fred& c)


{
return add(add(mul(a,b), mul(b,c)), mul(c,a)); // Hum...
}

#else

// Avec surcharge d'opérateur


Fred operator+ (const Fred& x, const Fred& y);
Fred operator* (const Fred& x, const Fred& y);

Fred f(const Fred& a, const Fred& b, const Fred& c)


{
return a*b + b*c + c*a;
}

#endif

Quel est l'avantage de surcharger un opérateur ?


Auteurs : Marshall Cline ,
Surcharger les opérateurs standards permet de tirer parti de l'intuition des utilisateurs de la classe. L'utilisateur va en
effet pouvoir écrire son code en s'exprimant dans le langage du domaine plutôt que dans celui de la machine.

Le but ultime est de diminuer à la fois le temps d'apprentissage et le nombre de bugs.

Quelques exemples de surcharge d'opérateur


Auteurs : Marshall Cline ,
Parmi les nombreux exemples que l'on pourrait citer :
• myString + yourString pourrait servir à concaténer deux objets string
• myDate++ pourrait servir à incrémenter un objet Date
• a * b pourrait servir à multiplier deux objets Number

- 134 -
Les sources présentées sur cette pages sont libres de droits, et vous pouvez les utiliser à votre convenance. Par contre la page de présentation de ces sources
constitue une oeuvre intellectuelle protégée par les droits d'auteurs. Copyright © 2008 Developpez LLC. Tout droits réservés Developpez LLC. Aucune
reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents et images sans l'autorisation expresse de Developpez
LLC. Sinon vous encourez selon la loi jusqu'à 3 ans de prison et jusqu'à 300 000 E de dommages et intérêts.
http://cpp.developpez.com/faq/cpp/
• a[ i ] pourrait donner accès à un élément contenu dans un objet Array
• x = *p pourrait déréférencer un "pointeur intelligent" qui "pointerait" en fait sur un enregistrement sur disque
# le déréférencement irait chercher l'enregistrement sur le disque, le lirait, et le stockerait dans x.

La surcharge d'opérateur n'embellit pas vraiment ma


classe ; ce n'est pas censé rendre le code plus lisible ?
Auteurs : Marshall Cline ,
La surcharge d'opérateur facilite la vie des utilisateurs d'une classe, mais pas celle du développeur de la classe !

Prenez l'exemple suivant :

class Array {
public:
int& operator[] (unsigned i); // Certains n'aiment pas cette syntaxe
// ...
};

inline int& Array::operator[] (unsigned i) // Certains n'aiment pas cette syntaxe


{
// ...
}

Certains programmeurs n'aiment pas le mot-clé operator ni la syntaxe quelque peu bizarre que l'on doit utiliser dans
le corps même de la classe. La surcharge d'opérateur n'est pas faite pour faciliter la vie du développeur de la classe,
mais est faite pour faciliter la vie de l'utilisateur de la classe :

int main()
{
Array a;
a[3] = 4; // Le code utilisateur doit être facile à écrire et à comprendre...
// ...
}

Souvenez vous que dans un monde orienté réutilisation, vos classes ont des chances d'être utilisées par de nombreux
programmeurs alors que leur construction incombe à vous et à vous seul. Donc, favorisez le plus grand nombre même
si ça rend votre tâche plus difficile.

Quels opérateurs peut-on ou ne peut-on pas surcharger ?


Auteurs : Marshall Cline ,
La plupart des opérateurs peuvent être surchargés. Les seuls opérateurs C que l'on ne peut pas surcharger sont . et ?: (et
aussi sizeof, qui techniquement est un opérateur). C++ vient avec quelques opérateurs supplémentaires, dont la plupart
peuvent être surchargés à l'exception de ::, typeid et de .*

Voici un exemple de surcharge de l'opérateur d'indexation (qui renvoie une référence). Tout d'abord, sans surcharge :

class Array {
public:
int& elem(unsigned i)
{
if (i > 99)
error();

- 135 -
Les sources présentées sur cette pages sont libres de droits, et vous pouvez les utiliser à votre convenance. Par contre la page de présentation de ces sources
constitue une oeuvre intellectuelle protégée par les droits d'auteurs. Copyright © 2008 Developpez LLC. Tout droits réservés Developpez LLC. Aucune
reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents et images sans l'autorisation expresse de Developpez
LLC. Sinon vous encourez selon la loi jusqu'à 3 ans de prison et jusqu'à 300 000 E de dommages et intérêts.
http://cpp.developpez.com/faq/cpp/
return data[i];
}
private:
int data[100];
};

int main()
{
Array a;
a.elem(10) = 42;
a.elem(12) += a.elem(13);
// ...
}

Le même exemple, cette fois-ci avec la surcharge :

class Array {
public:
int& operator[] (unsigned i)
{
if (i > 99)
error();
return data[i];
}
private:
int data[100];
};

int main()
{
Array a;
a[10] = 42;
a[12] += a[13];
}

Peut-on surcharger operator== de façon qu'il compare


deux char[] en faisant une comparaison de chaîne ?
Auteurs : Marshall Cline ,
Non, car au moins l'un des deux opérandes d'un opérateur surchargé doit être d'un type utilisateur (c'est-à-dire une
classe dans la majorité des cas).
Et même si C++ permettait cela (il ne le permet pas), vous auriez tout intérêt à utiliser la classe string qui est bien plus
adaptée qu'un tableau de caractères.

Peut-on définir un operator** qui calcule "x à la puissance y" ?


Auteurs : Marshall Cline ,
Non.

Le nom, la précédence, l'associativité et l'arité (le nombre d'opérandes) d'un opérateur sont fixés par le langage. Et C
++ n'ayant pas d'operator**, une classe ne peut à fortiori pas en avoir.

Si vous en doutez, sachez que x ** y est en fait équivalent à x * (*y) (le compilateur considère que y est un pointeur). En
outre, la surcharge d'opérateur est juste un sucre syntaxique qui est là pour remplacer avantageusement les appels de
fonction. Et ce sucre syntaxique, même s'il est bien utile, n'apporte rien de fondamental. Dans le cas qui nous intéresse

- 136 -
Les sources présentées sur cette pages sont libres de droits, et vous pouvez les utiliser à votre convenance. Par contre la page de présentation de ces sources
constitue une oeuvre intellectuelle protégée par les droits d'auteurs. Copyright © 2008 Developpez LLC. Tout droits réservés Developpez LLC. Aucune
reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents et images sans l'autorisation expresse de Developpez
LLC. Sinon vous encourez selon la loi jusqu'à 3 ans de prison et jusqu'à 300 000 E de dommages et intérêts.
http://cpp.developpez.com/faq/cpp/
ici, je vous suggère de surcharger la fonction pow(base,exposant) (<cmath> contient une version double précision de
cette fonction).

Notez en passant que l'operator^ pourrait faire l'affaire pour "x à la puissance y", à ceci près qu'il n'a ni la bonne
précédence ni la bonne associativité.

Comment implémenter un opérateur d'indexation pour une classe Matrix ?


Auteurs : Marshall Cline ,
Utilisez l'operator() plutôt que l'operator[].

La méthode la plus propre dans le cas d'indexes multiples consiste à utiliser l'operator() plutôt que l'operator[]. La
raison en est que l'operator[] prend toujours un et un seul paramètre, alors que l'operator() peut lui prendre autant de
paramètres qu'il est nécessaire (dans le cas d'une matrice rectangulaire, vous avez besoin de deux paramètres).

class Matrix {
public:
Matrix(unsigned rows, unsigned cols);
double& operator() (unsigned row, unsigned col);
double operator() (unsigned row, unsigned col) const;
...
~Matrix(); // Destructeur
Matrix(const Matrix& m); // Constructeur de copie
Matrix& operator= (const Matrix& m); // Opérateur d'assignement
...
private:
unsigned rows_, cols_;
double* data_;
};

inline Matrix::Matrix(unsigned rows, unsigned cols)


: rows_ (rows)
, cols_ (cols)
// data_ est initialisé en suivant (après l'instruction 'if/throw')
{
if (rows == 0 || cols == 0)
throw BadIndex("Matrix constructor has 0 size");
data_ = new double[rows * cols];
}

inline Matrix::~Matrix()
{
delete[] data_;
}

inline double& Matrix::operator() (unsigned row, unsigned col)


{
if (row >= rows_ || col >= cols_)
throw BadIndex("Matrix subscript out of bounds");
return data_[cols_*row + col];
}

inline double Matrix::operator() (unsigned row, unsigned col) const


{
if (row >= rows_ || col >= cols_)
throw BadIndex("const Matrix subscript out of bounds");
return data_[cols_*row + col];
}

Ainsi, l'accès à un élément de la Matrix m se fait en utilisant m(i,j) plutôt que m[j]:

- 137 -
Les sources présentées sur cette pages sont libres de droits, et vous pouvez les utiliser à votre convenance. Par contre la page de présentation de ces sources
constitue une oeuvre intellectuelle protégée par les droits d'auteurs. Copyright © 2008 Developpez LLC. Tout droits réservés Developpez LLC. Aucune
reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents et images sans l'autorisation expresse de Developpez
LLC. Sinon vous encourez selon la loi jusqu'à 3 ans de prison et jusqu'à 300 000 E de dommages et intérêts.
http://cpp.developpez.com/faq/cpp/
int main()
{
Matrix m(10,10);
m(5,8) = 106.15;
std::cout << m(5,8);
//...
}

Pourquoi est-il préférable que l'interface de ma classe Matrix


ne soit pas basée sur le modèle du tableau de tableaux ?
Auteurs : Marshall Cline ,
De quoi cette question traite-t-elle exactement ? Certains programmeurs créent des classes Matrix et leur donnent un
operator[] qui renvoie une référence à un objet Array, objet Array qui lui-même possède un operator[] qui renvoie un
élément de la matrice (par exemple, une référence sur un double). Ça leur permet d'accéder aux éléments de la matrice
en utilisant la syntaxe m[j] plutôt qu'une syntaxe de type m(i,j) .

Cette solution de tableau de tableaux fonctionne, mais elle est moins flexible que la solution basée sur l'operator() . En
effet, l'approche utilisant l'operator() offre certaines possibilités d'optimisation qui sont plus difficilement réalisables
avec l'approche operator[][]. Cette dernière approche est donc plus susceptible de causer, au moins dans un certain
nombre de cas, des problèmes de performances.

Pour vous donner un exemple, la façon la plus simple d'implémenter l'approche operator[][] consiste à représenter
physiquement la matrice comme une matrice dense stockant ses éléments en ligne (ou bien est-ce plutôt un stockage
en colonne, je ne m'en souviens jamais). L'approche utilisant l'operator() cache elle complètement la représentation
physique de la matrice, ce qui peut dans certains cas donner de meilleures performances.

En résumé : l'approche basée sur l'operator() n'est jamais moins bonne et s'avère parfois meilleure que l'approche
operator[][].

• L'approche operator() n'est jamais moins bonne car il est facile de l'implémenter en utilisant la représentation
physique "matrice dense - stockage en ligne". Et donc dans les cas où cette représentation physique est la plus
adaptée d'un point de vue performance, l'approche operator() est aussi facile à implémenter que l'approche
operator[][] (il se pourrait même que l'approche operator() soit légèrement plus facile à implémenter, mais je ne
vais pas pinailler).
• L'approche operator() s'avère parfois meilleure car à partir du moment où la représentation physique optimale
n'est pas la représentation "matrice dense - stockage en ligne", il est le plus souvent sensiblement plus facile
d'implémenter l'approche operator() que l'approche operator[][].

J'ai travaillé récemment sur un projet qui a illustré l'importance de la différence que peut faire le choix de la
représentation physique. L'accès aux éléments de la matrice y était fait colonne par colonne (l'algorithme accédait
aux éléments d'une colonne, puis de la suivante, etc.), et dans ce cas, une représentation physique en ligne risquait de
diminuer l'efficacité de la mémoire cache. En effet, si les lignes sont presque aussi grosses que la taille du cache du
processeur, chaque accès à l'élément suivant dans la colonne va demander à ce que la ligne suivante soit chargée dans
le cache, ce qui fait perdre l'avantage que procure un cache. Sur ce projet, nous avons gagné 20% en performance en
découplant la représentation logique de la matrice (ligne, colonne) de sa représentation physique (colonne, ligne).

Des exemples de ce type, on en trouve en quantité en calcul numérique et quand on s'attaque au vaste sujet que
représentent les matrices creuses. Au final, puisqu'il est en général plus facile d'implémenter une matrice creuse ou
d'inverser l'ordre des lignes et des colonnes en utilisant l'operator(), vous n'avez rien à perdre et possiblement quelque
chose à gagner à utiliser cette approche.

- 138 -
Les sources présentées sur cette pages sont libres de droits, et vous pouvez les utiliser à votre convenance. Par contre la page de présentation de ces sources
constitue une oeuvre intellectuelle protégée par les droits d'auteurs. Copyright © 2008 Developpez LLC. Tout droits réservés Developpez LLC. Aucune
reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents et images sans l'autorisation expresse de Developpez
LLC. Sinon vous encourez selon la loi jusqu'à 3 ans de prison et jusqu'à 300 000 E de dommages et intérêts.
http://cpp.developpez.com/faq/cpp/
Utilisez l'approche basée sur l'operator()

Comment surcharger les opérateurs ++ et -- ?


Auteurs : Marshall Cline ,
Via un paramètre bidon.

Etant donné que ces opérateurs peuvent avoir deux définitions, le C++ leur donne deux signatures différentes. Les deux
s'appellent operator ++(), mais la version préincrémentation ne prend pas de paramètre, et l'autre prend un entier
bidon. Nous traiterons ici le cas de ++, mais l'opérateur -- se comporte de façon similaire. Tout ce qui s'applique à l'un
s'applique donc à l'autre.

class Number {
public:
Number& operator++ (); // prefix ++
Number operator++ (int); // postfix ++
};

A remarquer : la différence des types de retour. La version préfixée renvoie par référence, la postfixée par valeur. Si
cela semble inattendu, ce sera tout à fait logique après avoir examiné les définitions (vous vous souviendrez ensuite que
y = x++ et y = ++x affectent des résultats différents à y).

Number& Number::operator++ ()
{
// ...
return *this;
}

Number Number::operator++ (int)


{
Number ans = *this;
++(*this); // ou appeler simplement operator++()
return ans;
}

L'autre possibilité pour la version postfixée est de ne rien renvoyer :

class Number {
public:
Number& operator++ ();
void operator++ (int);
};

Number& Number::operator++ ()
{
//...
return *this;
}

void Number::operator++ (int)


{
++(*this); // ou appeler simplement operator++()
}

Attention, il ne faut pas que la version postfixée renvoie l'objet 'this' par référence, vous aurez été prévenus.

Voici comment utiliser ces opérateurs :

- 139 -
Les sources présentées sur cette pages sont libres de droits, et vous pouvez les utiliser à votre convenance. Par contre la page de présentation de ces sources
constitue une oeuvre intellectuelle protégée par les droits d'auteurs. Copyright © 2008 Developpez LLC. Tout droits réservés Developpez LLC. Aucune
reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents et images sans l'autorisation expresse de Developpez
LLC. Sinon vous encourez selon la loi jusqu'à 3 ans de prison et jusqu'à 300 000 E de dommages et intérêts.
http://cpp.developpez.com/faq/cpp/
Number x = /* ... */;
++x; // appel de Number::operator++(), c-a-d x.operator++()
x++; // appel de Number::operator++(int), c-a-d calls x.operator++(0)

Supposant que les types de retour ne sont pas void, on peut les utiliser dans des expressions plus complexes

Number x = /* ... */;


Number y = ++x; // y aura le nouvelle valeur de x
Number z = x++; // z aura la nouvelle valeur de x

Qu'est-ce qui est le plus rapide : i++ ou ++i ?


Auteurs : Marshall Cline ,
++i est parfois plus rapide que i++, mais en tout cas n'est jamais plus lent.

Pour les types de base comme les entiers, cela n'a aucune importance : i++ et ++i sont identiques point de vue rapidité.
Pour des types manipulant des classes, comme les itérateurs par exemple, ++i peut être plus rapide que i++ étant donné
que ce dernier peut prendre une copie de l'objet 'this.'

La différence, pour autant qu'il y en ait une, n'aura aucune influence à moins que votre application soit très dépendante
de la vitesse du CPU. Par exemple, si votre application attend la plupart du temps que l'utilisateur clique sur la souris,
ou qu'elle fasse des accès disques, ou des accès réseau, ou des recherches dans une base de données, cela ne risque pas
de poser problème que de perdre quelques cycles CPU.

Si vous écrivez i++ comme une instruction isolée plutôt que comme une partie d'une expression plus complexe,
pourquoi ne pas plutôt écrire ++i ? Vous ne perdrez jamais rien, et parfois même vous y gagneriez quelque chose. Les
programmeurs habitués à faire du C ont l'habitude d'écrire i++ plutôt que ++i. Par exemple, ils écrivent

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

Comme cette expression utilise i++ comme une instruction isolée, nous pourrions tout à fait écrire ++i à la place. Pour
des raisons de symétrie, j'ai une préférence pour ce style même si cela n'apporte rien au point de vue performance.

De toute évidence, quand i++ apparaît en tant que partie d'une expression plus complexe, la situation est différente :
il est utilisé parce que c'est la seule solution logique et correcte et non pas parce qu'il s'agit d'une habitude héritée de
l'époque ou l'on codait du C.

Qu'est-ce que l'auto-affectation ?


Auteurs : Laurent Gomila , Marshall Cline ,
Une auto-affectation a lieu quand quelqu'un affecte un objet à lui-même.

#include "Fred.hpp" // Déclaration de la classe Fred

void userCode(Fred& x)
{
x = x; // Auto-affectation
}

- 140 -
Les sources présentées sur cette pages sont libres de droits, et vous pouvez les utiliser à votre convenance. Par contre la page de présentation de ces sources
constitue une oeuvre intellectuelle protégée par les droits d'auteurs. Copyright © 2008 Developpez LLC. Tout droits réservés Developpez LLC. Aucune
reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents et images sans l'autorisation expresse de Developpez
LLC. Sinon vous encourez selon la loi jusqu'à 3 ans de prison et jusqu'à 300 000 E de dommages et intérêts.
http://cpp.developpez.com/faq/cpp/
Bien évidemment, personne n'écrit du code pareil, mais parce que des pointeurs ou des références distinctes peuvent
désigner le même objet (c'est l'aliasing), des auto-affectations peuvent avoir lieu derrière votre dos.

void userCode(Fred& x, Fred& y)


{
x = y; // C'est une auto-affectation si &x == &y
}

int main()
{
Fred z;
userCode(z, z);

return 0;
}

Pourquoi parle-t-on de l'auto-affectation ? Parce qu'elle peut être dangereuse. Imaginez une classe gérant un pointeur
brut, et son opérateur d'affectation :

class MaClasse
{
private :

Ressource* ptr;

public :

MaClasse& operator =(const MaClasse& Other)


{
// Destruction de ptr
delete this->ptr;

// Réallocation et affectation de ptr


this->ptr = new int(*Other.ptr);

return *this;
}
};

Dans le cas d'une auto-affectation, this et Other pointent vers la même instance, et donc vers le même ptr. Je vous laisse
imaginer ce qu'il se passe lorsqu'on essaye de lire Other.ptr alors qu'il vient d'être détruit à la ligne précédente.

Pour éviter les problèmes d'auto-affectation, ou simplement pour tenter d'optimiser le code, on ajoute souvent un simple
test permettant de vérifier que les deux instances sont différentes ; dans le cas contraire on peut quitter sans effectuer
d'affectation.

MaClasse& MaClasse::operator =(const MaClasse& Other)


{
if (this != &Other)
{
// Destruction de ptr
delete this->ptr;

// Réallocation et affectation de ptr


this->ptr = new int(*Other.ptr);
}

return *this;
}

- 141 -
Les sources présentées sur cette pages sont libres de droits, et vous pouvez les utiliser à votre convenance. Par contre la page de présentation de ces sources
constitue une oeuvre intellectuelle protégée par les droits d'auteurs. Copyright © 2008 Developpez LLC. Tout droits réservés Developpez LLC. Aucune
reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents et images sans l'autorisation expresse de Developpez
LLC. Sinon vous encourez selon la loi jusqu'à 3 ans de prison et jusqu'à 300 000 E de dommages et intérêts.
http://cpp.developpez.com/faq/cpp/
Mais attention ce code aussi est un piège : en effet nous ajoutons un test que l'on croit utile, mais qui sera dans 99.9% des
cas effectué pour rien (n'oubliez pas que l'auto-affectation est tout de même très rare). D'autant plus que si vous écrivez
correctement votre opérateur d'affectation, comme indiqué dans la question Comment écrire un opérateur d'affectation
correct ?, les éventuels problèmes d'auto-affectation sont résolus automatiquement de manière élégante.

Comment écrire un opérateur d'affectation correct ?


Auteurs : Laurent Gomila ,
La première chose à connaître est le prototype correct d'un opérateur d'affectation :

MaClasse& MaClasse::operator =(const MaClasse& Other)


{
// ...
return *this;
}

Le retour de this permettra de chaîner les affectations (a = b = c).


Le paramètre Other est pris par référence constante car celui-ci ne doit pas (sauf cas très spécifiques) être modifié par
la fonction ; cela permettra de recevoir ce que l'on appelle des temporaires non nommés (retours de fonction, objets
construits à la volée, ...). Une autre erreur potentielle, parfois commise par les débutants, est de mal comprendre le
fonctionnement de l'opérateur = et d'affecter this à Other ; la référence constante provoquera des erreurs de compilation
et permettra de le détecter immédiatement.
Quant au fait que l'on renvoie une référence non constante, il y a deux raisons à cela. La première étant que cela assure
que nous ne serons pas tentés de renvoyer le paramètre (Other ici), ce qui serait incorrect ; la seconde raison est que
le retour d'une affectation peut être modifié (donc non constant) pour les types primitifs, par souci de cohérence il est
donc bon de faire de même pour les types que vous écrivez.

La seconde chose à savoir est que le compilateur génère un opérateur d'affectation automatiquement si vous ne le faites
pas, et que celui-ci sera suffisant dans la plupart des cas. Vous pouvez donc parfois tout simplement éviter de l'écrire.

Il faudra écrire explicitement un opérateur d'affectation dès lors qu'une simple affectation membre à membre des
données de votre classe n'est plus suffisante, par exemple si elle gère une ressource (un pointeur brut, une connexion
à une base de données, une texture en mémoire vidéo, ...).

Il existe également des situations où l'on ne veut pas de l'opérateur d'affectation généré par le compilateur, notamment
pour les classes dont les instances ne doivent pas être copiées (une base de données, un singleton, etc.). Dans ce cas, une
bonne pratique est d'en interdire l'utilisation en le déclarant privé et en ne le définissant pas (ie. ne pas écrire son corps).
Il en va d'ailleurs de même pour le constructeur par copie, les deux allant généralement de paire.

Pour définir l'opérateur d'affectation d'une classe gérant une ressource brute, on serait tenté d'écrire ce genre de code :

MaClasse& MaClasse::operator =(const MaClasse& Other)


{
// Destruction de la ressource (ici un pointeur brut)
delete this->Ressource;

// Allocation d'une nouvelle


this->Ressource = new ClasseRessouce;

// Copie de la ressource
*this->Ressource = *Other.Ressource;

return *this;

- 142 -
Les sources présentées sur cette pages sont libres de droits, et vous pouvez les utiliser à votre convenance. Par contre la page de présentation de ces sources
constitue une oeuvre intellectuelle protégée par les droits d'auteurs. Copyright © 2008 Developpez LLC. Tout droits réservés Developpez LLC. Aucune
reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents et images sans l'autorisation expresse de Developpez
LLC. Sinon vous encourez selon la loi jusqu'à 3 ans de prison et jusqu'à 300 000 E de dommages et intérêts.
http://cpp.developpez.com/faq/cpp/
}

Mais ce code est incorrect. En effet, que se passe-t-il si l'on se trouve dans le cas (rare) d'une auto-affectation ? this et
Other seront la même instance, et pointeront donc sur la même ressource. Au moment où l'on va tenter de la copier,
elle aura été détruite, provoquant un comportement indéterminé.

De même, imaginez que l'appel à new échoue (ce qui peut très bien arriver) : la ressource aura déjà été détruite, mais
ne sera pas recréée, laissant l'objet dans un état invalide, et menant là encore à des comportements indéterminés.

La solution à cela est d'effectuer toutes les allocations (plus généralement, les choses qui sont susceptibles de lever des
exceptions) en premier, puis si tout a réussi, alors on peut effectuer les libérations.

Voici une version plus correcte de l'opérateur d'affectation précédent :

MaClasse& MaClasse::operator =(const MaClasse& Other)


{
// Réallocation de la ressource
ClasseRessource* NouvelleRessource = new ClasseRessource(Other.Ressource);

// Destruction de la ressource
delete this->Ressource;

// Réaffectation de la nouvelle
this->Ressource = NouvelleRessource;

return *this;
}

Cette version est correcte sur tous les plans : en cas d'Qu'est-ce que l'auto-affectation ? la ressource sera bien recopiée
avant d'être détruite, et en cas d'exception pendant l'allocation, l'objet sera toujours tel qu'il était avant l'appel à
l'opérateur d'affectation.

Nous pouvons aller encore un peu plus loin, en constatant que finalement tout ceci est déjà plus ou moins implémenté
dans votre classe. En effet, si celle-ci est bien codée, la réallocation de la ressource est exactement le boulot du
constructeur par copie, et la destruction celui du destructeur.

Ainsi une version plus élégante du code précédent serait la suivante :

MaClasse& MaClasse::operator =(const MaClasse& Other)


{
// Utilisation du constructeur par copie pour copier la ressource MaClasse
MaClasse Temp(Other);

// Réaffectation : on prend les nouvelles données dans Temp, et on lui


// donne les données à détruire en échange
std::swap(Temp.Ressource, this->Ressource);

return *this;
} // A la fin de la fonction, Temp sera détruit automatiquement
// et son destructeur désallouera correctement la ressource

Avec donc un constructeur par copie et un destructeur correctement implémentés :

// Le constructeur par copie


MaClasse::MaClasse(const MaClasse& Other)

- 143 -
Les sources présentées sur cette pages sont libres de droits, et vous pouvez les utiliser à votre convenance. Par contre la page de présentation de ces sources
constitue une oeuvre intellectuelle protégée par les droits d'auteurs. Copyright © 2008 Developpez LLC. Tout droits réservés Developpez LLC. Aucune
reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents et images sans l'autorisation expresse de Developpez
LLC. Sinon vous encourez selon la loi jusqu'à 3 ans de prison et jusqu'à 300 000 E de dommages et intérêts.
http://cpp.developpez.com/faq/cpp/
{
if (Other.Ressource)
this->Ressource = new ClasseRessource(*Other.Ressource);
else
this->Ressource = NULL;
}

// Le destructeur
MaClasse::~MaClasse()
{
delete this->Ressource;
}

J'ai pour surcharger mon opérateur la possibilité d'utiliser


une fonction membre ou une fonction libre, que choisir ?
Auteurs : JolyLoic ,
Par exemple, pour définir un opérateur + dans une classe A, on peut écrire :

Version avec une fonction membre


class A
{
A operator+(A const & second);
};

Version avec une fonction libre


class A
{
};

A operator+(A const &first, A const &second);

Quelle est la version préférable ? En général, pour un opérateur binaire, il s'agit de la fonction libre, car elle respecte
la symétrie que l'on s'attend à trouver entre les opérandes d'un tel opérateur, alors que la fonction membre considère
que l'élément sur lequel elle agit (le this) doit être exactement du type voulu.

En particulier, imaginons que l'on puisse convertir un entier en une variable de type A (par exemple si A possède un
constructeur non explicite prenant uniquement un entier en paramètre). Alors on a le comportement suivant :

Version avec une fonction membre


A a1, a2;
a1 + a2; // Ok
a1 + 42; // Ok
42 + a2; // Ne compile pas

Version avec une fonction libre


A a1, a2;
a1 + a2; // Ok
a1 + 42; // Ok
42 + a2; // Ok

- 144 -
Les sources présentées sur cette pages sont libres de droits, et vous pouvez les utiliser à votre convenance. Par contre la page de présentation de ces sources
constitue une oeuvre intellectuelle protégée par les droits d'auteurs. Copyright © 2008 Developpez LLC. Tout droits réservés Developpez LLC. Aucune
reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents et images sans l'autorisation expresse de Developpez
LLC. Sinon vous encourez selon la loi jusqu'à 3 ans de prison et jusqu'à 300 000 E de dommages et intérêts.
http://cpp.developpez.com/faq/cpp/
Il y a par contre des opérateurs que l'on n'a le droit de surcharger que comme fonction membre : operator=, operator(),
operator[] et operator->.

Comment surcharger correctement l'opérateur == ?


Auteurs : JolyLoic ,
Bien qu'il soit possible de faire ce que l'on veut quand on surcharge un opérateur, il y a des règles à respecter si on a
envie que notre surcharge marche bien avec le reste du langage, et sans mauvaise surprise pour l'utilisateur. Ainsi, un
opérateur == qui modifierait ses paramètres serait très malvenu.

En plus de ces règles de bon sens, un opérateur == bien éduqué doit répondre à des critères supplémentaires, ce que les
mathématiciens indiquent en disant qu'il doit définir une relation d'équivalence. Voici ces critères :

• Pour tout a, a==a (un objet doit être identique à lui même).
• Pour tout a et b, si a==b, alors b==a (être égal marche dans les deux sens).
• Pour tout a, b et c, si a==b et b==c, alors a==c (on dit que c'est transitif).

Généralement, on écrit naturellement des opérateur == qui respectent ces règles, sans même le savoir, mais il y a quand
même des possibilités d'erreur. Un exemple de code qui ne marche pas :

class Double
{
public:
Double(double d) : myValue(d) {}
double val() {return myValue;}
private:
double myValue;
};

bool operator== (Double const &d1, Double const &d2)


{
static double const epsilon = 0.001;
return d2.val() - d1.val() < epsilon;
}

Le problème avec ce code, pourtant bien intentionné, qui veut définir que deux Doubles sont à considérer comme
identiques s'ils sont suffisamment proches l'un de l'autre est qu'il ne respecte pas la condition de transitivité. Ainsi :

Double a(0), b(0.0009), c(0.0011);


assert(a==b); // Ok
assert(b==c); // Ok
assert(a==c); // Erreur

Remarque : Ces règles s'appliquent aussi à un prédicat de type égalité que l'on passerait à un algorithme.

Comment surcharger correctement l'opérateur < ?


Auteurs : JolyLoic ,
De même que l'opérateur ==, l'opérateur < se doit de respecter certaines règles. Ces règles sont moins évidentes que
pour l'opérateur ==, aussi est-il facile de se tromper. Les voici :

- 145 -
Les sources présentées sur cette pages sont libres de droits, et vous pouvez les utiliser à votre convenance. Par contre la page de présentation de ces sources
constitue une oeuvre intellectuelle protégée par les droits d'auteurs. Copyright © 2008 Developpez LLC. Tout droits réservés Developpez LLC. Aucune
reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents et images sans l'autorisation expresse de Developpez
LLC. Sinon vous encourez selon la loi jusqu'à 3 ans de prison et jusqu'à 300 000 E de dommages et intérêts.
http://cpp.developpez.com/faq/cpp/
• Pour tout a, a<a est faux.
• Pour tout a, b et c, si a<b et b<c alors a<c.
• Pour tout a et b, si a<b est faux et b<a est faux, on dit que a et b sont équivalents (cette relation doit être une
relation d'équivalence, comme précisée dans la question sur l'Comment surcharger correctement l'opérateur
== ?.

Un cas classique où l'on a besoin de définir l'opérateur < est quand un objet se compose de sous-objets, et que la relation
d'ordre sur les objets dépend de celle des sous-objets. On voit souvent du code comme :

class Personne
{
public:
string nom;
string prenom;
};

bool operator< (Personne const &p1, Personne const &p2)


{
return p1.nom < p2.nom && p1.prenom < p2.prenom;
}

Si l'on prend par exemple les personnes suivantes : a = {"Stroustrup", "Bjarne"} et b = {"Clamage", "Steve"} on a :

• a<b qui est faux (car "Stroustrup" < "Clamage" est faux) ;
• b<a qui est faux (car "Steve" < "Bjarne" est faux).

Ce qui implique que ces deux personnes sont équivalentes (si on insérait les deux dans une map, par exemple, la map
ne contiendrait qu'un seul élément).

J'ai souvent vu cette tentative de correction :

bool operator< (Personne const &p1, Personne const &p2)


{
if (p1.nom < p2.nom)
return true;
else
return p1.prenom < p2.prenom;
}

Mais ce code ne marche pas non plus (cette fois ci, on a a<b et b<a qui sont tous deux simultanément vrais). Une bonne
solution ressemble plutôt à :

bool operator< (Personne const &p1, Personne const &p2)


{
if (p1.nom < p2.nom)
return true;
else if (p2.nom < p1.nom)
return false;
else
return p1.prenom < p2.prenom;
}

Remarque : Ces règles s'appliquent aussi à un prédicat de type inférieur que l'on passerait à un algorithme ou en
argument d'un conteneur. On pourrait imaginer des règles semblables pour la surcharge des opérateurs <, <=,... mais

- 146 -
Les sources présentées sur cette pages sont libres de droits, et vous pouvez les utiliser à votre convenance. Par contre la page de présentation de ces sources
constitue une oeuvre intellectuelle protégée par les droits d'auteurs. Copyright © 2008 Developpez LLC. Tout droits réservés Developpez LLC. Aucune
reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents et images sans l'autorisation expresse de Developpez
LLC. Sinon vous encourez selon la loi jusqu'à 3 ans de prison et jusqu'à 300 000 E de dommages et intérêts.
http://cpp.developpez.com/faq/cpp/
en pratique, comme, sauf surprise, ces opérateurs sont reliés entre eux, c'est devenu une habitude en C++ de se limiter
à l'utilisation de < dans les algorithmes et conteneurs.

- 147 -
Les sources présentées sur cette pages sont libres de droits, et vous pouvez les utiliser à votre convenance. Par contre la page de présentation de ces sources
constitue une oeuvre intellectuelle protégée par les droits d'auteurs. Copyright © 2008 Developpez LLC. Tout droits réservés Developpez LLC. Aucune
reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents et images sans l'autorisation expresse de Developpez
LLC. Sinon vous encourez selon la loi jusqu'à 3 ans de prison et jusqu'à 300 000 E de dommages et intérêts.
http://cpp.developpez.com/faq/cpp/
Sommaire > Gestion dynamique de la mémoire
Comment allouer de la mémoire ?
Auteurs : LFE ,
En C, l'allocation dynamique de mémoire se faisait avec la fonction malloc(). En C++, l'allocation de mémoire se fait
avec l'opérateur new.

A noter que la fonction malloc() fonctionne toujours en C++ comme en C.

lien : Comment libérer de la mémoire ?

Que se passe-t-il si new ne parvient pas à allouer la mémoire demandée ?


Auteurs : Aurélien Regat-Barrel ,
En cas d'échec d'allocation de new, une exception std::bad_alloc est levée. Cependant certains compilateurs un peu
anciens (tel que Visual C++ 6) se contentent de renvoyer un pointeur nul (zéro donc). Soyez donc vigilant !

Pourquoi utiliser new plutôt que malloc ?


Auteurs : LFE ,
Sur les types des base (int, char, float, ...) l'intérêt n'est pas énorme. Par contre, sur les classes, plutôt que simplement
allouer la place mémoire pour stocker l'objet, il y a un appel au constructeur qui se fait et qui permet donc d'initialiser
correctement l'objet.

De même, delete appelle le destructeur de la classe, alors que free() ne le fait pas.

MaClasse *BonObjet, *MauvaisObjet;

MauvaisObjet = malloc(sizeof(MaClasse)); // alloue de la mémoire mais n'appelle pas le constructeur


BonObjet = new MaClasse; // alloue de la mémoire et appelle le constructeur.

Comment libérer de la mémoire ?


Auteurs : LFE ,
En C, la libération de mémoire se fait avec la fonction free(). En C++, la libération d'un pointeur se fait avec l'opérateur
delete.

A noter que la fonction free() fonctionne toujours en C++ comme en C.

- 148 -
Les sources présentées sur cette pages sont libres de droits, et vous pouvez les utiliser à votre convenance. Par contre la page de présentation de ces sources
constitue une oeuvre intellectuelle protégée par les droits d'auteurs. Copyright © 2008 Developpez LLC. Tout droits réservés Developpez LLC. Aucune
reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents et images sans l'autorisation expresse de Developpez
LLC. Sinon vous encourez selon la loi jusqu'à 3 ans de prison et jusqu'à 300 000 E de dommages et intérêts.
http://cpp.developpez.com/faq/cpp/
Le delete fonctionne de la façon suivante : il appelle (implicitement) le destructeur de la classe et puis libère le pointeur.

lien : Comment allouer de la mémoire ?

Que se passe-t-il si je fais un delete sur un pointeur qui vaut NULL ?


Auteurs : LFE ,
Il ne se passe rien du tout. On peut considérer qu'un delete sur un pointeur NULL est purement et simplement ignoré,
ce qui permet d'éviter de devoir faire ce contrôle.

Puis-je utiliser free() pour libérer un pointeur alloué par new ?


Auteurs : LFE ,
La réponse est négative. Un pointeur alloué par new doit être libéré par un delete et un pointeur alloué par malloc()
doit être libéré par free().

Il est tout à fait possible que ce type d'allocation/libération fonctionne parfaitement sur un compilateur mais donnera
des résultats tout à fait imprévisibles sur un autre.

Comment allouer dynamiquement un tableau ?


Auteurs : LFE , Aurélien Regat-Barrel ,
Allouer un tableau dynamiquement en C++ se fait grâce à l'opérateur new []. Ce pointeur doit être libéré avec
l'opérateur delete [].
Un des avantages de new par rapport au malloc du C est que le constructeur par défaut des objets alloués est
automatiquement appelé. Il en est de même pour leur destructeur lors de l'appel à delete [].

int * tableau = new int[ 10] ; // alloue un tableau de 10 entiers

delete [] tableau; // ATTENTION : ne pas oublier les crochets []

En C++ on préfère utiliser un std::vector issu de la STL car ce dernier gère seul l'allocation, la réallocation (pour
grossir) ainsi que la libération de la mémoire. Pour plus d'informations sur std::vector, lire Comment créer et utiliser
un tableau avec std::vector ?.

lien : Comment libérer un tableau alloué dynamiquement ?

Comment libérer un tableau alloué dynamiquement ?


Auteurs : LFE , Aurélien Regat-Barrel ,
Libérer un tableau alloué dynamiquement en C++ se fait grâce à l'opérateur delete [].

int * tableau = new int[ 10 ];


delete [] tableau;

delete [] se charge d'appeler le destructeur de chaque objet du tableau.


Notez qu'il n'y a pas besoin de spécifier le nombre d'éléments à libérer. Cette information est conservée au moment de
l'allocation du tableau avec new [].

- 149 -
Les sources présentées sur cette pages sont libres de droits, et vous pouvez les utiliser à votre convenance. Par contre la page de présentation de ces sources
constitue une oeuvre intellectuelle protégée par les droits d'auteurs. Copyright © 2008 Developpez LLC. Tout droits réservés Developpez LLC. Aucune
reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents et images sans l'autorisation expresse de Developpez
LLC. Sinon vous encourez selon la loi jusqu'à 3 ans de prison et jusqu'à 300 000 E de dommages et intérêts.
http://cpp.developpez.com/faq/cpp/
Une erreur grave et fréquente est d'oublier les crochets après le mot-clé delete. Malheureusement cette erreur n'est pas
détectée à la compilation, mais seulement à l'exécution pour les meilleurs compilateurs lors d'une exécution en mode
de débogage. Cette détection se traduit généralement par un message de corruption de la mémoire. En effet, appeler
delete au lieu de delete [] provoque un comportement indéfini par la norme, qui se traduit souvent par un plantage pur
et simple, ou par un fonctionnement anormal du programme (mémoire qui semble être modifiée toute seule, ...). Donc
en cas de problème de ce genre, vérifiez vos delete [] !
Pour cette raison, et bien d'autres, on évite d'allouer des tableaux en C++. On préfère utiliser std::vector qui fait tout
correctement à notre place. Pour plus d'informations lire Comment créer et utiliser un tableau avec std::vector ?.

delete [] mesObjets ; // libère le tableau mesObjets

lien : Comment allouer dynamiquement un tableau ?

Comment allouer dynamiquement un tableau à plusieurs dimensions ?


Auteurs : Bob ,
Pour chaque dimension il faut créer un tableau de pointeurs sur des éléments de la dimension suivante. Par exemple,
pour un tableau d'entiers à 2 dimensions, il faut créer un tableau de pointeurs sur des entiers et initialiser chaque
pointeur avec un tableau d'entiers.

#include <algorithm> // pour fill_n

// créer un tableau d'entiers à 2 dimensions [ 10 ][ 20 ]


const int dim1 = 10; // taille de la dimension 1
const int dim2 = 20; // taille de la dimension 2
int dim_allouee = 0; // nombre d'éléments alloués avec succès sur la dimension 2
int * * Tab = 0;
// tenter d'allouer la taille demandée dim1 x dim2
try
{
// dimension 1 : tableau de 10 pointeurs vers des tableaux d'entiers
Tab = new int * [ dim1 ];
// initialiser les 10 pointeurs à 0 (NULL)
std::fill_n( Tab, dim1, static_cast<int*>( 0 ) );

// dimension 2 : les tableaux d'entiers


for ( dim_allouee = 0; dim_allouee < dim1; ++dim_allouee)
{
Tab[ dim_allouee ] = new int[ dim2 ];
}
}
catch ( const std::bad_alloc & ) // erreur d'allocation
{
// désallouer tout ce qui a été alloué avec succès
for ( int i = 0; i < dim_allouee; ++i )
{
delete [] Tab[ i ];
}
delete [] Tab;
}

Le code précédent évite les fuites de mémoire en cas d'erreur d'allocation donnant lieu à une exception std::bad_alloc.
Cette exception n'est pas déclenchée par les compilateurs un peu anciens (comme Visual C++ 6 par exemple), et new
renvoie à la place un pointeur nul. Si vous utilisez un tel compilateur il convient de modifier le code proposé en
conséquences.

- 150 -
Les sources présentées sur cette pages sont libres de droits, et vous pouvez les utiliser à votre convenance. Par contre la page de présentation de ces sources
constitue une oeuvre intellectuelle protégée par les droits d'auteurs. Copyright © 2008 Developpez LLC. Tout droits réservés Developpez LLC. Aucune
reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents et images sans l'autorisation expresse de Developpez
LLC. Sinon vous encourez selon la loi jusqu'à 3 ans de prison et jusqu'à 300 000 E de dommages et intérêts.
http://cpp.developpez.com/faq/cpp/
En revanche quelque soit votre compilateur le fait d'appeler delete [] sur un pointeur nul est sans conséquence (voir
Que se passe-t-il si je fais un delete sur un pointeur qui vaut NULL ?).

Comment libérer un tableau à plusieurs dimensions alloué dynamiquement ?


Auteurs : Bob ,
int** Tab;
int i;

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


delete [] Tab[i];
}

delete []Tab;

Voir aussi [Exemple] Comment détruire les pointeurs d'un conteneur ?.

Comment réallouer / agrandir une zone mémoire ?


Auteurs : Laurent Gomila ,
En C, on pouvait utiliser realloc pour agrandir un espace mémoire alloué dynamiquement. Cependant cette fonction
est à éviter en C++ : elle n'est garantie de fonctionner qu'avec la mémoire allouée via malloc (voir Pourquoi utiliser
new plutôt que malloc ?).

Pour agrandir une zone (généralement un tableau) allouée via l'opérateur new il faudra faire la manipulation à la main :
- Allouer un nouvel espace mémoire de la taille souhaitée
- Y copier son contenu
- Libérer l'ancien espace mémoire

Tout ceci étant fastidieux et difficile à maintenir, en C++ on utilise simplement std::vector lorsqu'il s'agit de tableaux, qui
fera tout cela automatiquement et bien plus efficacement. Voir Comment créer et utiliser un tableau avec std::vector ?.

Comment récupérer la taille d'un tableau dynamique ?


Auteurs : Laurent Gomila , Aurélien Regat-Barrel ,
Il est possible de récupérer la taille des tableaux statiques avec l'opérateur sizeof.

#include <iostream>

int Tab[50];
std::cout << sizeof(Tab) / sizeof(int); // affiche "50"

Ou encore via cette fonction template :

template<typename T, size_t N>


inline size_t length_of(T(&)[N])
{
return N;

- 151 -
Les sources présentées sur cette pages sont libres de droits, et vous pouvez les utiliser à votre convenance. Par contre la page de présentation de ces sources
constitue une oeuvre intellectuelle protégée par les droits d'auteurs. Copyright © 2008 Developpez LLC. Tout droits réservés Developpez LLC. Aucune
reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents et images sans l'autorisation expresse de Developpez
LLC. Sinon vous encourez selon la loi jusqu'à 3 ans de prison et jusqu'à 300 000 E de dommages et intérêts.
http://cpp.developpez.com/faq/cpp/
}

Si vous voulez obtenir la taille sous forme d'une constante connue à la compilation, vous pouvez utiliser cette variante :

template <std::size_t N>


struct array {char value[N];};

template<typename T, std::size_t N>


array<N> length_of_helper(T (&)[N]);

#define length_of(array) (sizeof length_of_helper(array).value)

Pour un tableau dynamique alloué via new par contre, l'écriture utilisant sizeof ne renverra pas le résultat escompté. En
effet, cela renverra la taille du pointeur (généralement 4 octets sur plateforme 32 bits) et non la taille de la zone pointée.

#include <iostream>

int* Tab = new int[50];


std::cout << sizeof(Tab); // affiche "4"

On préfèrera donc utiliser la seconde écriture, à base de template, qui provoquera elle une erreur de compilation si l'on
tente de lui passer en paramètre un pointeur.

Vous l'aurez compris, il est donc impossible de récupérer la taille d'un tableau dynamique alloué avec new. Pour cela
il faudra stocker séparément sa taille, ou mieux : utiliser std::vector. Voir Comment créer et utiliser un tableau avec
std::vector ?.

#include <iostream>
#include <vector>

std::vector<int> Tab(50);
std::cout << Tab.size(); // affiche "50"

lien : Les utilisateurs de Visual C++ 2005 peuvent aussi se référer à la macro _countof

Peut-on déréférencer un pointeur NULL ?


Auteurs : LFE ,
La réponse est NON.
NULL étant une adresse non valide, *NULL donne une référence impossible.

Est-il possible de forcer new à allouer la mémoire à une adresse précise ?


Auteurs : Marshall Cline ,
Oui. La bonne nouvelle est que ces "pools de mémoire" sont utiles dans un certain nombre de situations. La mauvaise
nouvelle est qu'il va falloir descendre dans le "comment cela fonctionne" avant de voir comment on l'utilise. Si vous ne
savez pas comment fonctionnent les "pools de mémoire", ce sera chose réglée bientôt.

Avant tout, il faut savoir qu'un allocateur de mémoire est supposé retourner une zone de mémoire non initialisée, il n'est
pas supposé créer des objets. En particulier, l'allocateur de mémoire n'est pas supposé mettre à jour le pointeur virtuel
ou n'importe quelle autre partie de l'objet, étant donné que c'est le travail du constructeur qui est exécuté juste après

- 152 -
Les sources présentées sur cette pages sont libres de droits, et vous pouvez les utiliser à votre convenance. Par contre la page de présentation de ces sources
constitue une oeuvre intellectuelle protégée par les droits d'auteurs. Copyright © 2008 Developpez LLC. Tout droits réservés Developpez LLC. Aucune
reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents et images sans l'autorisation expresse de Developpez
LLC. Sinon vous encourez selon la loi jusqu'à 3 ans de prison et jusqu'à 300 000 E de dommages et intérêts.
http://cpp.developpez.com/faq/cpp/
l'allocation de la mémoire. En démarrant avec une simple fonction d'allocation de mémoire, allocate(), nous utilisons
placement new pour construire un objet dans cette mémoire. En d'autres mots, ce qui suit est moralement équivalent
à new Foo() :

void* raw = allocate(sizeof(Foo)); // ligne 1


Foo* p = new(raw) Foo(); // ligne 2

En supposant que l'on ait utilisé placement new et que l'on ait survécu au code précédent, l'étape suivante est de
transformer l'allocateur de mémoire en un objet. Ce type d'objet est appelé un pool mémoire. Cela permet aux
utilisateurs d'avoir plusieurs pools à partir desquels la mémoire peut être allouée. Chacun de ces pools mémoire allouera
une grande quantité de mémoire en utilisant un appel système spécifique (mémoire partagée, mémoire persistante,
etc ....) et le distribuera en petites quantités à la demande. Notre pool mémoire ressemblera à quelque chose de ce type :

class Pool
{
public:
void* alloc(size_t nbytes);
void dealloc(void* p);
private:
// données membres...
};

void* Pool::alloc(size_t nbytes)


{
// code d'allocation
}

void Pool::dealloc(void* p)
{
// code de libération
}

Maintenant, l'utilisateur devrait pouvoir obtenir un Pool (appelé pool), à partir duquel il pourra allouer des objets de
la façon suivante :

Pool pool;
// ...
void* raw = pool.alloc(sizeof(Foo));
Foo* p = new(raw) Foo();

ou encore :

Foo* p = new(pool.alloc(sizeof(Foo))) Foo();

La raison pour laquelle il serait bon de transformer Pool en une classe est que cela permet à l'utilisateur de créer N
pools mémoire différents, plutôt que d'avoir un gros pool partagé par tous les utilisateurs. Cela permet aux utilisateurs
de faire un tas de choses plus ou moins drôles. Par exemple, si l'on dispose de fonctions système permettant d'allouer
et de libérer une énorme quantité de mémoire, la totalité de la mémoire pourrait être allouée dans un pool, et ensuite
ne faire aucun delete des allocations faites dans ce pool, pour finalement libérer la totalité du pool en une fois. Ou il
serait possible de créer une zone de mémoire partagée (où le système d'exploitation procure de la mémoire partagée
entre différents processus) et que ce pool alloue des morceaux de mémoire partagée plutôt que de la mémoire locale
au processus.
La plupart des systèmes supportent une fonction alloca() qui alloue un bloc de mémoire sur la pile, plutôt que dans le
tas. Bien entendu, ce bloc de mémoire est libéré à la fin de la fonction, faisant disparaître le besoin de faire des delete
explicites. Quelqu'un pourrait utiliser alloca() pour attribuer au Pool sa mémoire, et que toutes les petites allocations
dans ce pool agiraient comme si elles étaient faites sur la pile : elles disparaîtraient à la fin de la fonction. Bien sûr, les
destructeurs ne seraient pas appelés dans n'importe lequel de ces cas, et si celui-ci devait faire des choses non triviales,

- 153 -
Les sources présentées sur cette pages sont libres de droits, et vous pouvez les utiliser à votre convenance. Par contre la page de présentation de ces sources
constitue une oeuvre intellectuelle protégée par les droits d'auteurs. Copyright © 2008 Developpez LLC. Tout droits réservés Developpez LLC. Aucune
reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents et images sans l'autorisation expresse de Developpez
LLC. Sinon vous encourez selon la loi jusqu'à 3 ans de prison et jusqu'à 300 000 E de dommages et intérêts.
http://cpp.developpez.com/faq/cpp/
il vous serait impossible d'utiliser ces techniques, mais dans le cas ou le destructeur ne fait que désallouer la mémoire,
ce genre de techniques peut être utiles.

Maintenant que l'on a inclus les quelques lignes de code nécessaires à l'allocation dans la classe Pool, l'étape suivante
est de changer la syntaxe d'allocation des objets. Le but est de transformer une allocation au format inhabituel
(new(pool.alloc(sizeof(Foo)))) en quelque chose de tout à fait classique (new(pool)). Pour y arriver, il faut ajouter les 2
lignes suivantes à la définition de la classe Pool

inline void* operator new(size_t nbytes, Pool& pool)


{
return pool.alloc(nbytes);
}

Maintenant, lorsque le compilateur rencontrera une instruction

new(pool) Foo()

l'opérateur new que l'on vient de définir passera sizeof( Foo ) et pool en tant que paramètres, et la seule fonction qui
manipulera le pool sera ce nouvel opérateur new.

Passons maintenant à la destruction de l'objet Foo. Il est à noter que l'approche brutale qui est parfois utilisée avec
placement new est d'appeler explicitement le destructeur et d'ensuite désallouer la mémoire :

void sample(Pool& pool)


{
Foo* p = new(pool) Foo();
// ...
p->~Foo(); // appel explicite du destructeur
pool.dealloc(p); // libération explicite de la mémoire
}

Ce code présente plusieurs problèmes, mais qui peuvent tous être réglés.

Il y aura une perte de mémoire si le constructeur lance une exception La syntaxe de destruction/désallocation n'est pas
conforme à ce que les programmeurs ont l'habitude de voir, ce qui va sûrement les perturber fortement.

L'utilisateur doit se rappeler d'une façon ou d'une autre des associations pool/objet. Etant donné que le code qui alloue
est souvent situé dans une autre fonction que celle qui libère, le programmeur devra manipuler deux pointeurs (un
pour la classe et un pour le pool), ce qui peut devenir rapidement indigeste (par exemple, un tableau d'objets Foo qui
seraient alloués dans des pools différents)

Nous allons régler ces problèmes.

Problème n° 1 : la fuite mémoire Quand on utilise l'opérateur new habituel, le compilateur génère un bout de code
particulier pour gérer le cas ou le constructeur lance une exception. Ce code ressemble à ceci :

principe
Foo* p;

// ne pas intercepter les exceptions lancées par l'allocateur


void* raw = operator new(sizeof(Foo));

// intercepter toute exception lancée par le constructeur


try {
p = new(raw) Foo(); // appel du constructeur avec 'raw' comme pointeur 'this'
} catch (...) {

- 154 -
Les sources présentées sur cette pages sont libres de droits, et vous pouvez les utiliser à votre convenance. Par contre la page de présentation de ces sources
constitue une oeuvre intellectuelle protégée par les droits d'auteurs. Copyright © 2008 Developpez LLC. Tout droits réservés Developpez LLC. Aucune
reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents et images sans l'autorisation expresse de Developpez
LLC. Sinon vous encourez selon la loi jusqu'à 3 ans de prison et jusqu'à 300 000 E de dommages et intérêts.
http://cpp.developpez.com/faq/cpp/
principe
// le constructeur a lancé une exception
operator delete(raw);
throw; // relancer l'exception du constructeur
}

Le point à remarquer est que le compilateur libère la mémoire si le constructeur lance une exception. Mais dans le cas
du "new avec paramètres" (appelé communément "new avec placement"), le compilateur ne sait pas quoi faire si une
exception est lancée, il ne fait donc rien.

principe
void* raw = operator new(sizeof(Foo), pool);
// cette fonction renvoie simplement "pool.alloc(sizeof(Foo))"

Foo* p = new(raw) Foo();


// si la ligne précédente provoque une exception, pool.dealloc(raw) n'est PAS appelée

Le but est donc de faire faire au compilateur quelque chose de semblable à ce qu'il fait avec l'opérateur new global.
Heureusement, c'est simple : quand le compilateur rencontre

new(pool) Foo()

il cherche un opérateur delete correspondant. S'il en trouve un, il fait un wrapping équivalent à celui de l'appel du
constructeur dans un bloc try/catch. Nous devons juste fournir un opérateur delete avec la signature suivante. Attention
de ne pas se tromper ici, car si le second paramètre a un type différent de celui de l'opérateur new, le compilateur
n'émettra aucun message, il ignorera simplement le bloc try/catch quand l'utilisateur effectuera l'allocation.

void operator delete(void* p, Pool& pool)


{
pool.dealloc(p);
}

Maintenant, le compilateur intégrera automatiquement les appels au constructeur dans un bloc try/catch.

principe
Foo* p;

// ne pas intercepter les exceptions lancées par l'allocateur


void* raw = operator new(sizeof(Foo), pool);

// le code précédent renvoie simplement "pool.alloc(sizeof(Foo))"

// intercepter toute exception lancée par le constructeur


try {
p = new(raw) Foo(); // appel du constructeur avec raw en tant que this
} catch (...) {
// le constructeur lance une exception
operator delete(raw, pool); // la ligne "magique"
throw; // relance l'exception
}

En d'autres mots, l'ajout de l'opérateur delete avec la signature ad hoc règle automatiquement le problème de fuite
de mémoire.

Problème 2 : se souvenir des associations objet/pool Ce problème est réglé par l'ajout de quelques lignes de code à un
endroit. En d'autres mots, nous allons ajouter ces lignes de code à un endroit (le fichier header du pool), ce qui va
simplifier par la même occasion un certain nombre d'appels.

- 155 -
Les sources présentées sur cette pages sont libres de droits, et vous pouvez les utiliser à votre convenance. Par contre la page de présentation de ces sources
constitue une oeuvre intellectuelle protégée par les droits d'auteurs. Copyright © 2008 Developpez LLC. Tout droits réservés Developpez LLC. Aucune
reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents et images sans l'autorisation expresse de Developpez
LLC. Sinon vous encourez selon la loi jusqu'à 3 ans de prison et jusqu'à 300 000 E de dommages et intérêts.
http://cpp.developpez.com/faq/cpp/
L'idée est d'associer de manière implicite un Pool* avec chaque allocation. Le Pool* associé à l'allocateur global pourrait
être NULL, mais conceptuellement on peut dire que chaque allocation a un Pool* associé.

Ensuite, nous remplaçons l'opérateur delete global de façon qu'il examine le Pool* associé, et s'il est non NULL, il
appellera la fonction de libération associée. Par exemple, si le désallocateur normal utilisait free(), le remplacement
pour l'opérateur delete global ressemblerait à quelque chose comme ceci :

void operator delete(void* p)


{
if (p != NULL) {
Pool* pool = /* quelqu'un qui détient le 'Pool*' associé */;
if (pool == null)
free(p);
else
pool->dealloc(p);
}
}

Si free() était le désallocateur normal, l'approche la plus sûre serait de remplacer aussi l'opérateur new par quelque
chose qui utiliserait malloc(). Le code remplaçant l'opérateur global new ressemblerait alors à quelque chose comme
ce qui suit :

void* operator new(size_t nbytes)


{
if (nbytes == 0)
nbytes = 1; // chaque alloc obtient donc une adresse différente
void* raw = malloc(nbytes);
// ... associer le Pool* à Null a 'raw'...
return raw;
}

Le dernier problème est d'associer un Pool* à une allocation. Une approche, utilisée dans au moins un produit
commercial, est d'utiliser un

<void*,Pool*>

En d'autres mots, il suffit de construire une table associative ou les clés sont les pointeurs alloués et les valeurs sont les
Pool* associés. Pour différentes raisons, il est essentiel que les paires clé/valeur soient insérées à partir de l'opérateur
new. En particulier, il ne faut pas insérer une paire de clé/valeur à partir de l'opérateur new global. La raison est la
suivante, faire cela créerait un problème circulaire : étant donné que std::map utilise plus que probablement l'opérateur
new global, à chaque insertion d'un élément serait appelé, pour insérer une nouvelle entrée, ce qui mène directement
à une récursion infinie.

Même si cette technique exige une recherche dans le std::map à chaque libération, elle semble avoir des performances
suffisantes, du moins dans la plupart des cas.

Une autre approche, plus rapide, mais qui peut utiliser plus de mémoire, et est un peu plus complexe, est de spécifier
un Pool* juste avant toutes les allocations. Par exemple, si nbytes vaut 24, c'est-à-dire que l'appelant veut allouer 24
bytes, on alloue 28 bytes (ou 32, si la machine aligne les doubles ou les long long sur 8 bytes), spécifie le Pool* dans les 4
premiers bytes, et retourne le pointeur avec un décalage de 4 bytes (ou 8) suivant l'architecture. Pour la libération du
pointeur, l'opérateur delete libère la mémoire en tenant compte du décalage de 4 (ou 8) bytes. Si Pool* vaut NULL, on
utilise free(), sinon pool->dealloc(). Le paramètre passé à free() et à pool->dealloc() est le pointeur décrémente de 4 ou
8 bytes du paramètre original. Pour un alignement de 4 bytes, le code pourrait ressembler à ceci :

void* operator new(size_t nbytes)


{
if (nbytes == 0)

- 156 -
Les sources présentées sur cette pages sont libres de droits, et vous pouvez les utiliser à votre convenance. Par contre la page de présentation de ces sources
constitue une oeuvre intellectuelle protégée par les droits d'auteurs. Copyright © 2008 Developpez LLC. Tout droits réservés Developpez LLC. Aucune
reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents et images sans l'autorisation expresse de Developpez
LLC. Sinon vous encourez selon la loi jusqu'à 3 ans de prison et jusqu'à 300 000 E de dommages et intérêts.
http://cpp.developpez.com/faq/cpp/
nbytes = 1; // ainsi toutes les allocations possèdent une adresse distincte
void* ans = malloc(nbytes + 4); // on alloue 4 bytes supplémentaires
*(Pool**)ans = NULL; // on utilise NULL pour le new global
return (char*)ans + 4; // on cache le Pool* à l'utilisateur
}

void* operator new(size_t nbytes, Pool& pool)


{
if (nbytes == 0)
nbytes = 1;
// ainsi toutes les allocations possèdent une adresse distincte
void* ans = pool.alloc(nbytes + 4); // on alloue 4 bytes supplémentaires
*(Pool**)ans = &pool; // stocker le Pool* ici
return (char*)ans + 4; // on cache le Pool* à l'utilisateur
}

void operator delete(void* p)


{
if (p != NULL) {
p = (char*)p - 4; // on récupère le Pool*
Pool* pool = *(Pool**)p;
if (pool == null)
free(p); // note : 4 bytes de moins que le p original
else
pool->dealloc(p); // note : 4 bytes de moins que le p original
}
}

Naturellement, ces derniers paragraphes sont uniquement valables si on peut modifier l'opérateur new global, ainsi
que delete.
S'il n'est pas possible de changer le comportement de ces opérateurs globaux, les trois quarts du texte qui précède
restent valables.

Qu'est-ce que "placement new" et dans quels cas l'utilise-t-on ?


Auteurs : Marshall Cline ,
On peut utiliser placement new dans de nombreux cas. L'utilisation la plus simple permet de placer un objet à une
adresse mémoire précise. Pour cela, l'adresse choisie est représentée par un pointeur que l'on passe à la partie new de
la new expression :

#include <new> // On doit inclure <new.h> pour utiliser "placement new"


#include "Fred.h" // Déclaration de la classe Fred

void someCode()
{
char memory[sizeof(Fred)]; // Ligne 1
void* place = memory; // Ligne 2

Fred* f = new(place) Fred(); // Ligne 3 (voir "DANGER" ci-dessous)


// Les deux pointeurs f et place sont maintenant égaux
}

La ligne 1 crée un tableau dont la taille en octets est sizeof(Fred), tableau donc assez grand pour que l'on puisse y stocker
un objet de type Fred.
La ligne 2 crée un pointeur place qui pointe sur le premier octet de cette zone mémoire (les programmeurs C
expérimentés auront noté que cette deuxième étape n'était pas strictement nécessaire ; en fait, elle est là juste pour
rendre le code plus lisible).
Pour faire simple, on peut de dire de la ligne 3 qu'elle appelle le constructeur Fred::Fred(). Dans ce constructeur, this
et place ont la même valeur. Le pointeur f retourné sera donc lui aussi égal à place.

- 157 -
Les sources présentées sur cette pages sont libres de droits, et vous pouvez les utiliser à votre convenance. Par contre la page de présentation de ces sources
constitue une oeuvre intellectuelle protégée par les droits d'auteurs. Copyright © 2008 Developpez LLC. Tout droits réservés Developpez LLC. Aucune
reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents et images sans l'autorisation expresse de Developpez
LLC. Sinon vous encourez selon la loi jusqu'à 3 ans de prison et jusqu'à 300 000 E de dommages et intérêts.
http://cpp.developpez.com/faq/cpp/
Conseil N'utilisez pas cette syntaxe du "placement new" si vous n'en avez pas l'utilité. Utilisez-là uniquement si vous
avez besoin de placer un objet à une adresse mémoire précise. Utilisez-là par exemple si le matériel sur lequel vous
travaillez dispose d'un périphérique de gestion du temps mappé en mémoire à une adresse précise, et que vous voulez
placer un objet Clock à cette adresse.

Danger Il est de votre entière responsabilité de garantir que le pointeur que vous passez à l'opérateur "placement
new" pointe sur une zone mémoire assez grande et correctement alignée pour l'objet que vous voulez y placer. Ni le
compilateur ni le runtime de votre système ne vérifient que c'est effectivement le cas. Vous pouvez vous retrouver dans
une situation fâcheuse si votre classe Fred nécessite un alignement sur une frontière de 4 octets et que vous avez utilisé
une zone mémoire qui n'est pas correctement alignée (si vous ne savez pas ce qu'est "l'alignement", alors SVP n'utilisez
pas la syntaxe du "placement new"). On vous aura prévenu.

La destruction de l'objet ainsi créé est aussi sous votre entière responsabilité. Pour détruire l'objet, il faut appeler
explicitement son destructeur.

void someCode()
{
char memory[sizeof(Fred)];
void* p = memory;
Fred* f = new(p) Fred();
// ...
f->~Fred(); // Appel explicite au destructeur
}

C'est un des très rares cas d'appel explicite au destructeur. A ce sujet vous pouvez lire Est-il possible d'invoquer
explicitement le destructeur d'une classe ?.

Qu'est-ce qu'un pointeur intelligent ?


Auteurs : Laurent Gomila , Aurélien Regat-Barrel , Luc Hermitte ,
Toute mémoire allouée dans vos programmes avec new doit être libérée à un moment ou un autre avec delete. Cette
bonne règle de programmation peut vite devenir contraignante et parfois difficile à mettre en oeuvre dans la pratique.
Les pointeurs intelligents (smart pointers en anglais) sont des objets se comportant comme des pointeurs classiques
(mimétisme dans la syntaxe et certaines sémantiques), mais qui offrent en plus des fonctionnalités intéressantes
permettant une gestion quasi automatique de la mémoire (en particulier de sa libération). Leur syntaxe est très proche
de celle des pointeurs classiques (grâce à la surcharge des opérateurs *, ->, etc...)., mais ils utilisent en interne divers
mécanismes (comptage de références, ...) qui permettent de déceler qu'un objet n'est plus utilisé, auquel cas le pointeur
intelligent se charge de le détruire ce qui permet d'éviter les fuites de mémoire.
Utiliser des pointeurs intelligents est généralement une très bonne idée, en particulier lors de l'écriture de code
susceptible d'être interrompu par des exceptions (soit presque tout le temps !). Si tel est le cas, ceux-ci ne manqueront
pas de libérer la mémoire qui leur est associée lors de leur destruction (suite à une exception ou non), ce qu'il n'est pas
possible d'assurer sans multiplier les blocs try...catch dans son code.

// Test1 est exception safe sans utilisation de pointeur intelligent


void Test1()
{
int * ptr = new int;
try
{
// code pouvant lever une exception
}
catch ( ... ) // gestion approximative...
{
// libérer le pointeur
delete ptr;

- 158 -
Les sources présentées sur cette pages sont libres de droits, et vous pouvez les utiliser à votre convenance. Par contre la page de présentation de ces sources
constitue une oeuvre intellectuelle protégée par les droits d'auteurs. Copyright © 2008 Developpez LLC. Tout droits réservés Developpez LLC. Aucune
reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents et images sans l'autorisation expresse de Developpez
LLC. Sinon vous encourez selon la loi jusqu'à 3 ans de prison et jusqu'à 300 000 E de dommages et intérêts.
http://cpp.developpez.com/faq/cpp/
// relancer l'exception
throw;
}
// tout s'est bien passé, libérer la mémoire
delete ptr;
}

Comme on peut le voir, cette gestion des exceptions est assez polémique, et surtout elle est totalement inélégante. Pensez
qu'il faudrait procéder ainsi partout où il y a un new ! Voici un autre moyen de rendre l'appel à new exception safe :

#include <memory> // pour std::auto_ptr

// Test2 est exception safe en utilisant un pointeur intelligent


void Test2()
{
// std::auto_ptr est la classe pointeur intelligent standard
std::auto_ptr<int> smart_ptr = new int;

// code pouvant lever une exception (rien d'autre !)


}

La nouvelle version est tout aussi sûre, mais elle est bien plus triviale à mettre en place. Attention cependant à
std::auto_ptr dont l'usage est un peu spécial en particulier en cas de copie de pointeur (à ce sujet lire Pourquoi faut-
il se méfier de std::auto_ptr ?). Pour cette raison il est conseillé de plutôt se tourner vers une autre classe de pointeurs
intelligents déjà toute faite (rien ne sert de réinventer la roue !) telle que boost::shared_ptr.
Cependant attention, les pointeurs intelligents n'échappent pas à la règle que ce qui a été alloué avec new doit être libéré
avec delete et ce qui a été alloué avec new [] doit l'être avec delete []. Or std::auto_ptr tout comme boost::shared_ptr
appellent l'opérateur delete. Donc ces pointeurs intelligents ne doivent pas être utilisés avec des tableaux. Pour cela il
faudra utiliser une autre classe telle que boost::shared_array. Mais n'oubliez pas que std::vector est là pour éviter ces
tracasseries avec les tableaux ce qui fait une bonne raison de plus de l'utiliser à la place des tableaux classiques (lire à
ce sujet Comment créer et utiliser un tableau avec std::vector ?).

lien : Comment utiliser les pointeurs intelligents de Boost ?


lien : Présentation des pointeurs intelligents.

Pourquoi faut-il se méfier de std::auto_ptr ?


Auteurs : Laurent Gomila , Aurélien Regat-Barrel ,
Il existe un type standard de pointeurs intelligents : std::auto_ptr déclaré dans l'en-tête <memory>. Mais en pratique,
son utilisation peut s'avérer problématique si l'on n'a pas pris le temps de bien comprendre son fonctionnement. C'est
pourquoi cette classe est souvent déconseillée, surtout aux débutants.
Contrairement à beaucoup de pointeurs intelligents, std::auto_ptr n'utilise pas un mécanisme de comptage de référence
afin de partager un même objet pointé par plusieurs pointeurs intelligents (l'objet est détruit lorsque plus aucun
pointeur ne l'utilise). std::auto_ptr utilise un mécanisme plus simple mais plus risqué : il n'y a qu'un seul propriétaire
de l'objet pointé et ce dernier est détruit lorsque son propriétaire l'est. Si une copie est effectuée d'un auto_ptr vers un
autre, il y a transfert de propriété, c'est-à-dire que la possession de l'objet pointé sera transmise du premier au second,
et le premier deviendra alors un pointeur invalide (NULL).

int Test()
{
// ptr1 est le propriétaire
std::auto_ptr<int> ptr1( new int );
{
// ptr2 devient le propriétaire
std::auto_ptr<int> ptr2 = ptr1;
}
// ici ptr2 est détruit, le int est libéré !

- 159 -
Les sources présentées sur cette pages sont libres de droits, et vous pouvez les utiliser à votre convenance. Par contre la page de présentation de ces sources
constitue une oeuvre intellectuelle protégée par les droits d'auteurs. Copyright © 2008 Developpez LLC. Tout droits réservés Developpez LLC. Aucune
reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents et images sans l'autorisation expresse de Developpez
LLC. Sinon vous encourez selon la loi jusqu'à 3 ans de prison et jusqu'à 300 000 E de dommages et intérêts.
http://cpp.developpez.com/faq/cpp/
// ptr1 pointe désormais vers NULL
*ptr1 = 10; // aille aille aille...
}

L'exemple précédent peut paraître simple à éviter, et pourtant, de nombreuses copies peuvent être faites à votre insu :

include <memory>

// Ptr est passé par valeur, donc a priori aucun risque que le
// paramètre donné lors de l'appel ne soit modifié
void Test( std::auto_ptr<MaClasse> Ptr )
{
// Mais ce qui va réellement se passer, c'est que Ptr va prendre
// le contrôle de l'objet pointé, et le détruire à la fin de la fonction !
}

MaClasse* Ptr1 = new MaClasse;


Test( std::auto_ptr<MaClasse>( Ptr1 ) );
// Ici Ptr1 ne pointe plus sur une zone valide, il a été détruit suite à l'appel

std::auto_ptr<MaClasse> Ptr2( new MaClasse );


Test( Ptr2 );
// Ici Ptr2 ne pointe plus sur la donnée originale
// il pointe sur NULL et la donnée a été détruite dans la fonction

std::auto_ptr est donc à proscrire dans bien des cas (lors de copies ou de passages à des fonctions). En particulier il ne
faut pas l'utiliser avec les conteneurs standards non plus (std::vector, ...).
Un autre point important est que std::auto_ptr appelle l'opérateur delete et non delete [] ce qui en interdit l'usage avec
des pointeurs retournés par new [] (tableaux).
On peut tout de même envisager de l'utiliser pour rendre une fonction exception-safe à peu de frais, comme cela est
expliqué dans la question Qu'est-ce qu'un pointeur intelligent ?. Mais on préfèrera utiliser les pointeurs intelligents de
Boost par exemple (voir Comment utiliser les pointeurs intelligents de Boost ?).

Comment gérer proprement des allocations / désallocations de ressources ? Le RAII !


Auteurs : Aurélien Regat-Barrel , Laurent Gomila , JolyLoic , Luc Hermitte ,
RAII signifie Resource Acquisition Is Initialization (acquisition de ressources lors de l'initialisation). Il s'agit d'un idiome
de programmation consistant à manipuler une ressource quelconque (mémoire, fichier, mutex, connexion à une base
de données, ...) au moyen d'une variable locale qui va acquérir cette ressource lors de son initialisation et la libérer
lors de sa destruction.

Le C++ est particulièrement bien adapté à la mise en oeuvre de cet idiome car c'est un langage qui détruit de manière
déterministe les objets automatiques. Autrement dit, le RAII est rendu possible en C++ par le fait que les classes
disposent d'un destructeur qui est appelé dès qu'un objet sort de son bloc de portée. On place dans ce destructeur le
code nécessaire à la libération de la ressource acquise. On est ainsi assuré que la ressource sera bien libérée, sans que
l'utilisateur n'ait eu à appeler de fonction close() ou free(), et celà même en cas d'une exception.

Le RAII est une technique très puissante, qui simplifie grandement la gestion des ressources en général, de la mémoire en
particulier. Cet idiome permet tout simplement de créer du code exception-safe sans aucune fuite de mémoire. C'est donc
une alternative de choix à la clause finally d'autres langages, ou fournie par certains compilos C++. De manière concrète,
le RAII peut se résumer en "tout faire dans le constructeur et le destructeur". Si la ressource n'a pas pû être acquise
dans le constructeur, alors on lève en général une exception (voir Que faire en cas d'échec du constructeur ?) ; ainsi
l'objet responsable de la durée de vie de la ressource n'est pas construit. A l'inverse, si la ressource a été correctement
allouée alors sa responsabilité est confiée à l'objet, qui la libérera correctement quoiqu'il arrive dans son destructeur.
Cela simplifie donc beaucoup le code : il n'y a pas d'allocation de ressource, pas de test de réussite, et pas de libération

- 160 -
Les sources présentées sur cette pages sont libres de droits, et vous pouvez les utiliser à votre convenance. Par contre la page de présentation de ces sources
constitue une oeuvre intellectuelle protégée par les droits d'auteurs. Copyright © 2008 Developpez LLC. Tout droits réservés Developpez LLC. Aucune
reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents et images sans l'autorisation expresse de Developpez
LLC. Sinon vous encourez selon la loi jusqu'à 3 ans de prison et jusqu'à 300 000 E de dommages et intérêts.
http://cpp.developpez.com/faq/cpp/
explicite : tout est fait automatiquement, de manière certaine. L'utilisation même des objets est simplifiée en encapsulant
d'avantage le gestion des ressources et donc en améliorant l'abstraction.

Soit la fonction suivante, qui permet d'écrire le contenu d'un fichier dans une base de données :

// Enregistre un fichier donné dans la base de données


// lève une exception en cas d'échec
void WriteFileInDatabase( DataBase & Db, std::string Filename )
{
// tenter d'ouvrir le fichier
Datafile file;
if ( !file.Open( Filename ) )
{
// impossible d'ouvrir le fichier...
throw InvalidArgument();
}
// verrouiller la base de données
if ( !Db.Lock() )
{
// on ne peut pas utiliser la base...
throw DatabaseError();
}
// écrire le fichier dans la base
Db.WriteData( file.ReadData() );

// tout s'est bien passé : libérer les ressources


Db.Unlock();
file.Close();
}

Ce code semble correct, mais en réalité il ne l'est pas : que se passe-t-il si une erreur se produit et que l'une de nos
exceptions est lancée ? S'assure-t-on de toujours fermer le fichier et déverrouiller la base de données quoiqu'il arrive ?
La réponse est non, tout ceci n'est fait que si tout se déroule bien et que la fonction arrive à son terme.
Voici maintenant la même fonction, modifiée pour gérer correctement ces erreurs :

// Enregistre un fichier donné une la base de donnée


// lève une exception en cas d'échec
void WriteFileInDatabase( DataBase & Db, std::string Filename )
{
// tenter d'ouvrir le fichier
Datafile file;
if ( !file.Open( Filename ) )
{
// impossible d'ouvrir le fichier...
throw InvalidArgument();
}
// verrouiller la base de données
if ( !Db.Lock() )
{
// fermer le fichier
file.Close();
// on ne peut pas utiliser la base...
throw DatabaseError();
}
// écrire le fichier dans la base
try
{
Db.WriteData( file.ReadData() );
}
catch ( ... )
{
// libérer les ressources en cas d'erreur

- 161 -
Les sources présentées sur cette pages sont libres de droits, et vous pouvez les utiliser à votre convenance. Par contre la page de présentation de ces sources
constitue une oeuvre intellectuelle protégée par les droits d'auteurs. Copyright © 2008 Developpez LLC. Tout droits réservés Developpez LLC. Aucune
reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents et images sans l'autorisation expresse de Developpez
LLC. Sinon vous encourez selon la loi jusqu'à 3 ans de prison et jusqu'à 300 000 E de dommages et intérêts.
http://cpp.developpez.com/faq/cpp/
Db.Unlock();
file.Close();
throw; // relancer l'exception
}
// tout s'est bien passé : libérer les ressources
Db.Unlock();
file.Close();
}

Ce code est exception-safe, c'est-à-dire qu'il ne provoque pas de fuite de ressources si une exception est levée. Mais à
quel prix ! Selon l'approche RAII, on peut modifier la classe Datafile pour qu'elle fasse le Open() dans son constructeur
(avec levée d'exception), et le Close() dans son destructeur.
Si l'on crée une petite classe utilitaire DBLock qui gère le verrouillage de la base :

struct DBLock
{
DBLock(DataBase& DB) : DB_(DB)
{
DB_.Lock();
}

~DBLock()
{
DB_.Unlock();
}

private :

DataBase& DB_;
};

La fonction précédente devient :

void WriteFileInDatabase( DataBase & Db, std::string Filename )


{
// ouvrir le fichier
Datafile file( Filename );
// fichier ouvert : vérrouiller la base
DBLock lock( Db );
// base verrouillée : écrire les données du fichier
Db.WriteData( file.ReadData() );
} // libération des ressources automatiques

Le RAII est donc un idiome particulièrement puissant : il permet d'écrire un code plus simple, exception-safe et sans
fuites de mémoire. Il est d'ailleurs utilisé intensivement dans la bibliothèque standard du C++ : gestion des fichiers
(std::fstream), des chaînes de caractères (std::string), des tableaux dynamiques (Comment créer et utiliser un tableau
avec std::vector ?), des pointeurs (Pourquoi faut-il se méfier de std::auto_ptr ?), ...
C'est également le principe de base des pointeurs intelligents (voir Qu'est-ce qu'un pointeur intelligent ?), qui permettent
d'envelopper toutes sortes de ressources.

On peut également citer quelques bonnes lectures sur le sujet :

• Chapitre 14.4 de "Le langage C++" de Bjarne Stroustrup : "Gestion des ressources".

Chapitre 6 de "C++ in action" : "Managing resources" ( http://www.relisoft.com/book/tech/5resource.html).

Les scope guards d'Andrei Alexandrescu ( http://www.cuj.com/documents/s=8000/cujcexp1812alexandr/
alexandr.htm).

- 162 -
Les sources présentées sur cette pages sont libres de droits, et vous pouvez les utiliser à votre convenance. Par contre la page de présentation de ces sources
constitue une oeuvre intellectuelle protégée par les droits d'auteurs. Copyright © 2008 Developpez LLC. Tout droits réservés Developpez LLC. Aucune
reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents et images sans l'autorisation expresse de Developpez
LLC. Sinon vous encourez selon la loi jusqu'à 3 ans de prison et jusqu'à 300 000 E de dommages et intérêts.
http://cpp.developpez.com/faq/cpp/

The law of big two ( http://www.artima.com/cppsource/bigtwo.html).

lien : Présentation des pointeurs intelligents.

- 163 -
Les sources présentées sur cette pages sont libres de droits, et vous pouvez les utiliser à votre convenance. Par contre la page de présentation de ces sources
constitue une oeuvre intellectuelle protégée par les droits d'auteurs. Copyright © 2008 Developpez LLC. Tout droits réservés Developpez LLC. Aucune
reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents et images sans l'autorisation expresse de Developpez
LLC. Sinon vous encourez selon la loi jusqu'à 3 ans de prison et jusqu'à 300 000 E de dommages et intérêts.
http://cpp.developpez.com/faq/cpp/
Sommaire > Les namespaces
Qu'est-ce qu'un namespace ?
Auteurs : LFE , Aurélien Regat-Barrel , JolyLoic ,
Un namespace, ou espace de nom (parfois aussi espace de nommage, voire référentiel lexical) est une zone de déclaration
d'identificateurs permettant au compilateur de résoudre les conflits de noms.
Si, par exemple, 2 développeurs définissent des fonctions avec le même nom, il y aura un conflit lors de l'utilisation de
ces fonctions dans un programme. Les espaces de nommage permettent de résoudre ce problème en ajoutant un niveau
supplémentaire aux identificateurs.

namespace a
{
void toto() // première fonction toto
{
}
}
namespace b
{
void toto() // seconde fonction toto()
{
}
}
a::toto(); // appelle la première
b::toto(); // appelle la seconde

Les namespaces offrent une alternative élégante à diverses techniques prénamespace telles que le préfixage des noms
ou l'utilisation de préprocesseur :

void mylib_fonction1();
void mylib_fonction2();

L'exemple précédent peut s'écrire ainsi grâce aux namespaces :

namespace mylib
{
void fonction1();
void fonction2();
}

Qu'est-ce qu'un namespace anonyme ?


Auteurs : Aurélien Regat-Barrel ,
Un namespace anonyme est un espace de nom... sans nom.

namespace
{
int a; // variable déclarée dans un namespace anonyme
}

Un namespace anonyme a la particularité de n'être visible que par l'unité de compilation dans laquelle il se trouve (c'est-
à-dire le fichier compilable). Son utilité est de permettre la déclaration d'une variable/fonction/type dont la portée doit
être celle du fichier.

- 164 -
Les sources présentées sur cette pages sont libres de droits, et vous pouvez les utiliser à votre convenance. Par contre la page de présentation de ces sources
constitue une oeuvre intellectuelle protégée par les droits d'auteurs. Copyright © 2008 Developpez LLC. Tout droits réservés Developpez LLC. Aucune
reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents et images sans l'autorisation expresse de Developpez
LLC. Sinon vous encourez selon la loi jusqu'à 3 ans de prison et jusqu'à 300 000 E de dommages et intérêts.
http://cpp.developpez.com/faq/cpp/
Parce qu'un namespace traduit mieux la notion de portée restreinte à un fichier, il est recommandé de privilégier son
usage en C++ par rapport à celui du mot-clé static utilisé dans le même but mais en C.

Comment définir l'implémentation d'une classe / fonction déclarée dans un namespace ?


Auteurs : LFE , Luc Hermitte , Aurélien Regat-Barrel ,
Si vous avez déclaré une classe ou des fonctions dans un espace de nom :

exemple.h
namespace ns
{
class Test
{
public:
void F1();
void F2();
};

void Fonction1();
void Fonction2();
}

Il suffit de procéder de la même manière pour les implémenter, ou bien de préfixer par le nom du namespace :

exemple.cpp
namespace ns
{
void Test::F1()
{
}

void Fonction1()
{
}
}

// autre possibilité

void ns::Test::F2()
{
}

void ns::Fonction2()
{
}

A quoi sert "using namespace std;" ?


Auteurs : Anomaly , Luc Hermitte , Aurélien Regat-Barrel ,
Toute la bibliothèque standard C++ est définie dans son propre espace de nom, le namespace std. Ainsi, il faut
systématiquement utiliser le préfixe std:: devant tous les éléments qui en sont issus.

#include <iostream>

int main()
std::cout << "cout" << std::endl;
}

- 165 -
Les sources présentées sur cette pages sont libres de droits, et vous pouvez les utiliser à votre convenance. Par contre la page de présentation de ces sources
constitue une oeuvre intellectuelle protégée par les droits d'auteurs. Copyright © 2008 Developpez LLC. Tout droits réservés Developpez LLC. Aucune
reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents et images sans l'autorisation expresse de Developpez
LLC. Sinon vous encourez selon la loi jusqu'à 3 ans de prison et jusqu'à 300 000 E de dommages et intérêts.
http://cpp.developpez.com/faq/cpp/
Pour alléger l'écriture, on utilise l'expression using namespace std; ce qui permet de s'affranchir de l'obligation de
préfixer par std::. Le code précédent peut alors s'écrire ainsi :

#include <iostream>
using namespace std;

int main()
{
cout << "coucou" << endl;
}

Il est conseillé de limiter l'utilisation de using namespace car cela fait tout simplement perdre l'utilité des namespaces.
En particulier, son usage est à proscrire dans les fichiers d'en-tête, ainsi que dans les cas où il génère des conflits avec
d'autres namespaces. On peut pour cela restreindre la portée de la clause using à un bloc en particulier :

#include <iostream>

void affiche_coucou()
{
using namespace std;
// dans tout le corps de la fonction préfixer par std:: est facultatif

cout << "coucou" << endl;


}

int main()
{
// ici il est obligatoire de préfixer par std::
affiche_coucou();
std::cout << "coucou" << std::endl;
}

Autre possibilité : importer un symbole du namespace et non le namespace tout entier :

#include <iostream>

using std::cout;
using std::endl;

int main()
{
// si on veut utiliser cin par exemple, il faudra écrire std::cin
cout << "coucou" << endl;
}

Pour plus d'informations, lire Qu'est-ce qu'un namespace ?.

Quand utiliser / ne pas utiliser using namespace ?


Auteurs : JolyLoic ,
Utiliser using namespace xxx; indique au compilateur qu'il a le droit, quand il voit un nom dans le reste de la portée
courante, de le rechercher dans l'espace de nom xxx, ce qui peut alléger le code, en permettant d'écrire :

using namespace std;

cout << hex << 42 << endl;

Au lieu de

- 166 -
Les sources présentées sur cette pages sont libres de droits, et vous pouvez les utiliser à votre convenance. Par contre la page de présentation de ces sources
constitue une oeuvre intellectuelle protégée par les droits d'auteurs. Copyright © 2008 Developpez LLC. Tout droits réservés Developpez LLC. Aucune
reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents et images sans l'autorisation expresse de Developpez
LLC. Sinon vous encourez selon la loi jusqu'à 3 ans de prison et jusqu'à 300 000 E de dommages et intérêts.
http://cpp.developpez.com/faq/cpp/
std::cout << std::hex << 42 << std::endl;

Pour plus de détails, voir A quoi sert "using namespace std;" ?.

Par contre, cette écriture est à proscrire dans des fichiers d'en-tête, du moins à portée de fichier. En effet, le but des
espaces de nom est de permettre d'éviter des collisions de nom entre deux objets qui auraient le même nom, mais
provenant de deux sources différentes (et donc classés dans deux espaces de nom différents). L'utilisation de using est
un raccourci, mais il n'est possible que si on sait qu'il n'y a pas de conflits. Si ce n'est pas le cas, il faut obligatoirement
utiliser le nom qualifié des objets.

Or, dans le cadre d'un fichier d'en-tête, on ne peut pas savoir dans quels contextes ce fichier sera utilisé. Et comme il
n'existe pas de commande qu'on puisse insérer pour dire d'arrêter d'utiliser un using, on risque si on utilise cette écriture
dans un fichier d'en-tête de provoquer un conflit chez un de ses clients, qui n'aura aucun recours pour le corriger.

- 167 -
Les sources présentées sur cette pages sont libres de droits, et vous pouvez les utiliser à votre convenance. Par contre la page de présentation de ces sources
constitue une oeuvre intellectuelle protégée par les droits d'auteurs. Copyright © 2008 Developpez LLC. Tout droits réservés Developpez LLC. Aucune
reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents et images sans l'autorisation expresse de Developpez
LLC. Sinon vous encourez selon la loi jusqu'à 3 ans de prison et jusqu'à 300 000 E de dommages et intérêts.
http://cpp.developpez.com/faq/cpp/
Sommaire > Utilisation des exceptions
Qu'est-ce qu'une exception ?
Auteurs : Aurélien Regat-Barrel ,
Les exceptions sont un nouveau moyen de gérer les erreurs dans les programmes. La grande différence vis à vis du
classique code d'erreur renvoyé par une fonction est qu'une exception se propage depuis l'appelé vers l'appelant jusqu'à
ce qu'elle rencontre un bloc de code qui s'occupe de la traiter. Au contraire d'un code d'erreur qui peut être ignoré (ce
qui est malheureusement souvent le cas) une exception doit être traitée. Si elle ne l'est pas dans la fonction qui en est
à l'origine, elle doit l'être dans l'une des fonctions appelantes. Le compilateur s'occupe tout seul de faire en sorte que
l'exception remonte le long de la pile des appels jusqu'à l'endroit où un bloc a été prévu pour la traiter. Cela permet
donc de facilement faire "remonter" les erreurs des fonctions appelées vers les fonctions appelantes. L'apparition d'une
exception interrompt l'exécution normale du programme et provoque sa reprise dans le gestionnaire d'exception le plus
proche (qui peut se trouver beaucoup plus en amont dans une fonction appelante).
Le programmeur n'a donc plus à se soucier de tester la réussite ou non des fonctions qu'il appelle au moyen d'un grand
nombre de tests comme dans l'exemple suivant :

// ces fonctions renvoient false en cas d'erreur


bool F1();
bool F2();
bool F3();
bool F4();

bool Test1()
{
// appeler F1 et F2
if ( !F1() )
{
return false;
}
if ( !F2() )
{
return false;
}
return true;
}

bool Test2()
{
// appeler Test1 et F3
if ( !Test1() )
{
return false;
}
if ( !F3() )
{
return false;
}
return true;
}

bool Test3()
{
// appeler Test2 et F4
if ( !Test2() )
{
return false;
}
if ( !F4() )
{
return false;
}
return true;
}

- 168 -
Les sources présentées sur cette pages sont libres de droits, et vous pouvez les utiliser à votre convenance. Par contre la page de présentation de ces sources
constitue une oeuvre intellectuelle protégée par les droits d'auteurs. Copyright © 2008 Developpez LLC. Tout droits réservés Developpez LLC. Aucune
reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents et images sans l'autorisation expresse de Developpez
LLC. Sinon vous encourez selon la loi jusqu'à 3 ans de prison et jusqu'à 300 000 E de dommages et intérêts.
http://cpp.developpez.com/faq/cpp/
int main()
{
if ( !Test3() )
{
std::cerr << "Une erreur est survenue, mais je ne sais pas où !".
}
}

Les exceptions permettent de grandement simplifier le code précédent tout en améliorant la qualité du renseignement
sur l'origine de l'erreur :

// ces fonctions lèvent des exceptions en cas d'erreur


void F1();
void F2();
void F3();
void F4();

void Test1()
{
F1();
F2();
}

void Test2()
{
Test1();
F3();
}

void Test3()
{
Test2();
F4();
}

int main()
{
try
{
Test3();
}
catch ( const std::bad_alloc & )
{
std::cerr << "Erreur : mémoire insuffisante.\n":
}
catch ( const std::out_of_range & )
{
std::cerr << "Erreur : débordement de mémoire.\n":
}
}

Comment lever une exception ?


Auteurs : Aurélien Regat-Barrel ,
Les exceptions sont déclenchées grâce à l'utilisation du mot-clé throw :

// lève une exception de type e


throw e;

- 169 -
Les sources présentées sur cette pages sont libres de droits, et vous pouvez les utiliser à votre convenance. Par contre la page de présentation de ces sources
constitue une oeuvre intellectuelle protégée par les droits d'auteurs. Copyright © 2008 Developpez LLC. Tout droits réservés Developpez LLC. Aucune
reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents et images sans l'autorisation expresse de Developpez
LLC. Sinon vous encourez selon la loi jusqu'à 3 ans de prison et jusqu'à 300 000 E de dommages et intérêts.
http://cpp.developpez.com/faq/cpp/
Les exceptions peuvent être de n'importe quel type (type de base du langage ou classe quelconque). Mais il est conseillé
d'utiliser une classe dérivant de la classe standard std::exception définie dans le fichier d'en-tête standard <exception>.
Cette classe dispose d'une fonction membre what() qui renvoie une description de l'exception.

try
{
// instructions pouvant déclencher des exceptions
// dérivant de std::exception
}
catch ( const std::exception & e )
{
std::cerr << e.what();
}

Les exceptions doivent être de préférence déclenchées par valeur, et attrapée par référence. Déclencher une exception
par pointeur pose en effet un problème :

try
{
// déclencher une exception par pointeur
throw new int( 10 );
}
catch ( const int * e )
{
std::cerr << "Erreur numéro " << *e;
}

Le code précédent fonctionne mais une question subsiste : comment est libéré le pointeur alloué dans le bloc try ? La
réponse est qu'il ne l'est pas, et il se produit donc une fuite de mémoire. Le problème de déclencher une exception par
pointeur est donc de savoir à qui incombe la responsabilité de libérer ce dernier. Voilà pourquoi on préfère lever des
exceptions par valeur, et les attraper par référence (de préférence constante) afin de permettre le polymorphisme et
d'améliorer les performances en évitant une recopie de l'objet.

#include <iostream>
#include <stdexcept>

int main()
{
try
{
// std::logic_error est une classe standard
// qui dérive de std::exception
throw std::logic_error( "Exemple d'exception" );
}
catch ( const std::exception & e )
{
// affiche "Exemple d'exception"
std::cerr << e.what();
}
}

Un cas particulier est celui des chaînes de caractères littérales :

try
{
throw "Message d'erreur";
}
catch ( const char * Msg )
{
std::cerr << Msg;
}

- 170 -
Les sources présentées sur cette pages sont libres de droits, et vous pouvez les utiliser à votre convenance. Par contre la page de présentation de ces sources
constitue une oeuvre intellectuelle protégée par les droits d'auteurs. Copyright © 2008 Developpez LLC. Tout droits réservés Developpez LLC. Aucune
reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents et images sans l'autorisation expresse de Developpez
LLC. Sinon vous encourez selon la loi jusqu'à 3 ans de prison et jusqu'à 300 000 E de dommages et intérêts.
http://cpp.developpez.com/faq/cpp/
Dans l'exemple précédent, bien qu'on utilise un pointeur, il n'y a pas de fuite de mémoire tout simplement parce qu'il n'y
a pas d'allocation dynamique. Ce serait même une grosse erreur de libérer ce pointeur avec delete [] car ce dernier pointe
vers une zone de mémoire spéciale (généralement en lecture seule) qui contient l'ensemble de chaînes de caractères
littérales utilisées dans le programme. Donc, à priori, utiliser des chaînes littérales est une bonne idée, mais cela est
néanmoins déconseillé car leur utilité est vite limitée du fait de l'impossibilité de formater les chaînes pour donner
la valeur d'une variable par exemple. Il y a fort à craindre que tôt ou tard un programmeur voudra effectuer cette
opération et provoquera alors inconsciemment une fuite de mémoire (car dans le bloc catch il n'y a aucun moyen de
distinguer une chaîne littérale d'une chaîne allouée avec new []).
Si vous persistez à vouloir utiliser de simple chaînes de caractères au lieu d'une classe dérivant de std::exception, utilisez
au moins le type chaîne de caractères du C++ : std::string.

try
{
throw std::string( "Message d'erreur" );
}
catch ( const std::string & Msg )
{
std::cerr << Msg;
}

Comment capturer les exceptions dans mon code ?


Auteurs : Aurélien Regat-Barrel ,
Le code susceptible de déclencher des exceptions doit être placé dans un bloc try...catch (essaye...attrape) de cette
manière :

int * ptr;
try
{
// tenter d'allouer 100 entiers
ptr = new int [ 100 ];
}
catch ( const std::bad_alloc & )
{
// échec de l'allocation
}

On peut mettre autant de blocs catch qu'il y a d'exceptions à rattraper.

Mauvais chaînage des blocs catch


try
{
// créer un tableau de taille 10
std::vector<int> tableau( 10 );
// accéder au 11° élément
tableau.at( 10 );
}
catch ( const std::exception & Exp )
{
std::cerr << "Erreur : " << Exp.what() << ".\n";
}
catch ( const std::bad_alloc & )
{
std::cerr << "Erreur : mémoire insuffisante.\n";
}
catch ( const std::out_of_range & )
{
std::cerr << "Erreur : débordement de mémoire.\n";
}

- 171 -
Les sources présentées sur cette pages sont libres de droits, et vous pouvez les utiliser à votre convenance. Par contre la page de présentation de ces sources
constitue une oeuvre intellectuelle protégée par les droits d'auteurs. Copyright © 2008 Developpez LLC. Tout droits réservés Developpez LLC. Aucune
reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents et images sans l'autorisation expresse de Developpez
LLC. Sinon vous encourez selon la loi jusqu'à 3 ans de prison et jusqu'à 300 000 E de dommages et intérêts.
http://cpp.developpez.com/faq/cpp/
Les exceptions levées dans le bloc try vont être filtrées par les différents blocs catch suivant leur ordre d'apparition. Ce
filtrage est effectué en fonction du type de l'exception, et est naturellement sensible au polymorphisme. C'est-à-dire que
le premier bloc catch rencontré capable de traiter l'exception levée est celui qui est utilisé. Les autres sont ignorés, même
si certains seraient mieux adaptés. En l'occurrence, dans l'exemple précédent, l'exception out_of_range est levée et ce
serait tout naturellement le dernier bloc catch qui devrait la traiter. Mais std::out_of_range dérive de std::exception
qui est la classe de base pour les exceptions standards, et donc c'est le premier bloc catch qui est appelé.
Il existe aussi un moyen d'attraper toutes les exceptions, en utilisant une ellipse (...) comme type de l'exception. Mais
alors il n'y a aucun moyen de connaître l'origine et le type de l'exception (sauf à la relancer et la traiter dans un nouveau
bloc try...catch). L'utilisation de cette forme générique doit être restreinte car elle ne permet de savoir si l'exception
capturée peut être traitée et ignorée ou si elle nécessite de terminer le programme (corruption de la mémoire, etc...).
On l'utilise en général comme dernier recours.

try
{
// créer un tableau de taille 10
std::vector<int> tableau( 10 );
// accéder au 11° élément
tableau.at( 10 );
}
catch ( const std::bad_alloc & )
{
std::cerr << "Erreur : mémoire insuffisante.\n";
}
catch ( const std::out_of_range & )
{
std::cerr << "Erreur : débordement de mémoire.\n";
}
catch ( const std::exception & Exp )
{
std::cerr << "Erreur : " << Exp.xhat() << ".\n";
}
catch ( ... ) // traite toutes les autres exceptions
{
std::cerr << "Erreur inconnue.\n";
}

Il est donc important de faire apparaître les blocs catch des classes dérivées en premier.
Notez que les exceptions sont récupérées par référence, comme cela est expliqué dans la question Pourquoi faut-il
capturer les exceptions par référence ?. Ces références ne sont cependant pas des références sur l'objet initial qui est
à l'origine de l'exception, mais sur une copie de celui-ci, car l'objet initial risque d'être détruit si l'on quitte la fonction
qui a levé l'exception.

Pourquoi faut-il capturer les exceptions par référence ?


Auteurs : Aurélien Regat-Barrel ,
Comme discuté dans la question Comment lever une exception ?, il est fortement recommandé de lever des exceptions
par valeur. En revanche, il vaut mieux les capturer par référence et non pas par valeur. Tout d'abord cela permet
d'éviter une recopie, mais aussi et surtout cela permet de conserver le polymorphisme. L'exemple suivant illustre les
problèmes posés par un traitement des exceptions par valeur :

#include <iostream>
#include <stdexcept>

int main()
{
using namespace std;

try

- 172 -
Les sources présentées sur cette pages sont libres de droits, et vous pouvez les utiliser à votre convenance. Par contre la page de présentation de ces sources
constitue une oeuvre intellectuelle protégée par les droits d'auteurs. Copyright © 2008 Developpez LLC. Tout droits réservés Developpez LLC. Aucune
reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents et images sans l'autorisation expresse de Developpez
LLC. Sinon vous encourez selon la loi jusqu'à 3 ans de prison et jusqu'à 300 000 E de dommages et intérêts.
http://cpp.developpez.com/faq/cpp/
{
// std::logic_error hérite de std::exception
throw logic_error( "exception de test" );
}
catch ( exception e ) // traitement par valeur
{
cerr << e.what();
}
}

Cet exemple affiche le message Unknown exception. Si l'on remplace le traitement par valeur par un traitement par
référence :

catch ( const exception & e ) // traitement par référence

Alors on obtient le message attendu exception de test. Ceci est dû au fait que le polymorphisme nécessite d'utiliser un
pointeur ou une référence, autrement dans notre cas l'objet de type std::logic_error est "tronqué" en un objet de type
std::exception. La fonction membre what appelée n'est donc pas celle de std::logic_error mais celle de std::exception,
qui n'est pas d'une grande utilité.
Donc à moins de rechercher volontairement ce comportement, il est recommandé de traiter les exceptions par référence,
de préférence constantes afin de permettre au compilateur d'effectuer des optimisations.

Est-il possible de capturer plusieurs exceptions dans un seul catch ?


Auteurs : LFE ,
Malheureusement, non, ce mécanisme n'est pas possible. Un catch ne pouvant capturer qu'un seul type d'exceptions, il
faut définir autant de blocs try/catch qu'il y a d'exceptions possibles.

L'utilisation de

try {
// ...
} catch(...) {
// ...
}

permet de capturer toutes les exceptions pouvant survenir, mais il est, dans ce cas, impossible de faire la distinction.

Comment relancer une exception que l'on a capturé ?


Auteurs : Aurélien Regat-Barrel ,
Le mot-clé throw permet de lever une nouvelle exception, mais aussi de relancer celle qui est en cours de traitement.

#include <iostream>
#include <stdexcept>

void Test()
{
try
{
throw std::logic_error( "Exception de test" );
}
catch ( const std::logic_error & e )
{

- 173 -
Les sources présentées sur cette pages sont libres de droits, et vous pouvez les utiliser à votre convenance. Par contre la page de présentation de ces sources
constitue une oeuvre intellectuelle protégée par les droits d'auteurs. Copyright © 2008 Developpez LLC. Tout droits réservés Developpez LLC. Aucune
reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents et images sans l'autorisation expresse de Developpez
LLC. Sinon vous encourez selon la loi jusqu'à 3 ans de prison et jusqu'à 300 000 E de dommages et intérêts.
http://cpp.developpez.com/faq/cpp/
std::cerr << "L'exception '" << e.what()
<< "' a été levée et va être relancée.\n";
throw; // relancer l'exception courante
}
}

int main()
{
try
{
Test();
}
catch ( const std::logic_error & e )
{
std::cerr << "Erreur : " << e.what() << ".\n";
}
}

Que se passe-t-il si aucun bloc catch n'existe pour traiter une exception ?
Auteurs : Aurélien Regat-Barrel ,
Lorsqu'une exception est déclenchée, le compilateur recherche un bloc catch capable de la traiter. S'il n'en trouve pas,
il remonte la pile d'exécution (déroulage de la pile) afin d'en trouver un plus en amont dans la hiérarchie des appels.
Dépiler un appel revient à quitter une fonction. A cette occasion ses objets locaux sont détruits et les destructeurs
appelés, ce qui permet de quitter proprement la fonction en libérant toutes les ressources acquises si les destructeurs
on été bien écrits. L'objet qui a servi à lever l'exception est lui même détruit car il est local à la fonction. C'est pourquoi
l'objet qui est transmis aux blocs catch est toujours une copie de l'objet initial qui a déclenché l'exception.
Si la pile des appels est vidée (donc que l'on est arrivé à main) et qu'aucun bloc catch satisfaisant n'a été trouvé,
la fonction standard terminate est appelée ce qui provoque par défaut un arrêt pur et simple du programme. Ce
comportement peut être modifié au moyen de la fonction set_terminate définie dans l'en-tête standard <exception>.
Cette fonction installe un nouveau gestionnaire et renvoie l'adresse du précédent. Elle s'utilise de cette manière :

#include <iostream>
#include <exception>

// ancien gestionnaire
void (*old_handler)();

// gestionnaire personnalisé
void my_handler()
{
std::cerr << "Exception inattendue.\n";
// appel du gestionnaire par défaut
(old_handler)();
}

int main()
{
// installer notre gestionnaire personnalisé
old_handler = set_terminate( my_handler );
// lever une exception que l'on ne traite pas
throw "test";
}

Le code précédent provoque le résultat suivant avec le compilateur Visual C++ 7.1 :

- 174 -
Les sources présentées sur cette pages sont libres de droits, et vous pouvez les utiliser à votre convenance. Par contre la page de présentation de ces sources
constitue une oeuvre intellectuelle protégée par les droits d'auteurs. Copyright © 2008 Developpez LLC. Tout droits réservés Developpez LLC. Aucune
reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents et images sans l'autorisation expresse de Developpez
LLC. Sinon vous encourez selon la loi jusqu'à 3 ans de prison et jusqu'à 300 000 E de dommages et intérêts.
http://cpp.developpez.com/faq/cpp/
Exception inattendue. This application has requested the Runtime to terminate it in an unusual way. Please contact the
application's support team for more information.

Comment créer son propre type d'exception ?


Auteurs : Aurélien Regat-Barrel ,
N'importe quel type de base ou classe C++ peut être utilisé comme type d'exception. Mais il est préférable de créer son
type qui hérite de la classe de base standard pour les exceptions : std::exception définie dans l'en-tête <exception>. Cette
classe possède une fonction membre virtuelle what qu'il convient de redéfinir :

#include <iostream>
#include <sstream>
#include <exception>

class my_exception : public std::exception


{
public:
my_exception( const char * Msg, int Line )
{
std::ostringstream oss;
oss << "Erreur ligne " << Line << " : "
<< Msg;
this->msg = oss.str();
}

virtual ~my_exception() throw()


{

virtual const char * what() const throw()


{
return this->msg.c_str();
}

private:
std::string msg;
};

int main()
{
try
{
throw my_exception( "exception test", __LINE__ );
}
catch ( const std::exception & e )
{
std::cerr << e.what() << "\n";
}
}

Cet exemple produit le résultat suivant :

- 175 -
Les sources présentées sur cette pages sont libres de droits, et vous pouvez les utiliser à votre convenance. Par contre la page de présentation de ces sources
constitue une oeuvre intellectuelle protégée par les droits d'auteurs. Copyright © 2008 Developpez LLC. Tout droits réservés Developpez LLC. Aucune
reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents et images sans l'autorisation expresse de Developpez
LLC. Sinon vous encourez selon la loi jusqu'à 3 ans de prison et jusqu'à 300 000 E de dommages et intérêts.
http://cpp.developpez.com/faq/cpp/
Erreur ligne 29 : exception test

Peut-on lever des exceptions dans les constructeurs ?


Auteurs : Aurélien Regat-Barrel , Luc Hermitte ,
Tout à fait. C'est même une des seules manières d'indiquer que l'initialisation de l'objet a échoué. Il faut cependant être
prudent car une exception levée dans un constructeur peut être à l'origine de fuites de mémoires ou d'autres problèmes
de non libération de ressources. C'est le cas dans l'exemple suivant :

Exemple de mauvaise gestion d'exceptions


class Test
{
public:
// exception bad_alloc en cas de mémoire insuffisante
Test( int A, int B ) :
tableau1( 0 ),
tableau2( 0 )
{
// ici tableau1 et tableau2 vallent NULL, donc en cas d'échec d'allocation
// on peut appeler delete [] sans problème dans le destructeur
this->tableau1 = new int[ A ];
this->tableau2 = new int[ B ];
}
~Test()
{
delete [] this->tableau2;
delete [] this->tableau1;
}

private:
int * tableau1;
int * tableau2;
};

L'idée du code ci-dessus est d'initialiser les pointeurs tableau1 et tableau2 à zéro ainsi si l'une des allocations échoue on
peut tout de même appeler en toute sérénité delete [] dans le destructeur et ainsi éviter les fuites de mémoire (souvenez
vous, appeler delete sur un pointeur nul ne fait rien, voir Que se passe-t-il si je fais un delete sur un pointeur qui vaut
NULL ?).
Le problème est que si une exception est levée lors de la construction d'un objet, c'est donc que celle-ci a échoué, et donc
que l'objet n'est pas créé. Comme il n'est pas créé, il n'a pas à être détruit, et donc son destructeur ne sera pas appelé.
Autrement dit, si une exception est levée dans le constructeur d'un objet, son destructeur ne sera pas appelé.
Il faut donc toujours s'assurer que le code contenu dans le constructeur est exception safe, c'est-à-dire qu'il résiste aux
exceptions en ne provoquant pas de pertes de ressources. Dans notre exemple précédent cela signifie qu'il faut gérer
l'exception bad_alloc de cette manière :

class Test
{
public:
// exception bad_alloc en cas de mémoire insuffisante
Test( int A, int B ) :
tableau1( 0 )
{
// ici tableau1 vaut NULL, donc en cas d'échec d'allocation
// on peut appeler delete [] sans problème
try
{
this->tableau1 = new int[ A ];
this->tableau2 = new int[ B ];
}

- 176 -
Les sources présentées sur cette pages sont libres de droits, et vous pouvez les utiliser à votre convenance. Par contre la page de présentation de ces sources
constitue une oeuvre intellectuelle protégée par les droits d'auteurs. Copyright © 2008 Developpez LLC. Tout droits réservés Developpez LLC. Aucune
reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents et images sans l'autorisation expresse de Developpez
LLC. Sinon vous encourez selon la loi jusqu'à 3 ans de prison et jusqu'à 300 000 E de dommages et intérêts.
http://cpp.developpez.com/faq/cpp/
catch ( const std::bad_alloc & )
{
// tableau2 n'a pas été alloué quoi qu'il arrive
// libérer tableau1 s'il a pu être alloué
delete [] this->tableau1;
// relancer l'exception
throw;
}
}
~Test()
{
delete [] this->tableau2;
delete [] this->tableau1;
}

private:
int * tableau1;
int * tableau2;
};

Le nouveau code produit toujours une exception bad_alloc en cas d'échec d'allocation, mais cette fois-ci il n'y a plus
de fuite de mémoire. Ce qui a pu être alloué est libéré, et l'exception bad_alloc capturée est relancée via l'instruction
throw (à ce sujet lire Comment relancer une exception que l'on a capturé ?).
Il est à noter que si en cas d'exception dans le constructeur le destructeur n'est pas appelé, tous les membres construits
jusqu'au point de l'exception sont quant à eux bien détruits.

Peut-on lever des exceptions dans les destructeurs ?


Auteurs : Aurélien Regat-Barrel ,
Il est possible de lever une exception dans un destructeur, mais c'est extrêmement déconseillé et considéré comme une
très mauvaise pratique. La raison en est simple : si la destruction d'un objet échoue, que faut-il faire ? Mais aussi un
autre problème plus grave peut apparaître. Lorsqu'une exception est levée, la pile des appels est remontée (on parle de
stack unwinding ou déroulage de la pile) et à cette occasion les objets locaux de la fonction que l'on s'apprête à quitter
sont détruits. Donc leur destructeur respectif est appelé. Si l'un d'entre eux vient à lever une exception, la situation
devient alors très complexe : laquelle des deux exceptions faut-il gérer ? N'oubliez pas qu'à ce moment nous ne sommes
toujours pas dans un bloc catch, mais en train de nous y rendre en quittant les fonctions appelées qui nous en sépare.
Or, en quittant l'une d'entre elles, on détruit un objet qui lance une nouvelle exception, et nous nous retrouvons alors
avec deux exceptions à traiter en même temps. La situation étant insoluble, la norme définit que la fonction standard
terminate est appelée dans un tel cas, ce qui provoque la fin brutale du programme.
Pour cette très bonne raison, il est important que les destructeurs ne lèvent jamais d'exceptions. On peut s'en assurer
en appelant uniquement des fonctions n'échouant jamais (voir Comment indiquer qu'une fonction ne lève jamais
d'exception ?).

lien : Question de la C++ FAQ Lite ayant inspiré cette réponse

Comment indiquer qu'une fonction ne lève jamais d'exception ?


Auteurs : Aurélien Regat-Barrel ,
Pour indiquer qu'une fonction ne lève jamais d'exceptions (ce qui est important si on veut l'appeler depuis un
destructeur par exemple (voir Peut-on lever des exceptions dans les destructeurs ?), on rajoute l'instruction throw () à
la suite du prototype de la fonction de cette façon :

void Test() throw ()


{
}

- 177 -
Les sources présentées sur cette pages sont libres de droits, et vous pouvez les utiliser à votre convenance. Par contre la page de présentation de ces sources
constitue une oeuvre intellectuelle protégée par les droits d'auteurs. Copyright © 2008 Developpez LLC. Tout droits réservés Developpez LLC. Aucune
reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents et images sans l'autorisation expresse de Developpez
LLC. Sinon vous encourez selon la loi jusqu'à 3 ans de prison et jusqu'à 300 000 E de dommages et intérêts.
http://cpp.developpez.com/faq/cpp/
Attention à bien faire en sorte qu'aucune exception ne soit effectivement levée, car la norme dit que si tel était le cas la
fonction standard unexpected serait appelée, ce qui se traduirait par un arrêt brutal du programme.

Quel est l'équivalent C++ du bloc finally des autres langages ?


Auteurs : Aurélien Regat-Barrel ,
Dans certains langages (tels que Java ou C#), il est possible de créer un bloc finally à la suite d'un bloc try...catch afin
de s'assurer qu'une opération (de libération de ressource par exemple) soit bien effectuée. Par exemple, en C++ on ne
peut pas écrire ceci :

char * buffer = new char[ 100 ];


try
{
// opération suceptible de lever une exception
}
finally
{
// s'assurer que la mémoire est libérée
delete [] buffer;
}

l'équivalent de l'écriture ci-dessus serait :

char * buffer = new char[ 100 ];


try
{
// opération suceptible de lever une exception
}
catch ( ... )
{
// éviter les fuites de mémoire
delete [] buffer;
// relancer l'exception
throw;
}
// tout s'est bien passé, librérer la mémoire
delete [] buffer;

Mais il ne s'agit là que d'une traduction en C++ d'une approche issue d'un autre langage. Or, quand on programme
dans un langage, il convient de le faire selon les concepts propres à ce langage, et non avec ceux d'un autre. L'approche
C++ à ce problème consiste à encapsuler cette gestion au sein d'un objet qui s'assurera dans son destructeur de la bonne
libération de la ressource qu'il gère. Ainsi, on est assuré de ne pas avoir de fuite même en cas d'exception, tout en ayant
une écriture plus légère car le bloc try...catch devient inutile dans ce cas.
Ce principe s'appelle le RAII, et est développé dans la question Comment gérer proprement des allocations /
désallocations de ressources ? Le RAII !.

- 178 -
Les sources présentées sur cette pages sont libres de droits, et vous pouvez les utiliser à votre convenance. Par contre la page de présentation de ces sources
constitue une oeuvre intellectuelle protégée par les droits d'auteurs. Copyright © 2008 Developpez LLC. Tout droits réservés Developpez LLC. Aucune
reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents et images sans l'autorisation expresse de Developpez
LLC. Sinon vous encourez selon la loi jusqu'à 3 ans de prison et jusqu'à 300 000 E de dommages et intérêts.
http://cpp.developpez.com/faq/cpp/
Sommaire > Les chaînes de caractères
Y a-t-il un type chaîne de caractères en C++ ?
Auteurs : Aurélien Regat-Barrel ,
Le C++ standard possède son propre type chaîne de caractères : std::string. Celui-ci est déclaré dans l'en-tête standard
<string>.

#include <iostream> // pour std::cout


#include <string> // pour std::string

int main()
{
std::string message = "Hello"; // création de la chaîne "Hello"
message += " World !"; // concaténation de " Word !"

std::cout << message << '\n'; // affichage de "Hello World !"


}

string est le type chaîne de caractères standard du langage C++. Il est donc judicieux de l'utiliser en priorité sur les
char * qui sont hérités du langage C. Pour plus de détails à ce sujet, vous pouvez lire Quels sont les avantages de string
par rapport à char* ?.

Quels sont les avantages de string par rapport à char* ?


Auteurs : Aurélien Regat-Barrel ,
Au delà du simple conteneur de caractères, string est aussi et avant tout l'interface chaîne de caractères de la
bibliothèque standard. Autrement dit en utilisant string vous bénéficiez des très nombreuses autres fonctionnalités de
la bibliothèque standard, ce qui est plus délicat avec les char*. Par exemple, il est possible de lire une ligne d'un fichier
sans avoir à se préoccuper de sa taille.
Un travail important de gestion et de vérification est fait en toute transparence, ce qui rend le code plus maintenable.
string est donc beaucoup plus simple et sûre d'utilisation que les nombreuses fonctions utilisant les char*.

Utiliser string n'est-il pas plus lent ?


Auteurs : Aurélien Regat-Barrel ,
Programmer en utilisant std::string est incontestablement plus rapide, plus lisible et plus sûr que de programmer en
utilisant les antiques char*. Si votre but est d'écrire un programme qui marche et qui soit facile à maintenir, utiliser
string est un bien meilleur choix. Il est en effet tentant de dire qu'utiliser les char* fait surtout planter le programme
plus vite.
Il faut aussi relativiser la possible lenteur de string par le fait que l'écart est très souvent sans conséquence pour
l'utilisateur. Même si lire et afficher une chaîne de caractères est deux fois plus lent avec les string qu'avec les char*,
l'écart est dans ce cas dérisoire voire non mesurable. Néanmoins, il est des cas où une application manipule un très grand
nombre de chaînes de caractères, et on peut alors, à juste titre, se poser la question. La réponse n'est pas évidente. Tout
d'abord, le premier point en faveur des string est que celles-ci connaissent en permanence leur longueur (fonction size()
ou length()), qui n'a donc pas besoin d'être calculée, contrairement aux char* ou il faut systématiquement effectuer un
appel à strlen(). Ainsi, plus la longueur des chaînes manipulées est importante, et plus string se révèlera performante
vis à vis des char*.
Un autre point important concerne la manière dont sont gérées les char*. Beaucoup de programmeurs ne veulent pas
s'embêter et définissent généralement une taille commune à toutes leurs chaînes de caractères.

#define MAX_SIZE 100

- 179 -
Les sources présentées sur cette pages sont libres de droits, et vous pouvez les utiliser à votre convenance. Par contre la page de présentation de ces sources
constitue une oeuvre intellectuelle protégée par les droits d'auteurs. Copyright © 2008 Developpez LLC. Tout droits réservés Developpez LLC. Aucune
reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents et images sans l'autorisation expresse de Developpez
LLC. Sinon vous encourez selon la loi jusqu'à 3 ans de prison et jusqu'à 300 000 E de dommages et intérêts.
http://cpp.developpez.com/faq/cpp/
void Exemple( void )
{
char nom[ MAX_SIZE ];

printf( "Veuillez entrer votre nom : " );


fgets( stdin, nom, MAX_SIZE );
}

Là aussi, string est meilleur car non seulement le problème (inévitable) de la fois où cette taille limite sera atteinte ne
se pose pas, mais aussi parce que ce code C provoque une perte plus ou moins importante de mémoire (typiquement,
MAX_SIZE vaut 100 voire 1000, c'est-à-dire que même si une chaîne ne comporte que 10 caractères, elle occupera
100 ou 1000 octets en mémoire !). La comparaison doit donc être faite avec une gestion dynamique de char*, chose
laborieuse à gérer en C et faite de manière transparente et sûre par string.
Enfin, il est difficile de généraliser sur les string dans la mesure où celles-ci sont spécifiques à un compilateur (et même
souvent une version de compilateur) et ce qui est vrai pour une implémentation ne l'est pas forcément pour une autre
(d'autant plus que la capacité d'optimisation du compilateur entre aussi en jeu). Ainsi, par exemple, la classe string
de Visual C++ 6 implémente le Copy On Write (pas de copie réelle de contenu entre 2 string, mais un partage via un
comptage de références) qui permet d'effectuer des affectations très rapides entre string. La version 7 de ce compilateur
n'implémente plus cette fonctionnalité mais intègre un petit buffer de 16 octets destiné à contenir directement les chaînes
de petite taille. Cela permet de se passer d'allocation dynamique et donc d'augmenter les performances qui sont du
coup égales à celles d'un tableau de char (dans le cas de petites chaînes).
La comparaison de performances entre string et char* est donc difficile, et elle ne peut être qu'au cas par cas. Bien
souvent, l'écart de performance entre les deux est de l'ordre de quelques pour cent.
Vous l'aurez compris, la performance est loin d'être le seul critère, aussi faites confiance aux nombreux développeurs
de talent qui ont développé le C++ et utilisez le type chaînes de caractères qui lui est propre : string.

Quelle est la différence entre char*, const char* et char const * ?


Auteurs : Luc Hermitte ,
const char * et char const * ont la même signification : un pointeur sur un caractère constant. La règle est que le const
s'applique toujours sur ce qui le précède. S'il n'y a rien avant, alors on inverse sa position avec ce qui est juste après.
Utiliser const signifie qu'il ne faut pas modifier le(s) caractère(s) référencé(s) par le pointeur.
Donc, typiquement, une fonction qui prend un char* déclare qu'elle modifiera le contenu du buffer pointé par le
pointeur (accès en écriture). Une fonction qui prend un const char* déclare qu'elle va lire le contenu du buffer sans
le modifier (accès en lecture seule).

Quelle est la différence entre #include <string> et #include <string.h> ?


Auteurs : Aurélien Regat-Barrel ,
<string> et <string.h> sont deux fichiers d'en-tête totalement différents. Le premier est l'en-tête standard C++ qui
définit le type std::string. Le second est un en-tête hérité du langage C qui définit diverses fonctions C de manipulation
de chaînes de caractères. Il est à noter qu'inclure <string.h> est obsolète, il convient d'inclure <cstring> à la place.
A ce sujet lire aussi Quelle est le différence entre #include <iostream.h> et #include <iostream> ?.

Quelle est la différence entre string::length() et string::size() ?


Auteurs : Aurélien Regat-Barrel ,
Aucune ! Ces deux fonctions renvoient toutes les deux la longueur de la chaîne. En fait, length() retourne size(), mais
on est libre d'utiliser l'une ou l'autre.

- 180 -
Les sources présentées sur cette pages sont libres de droits, et vous pouvez les utiliser à votre convenance. Par contre la page de présentation de ces sources
constitue une oeuvre intellectuelle protégée par les droits d'auteurs. Copyright © 2008 Developpez LLC. Tout droits réservés Developpez LLC. Aucune
reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents et images sans l'autorisation expresse de Developpez
LLC. Sinon vous encourez selon la loi jusqu'à 3 ans de prison et jusqu'à 300 000 E de dommages et intérêts.
http://cpp.developpez.com/faq/cpp/
Voir aussi Quelle différence entre string::size() et string::capacity() ?

Quelle différence entre string::size() et string::capacity() ?


Auteurs : Aurélien Regat-Barrel ,
size renvoie le nombre de caractères contenus dans la chaîne (par exemple 4 pour "abcd") et capacity la capacité de
stockage de la chaîne, c'est-à-dire le nombre total de caractères qu'elle peut stocker sans nécessiter une réallocation.
capacity est donc toujours au moins égal à size, et peut être plus élevé, en particulier si vous faites un appel à reserve.

#include <string>
#include <iostream>

int main()
{
using namespace std;

string s; // la chaîne est vide


cout << "size = " << s.size() << '\t' // size = 0
<< "capacity = " << s.capacity() // capacity = 15
<< '\n';

// on sait que l'on va faire de nombreuses concaténations,


// alors on réserve de l'espace pour éviter de multiples allocations
s.reserve( 26 );
cout << "size = " << s.size() << '\t' // size = 0
<< "capacity = " << s.capacity() // capacity = 31
<< '\n';

// concaténer les 26 lettres de l'alphabet


for ( char c = 'A'; c <= 'Z'; ++c )
{
s += c;
}

cout << "size = " << s.size() << '\t' // size = 26


<< "capacity = " << s.capacity() // capacity = 31
<< '\n';

s = "une assez longue chaîne qui oblige a faire une allocation";


cout << "size = " << s.size() << '\t' // size = 57
<< "capacity = " << s.capacity() // capacity = 63
<< '\n';
}

La valeur retournée par capacity peut varier d'un compilateur à l'autre, ou plutôt d'une implémentation de la STL à
l'autre. C'est pourquoi il est fort probable que vous n'obteniez pas les mêmes résultats si votre compilateur n'est pas
celui utilisé pour ce test (Visual C++ .Net 2003). Par contre quelque soit votre compilateur les valeurs retournées par
size devraient être les même.
Voir aussi Quelle est la différence entre string::length() et string::size() ?

Comment convertir un char* en un string ?


Auteurs : JEG , LFE , Aurélien Regat-Barrel ,
En utilisant tout simplement le constructeur ou l'opérateur d'affectation de la classe string :

#include <string>

- 181 -
Les sources présentées sur cette pages sont libres de droits, et vous pouvez les utiliser à votre convenance. Par contre la page de présentation de ces sources
constitue une oeuvre intellectuelle protégée par les droits d'auteurs. Copyright © 2008 Developpez LLC. Tout droits réservés Developpez LLC. Aucune
reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents et images sans l'autorisation expresse de Developpez
LLC. Sinon vous encourez selon la loi jusqu'à 3 ans de prison et jusqu'à 300 000 E de dommages et intérêts.
http://cpp.developpez.com/faq/cpp/
// construction
std::string str1 = "coucou";
std::string str2( "coucou" );
// affectation
std::string str3;
str3 = "coucou";
str3.assign( "coucou" );

Comment convertir une string en char* ?


Auteurs : LFE , Aurélien Regat-Barrel ,
Même en C++ on est parfois obligé d'utiliser des char *. Pour obtenir une chaîne de caractères C non modifiable (const
char *), il suffit d'appeler la fonction c_str() de string.

string file_name = "fichier.txt";


// ouverture du fichier
ifstream file( file_name.c_str() ); // le constructeur de std::ifstream n'accepte pas de string

La chaîne renvoyée est qualifiée comme non modifiable via le mot-clé const. Il ne faut pas chercher à la modifier. A ce
sujet, lire les questions Quelle est la différence entre char*, const char* et char const * ? et Quelles précautions faut-
il prendre avec string::c_str() et string::data() ?.
Pour obtenir une chaîne de type C modifiable, il faut créer une copie de la string.

#include <string>
#include <cstring>
#include <iostream>

int main()
{
using namespace std;

string str = "une chaîne de caractères";


// créer le buffer pour copier la chaîne
size_t size = str.size() + 1;
char * buffer = new char[ size ];
// copier la chaîne
strncpy( buffer, str.c_str(), size );
// utiliser le buffer
cout << buffer << '\n'; // "une chaîne de caractères"
// libérer la mémoire
delete [] buffer;
}

Comment convertir un nombre en une string ?


Auteurs : Aurélien Regat-Barrel ,
L'utilisation d'un objet ostringstream permet de convertir un entier en une string :

#include <sstream>

int main()
{
// créer un flux de sortie
std::ostringstream oss;
// écrire un nombre dans le flux
oss << 10;
// récupérer une chaîne de caractères

- 182 -
Les sources présentées sur cette pages sont libres de droits, et vous pouvez les utiliser à votre convenance. Par contre la page de présentation de ces sources
constitue une oeuvre intellectuelle protégée par les droits d'auteurs. Copyright © 2008 Developpez LLC. Tout droits réservés Developpez LLC. Aucune
reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents et images sans l'autorisation expresse de Developpez
LLC. Sinon vous encourez selon la loi jusqu'à 3 ans de prison et jusqu'à 300 000 E de dommages et intérêts.
http://cpp.developpez.com/faq/cpp/
std::string result = oss.str();
}

Pour une solution générique à ce problème, consultez Comment convertir n'importe quel type d'objets en string ?. Pour
éviter les erreurs, lire aussi [Piège] Comment initialiser/affecter un nombre à une string ?.

Comment convertir une string en un entier ?


Auteurs : Aurélien Regat-Barrel ,
En utilisant istringstream:

#include <sstream>

int main()
{
// créer un flux à partir de la chaîne à convertir
std::istringstream iss( "10" );
// convertir en un int
int nombre;
iss >> nombre; // nombre vaut 10
}

Vous pouvez consulter Comment convertir une string en un objet de n'importe quel type ? pour une utilisation plus
générique de cette solution.

lien : Comment déterminer si une chaîne contient une valeur d'un certain type ?

Comment convertir n'importe quel type d'objets en string ?


Auteurs : Aurélien Regat-Barrel ,
L'utilisation de ostringstream permet de convertir en une string n'importe quel objet pour lequel l'opérateur
ostream::operator <<() a été défini. Il est donc possible de créer une fonction générique de conversion de n'importe quel
objet en une string grâce à l'utilisation des templates :

#include <sstream>

template<typename T>
std::string to_string( const T & Value )
{
// utiliser un flux de sortie pour créer la chaîne
std::ostringstream oss;
// écrire la valeur dans le flux
oss << Value;
// renvoyer une string
return oss.str();
}

int main()
{
std::string num = to_string( 10 );

- 183 -
Les sources présentées sur cette pages sont libres de droits, et vous pouvez les utiliser à votre convenance. Par contre la page de présentation de ces sources
constitue une oeuvre intellectuelle protégée par les droits d'auteurs. Copyright © 2008 Developpez LLC. Tout droits réservés Developpez LLC. Aucune
reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents et images sans l'autorisation expresse de Developpez
LLC. Sinon vous encourez selon la loi jusqu'à 3 ans de prison et jusqu'à 300 000 E de dommages et intérêts.
http://cpp.developpez.com/faq/cpp/
}

Comment convertir une string en un objet de n'importe quel type ?


Auteurs : Aurélien Regat-Barrel ,
L'utilisation de istringstream permet de convertir une string en n'importe quel type pour lequel l'opérateur
istream::operator >>() a été défini. Il est donc possible de créer une fonction générique de conversion d'une string en
un autre type grâce à l'utilisation des templates :

#include <sstream>

template<typename T>
bool from_string( const std::string & Str, T & Dest )
{
// créer un flux à partir de la chaîne donnée
std::istringstream iss( Str );
// tenter la conversion vers Dest
return iss >> Dest != 0;
}

int main()
{
int dix;
from_string( "10", dix );
}

Pour la conversion d'une string en une chaîne de caractères constante (const char *), utilisez la fonction membre c_str()
de std::string.
Pour la conversion d'une string en une chaîne de caractères modifiable, consultez Comment convertir une string en
char* ? ? Pour des détails sur le principe de la conversion avec l'opérateur >>, consultez Comment fonctionne le test
de réussite de conversion if ( str >> num ) ?

lien : Comment déterminer si une chaîne contient une valeur d'un certain type ?

Comment déterminer si une chaîne contient une valeur d'un certain type ?
Auteurs : LFE , Aurélien Regat-Barrel ,
Il suffit de tester le résultat de l'opération de conversion, comme le fait la fonction générique from_string de la question
Comment convertir une string en un objet de n'importe quel type ?. Ce principe est expliqué dans Comment fonctionne
le test de réussite de conversion if ( str >> num ) ?

#include <sstream>

bool is_float( const std::string & Str )


{
// créer un flux à partir de la chaîne donnée
std::istringstream iss( Str );
// créer un objet temporaire pour la conversion
float tmp;
// tenter la conversion et
// vérifier qu'il ne reste plus rien dans la chaîne
return ( iss >> tmp ) && ( iss.eof() );
}

int main()
{
is_float( "10.0" ); // vrai

- 184 -
Les sources présentées sur cette pages sont libres de droits, et vous pouvez les utiliser à votre convenance. Par contre la page de présentation de ces sources
constitue une oeuvre intellectuelle protégée par les droits d'auteurs. Copyright © 2008 Developpez LLC. Tout droits réservés Developpez LLC. Aucune
reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents et images sans l'autorisation expresse de Developpez
LLC. Sinon vous encourez selon la loi jusqu'à 3 ans de prison et jusqu'à 300 000 E de dommages et intérêts.
http://cpp.developpez.com/faq/cpp/
is_float( "abcd" ); // faux
is_float( "10.0abcd" ); // faux grâce au 2° test
}

Après avoir converti avec succès Str en un float, on tente d'extraire une chaîne, opération qui ne peut échouer que s'il
ne reste plus rien une fois le float extrait. Cela permet de faire échouer le troisième test avec "10.0abcd".
Cette solution spécifique aux float peut aisément être généralisée grâce aux templates :

#include <sstream>

template<typename T>
bool is_of_type( const std::string & Str )
{
// créer un flux à partir de la chaîne donnée
std::istringstream iss( Str );
// créer un objet temporaire pour la conversion
T tmp;
// tenter la conversion et
// vérifier qu'il ne reste plus rien dans la chaîne
return ( iss >> tmp ) && ( iss.eof() );
}

int main()
{
is_of_type<float>( "10.5" ); // vrai
is_of_type<int>( "10.5" ); // faux grâce au 2° test
}

L'utilisation des templates nécessite ici leur instanciation explicite, c'est-à-dire de spécifier au moment de l'utilisation de
la fonction le type dont on souhaite vérifier la conversion depuis une string. Comme le montre cet exemple, la réussite ou
non de la conversion est déterminée par le comportement de l'opérateur >> pour le type donné. La fonction is_of_type
devrait donc plutôt s'appeler is_convertible_in_type. Pour tester avec plus de rigueur le contenu d'une string il faut donc
se tourner vers une autre solution, comme l'utilisation d'expressions régulières.

lien : Comment convertir une string en un objet de n'importe quel type ?

Comment fonctionne le test de réussite de conversion if ( str >> num ) ?


Auteurs : Aurélien Regat-Barrel ,
Pour que le code suivant compile :

istringstream s( "10" );
int n;
if ( s >> n )
{
}

Le compilateur cherche à convertir l'expression s >> n en un booléen. Il commence par rechercher un opérateur
applicable et trouve celui hérité de istream qui accepte un int. Cela revient donc à avoir le code suivant :

istringstream s( "10" );
int n;
if ( s.operator >>( n ) )
{
}

Cet opérateur retourne une référence sur le flux utilisé, c'est-à-dire ici la variable s. Le compilateur essaye alors de
convertir cette référence (qui correspond à s après l'appel de l'opérateur >>, donc après avoir tenté la conversion en int)

- 185 -
Les sources présentées sur cette pages sont libres de droits, et vous pouvez les utiliser à votre convenance. Par contre la page de présentation de ces sources
constitue une oeuvre intellectuelle protégée par les droits d'auteurs. Copyright © 2008 Developpez LLC. Tout droits réservés Developpez LLC. Aucune
reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents et images sans l'autorisation expresse de Developpez
LLC. Sinon vous encourez selon la loi jusqu'à 3 ans de prison et jusqu'à 300 000 E de dommages et intérêts.
http://cpp.developpez.com/faq/cpp/
en booléen. istream ne définit pas d'opérateur de conversion vers bool, mais une conversion implicite est possible grâce
à l'opérateur operator void*(). Ce dernier retourne un pointeur nul si les indicateurs d'échec du flux sont positionnés,
ce qui est le cas en cas d'échec de conversion. L'ensemble du processus effectué par le compilateur correspond donc
aux appels explicites suivants :

istringstream s( "10" );
int n;
if ( s.operator >>( n ).operator void*() != 0 )
{
}

Quelle est la différence entre string::data() et string::c_str() ?


Auteurs : Aurélien Regat-Barrel ,
c_str() retourne un pointeur sur une chaîne de caractères constante terminée par le caractère nul (chaîne de caractères
C). data() retourne aussi un pointeur sur une chaîne de caractères constante, mais la présence du caractère terminal
nul n'est pas exigée donc non garantie. Donc c_str() renvoie un pointeur constant sur un buffer contenant size() + 1
caractères et data() sur un buffer de size() caractères.

Quelles précautions faut-il prendre avec string::c_str() et string::data() ?


Auteurs : Aurélien Regat-Barrel ,
Il ne faut faire aucune hypothèse quant à la façon dont est implémentée la classe string. Le comportement de cette
dernière est spécifique à presque chaque version de compilateur C++ existant.
Par exemple, les caractères peuvent ne pas être stockés en interne de manière contiguë (on peut envisager un système
de concaténation rapide via un chaînage de sous chaînes de caractères).
Ou encore, certaines implémentations utilisent le Copy On Write (COW) qui implique que plusieurs objets string
peuvent en interne partager le même espace mémoire pour stocker leurs caractères.
Le seul point commun à toutes ces implémentations est que l'on est assuré que le pointeur retourné par c_str() ou data()
désigne une chaîne de caractères contigus. Mais rien n'empêche celui-ci de pointer vers une copie créée pour l'occasion !
C'est pourquoi il est très important de ne jamais modifier la chaîne retournée par ces fonctions.
Un autre point important est que ce pointeur peut être invalidé suite à une modification, et que sa durée de vie n'excède
pas celle de l'objet associé. Le code suivant illustre ces deux points :

#include <string>

int main()
{
using std::string;

string str( "Hello" );


const char * c_str = str.c_str(); // ok

// erreur 1 : invalidation de pointeur suite à une modification


str += " World !"; // concaténer " World !" à "Hello"
// maintenant c_str est invalide, et ne doit plus être utilisé !

c_str = str.c_str();
// erreur 2 : l'utilisation de [] peut aussi invalider c_str
char c = str[ 0 ]; // ok, accès en lecture seulement
str[ 0 ] = 'A'; // modification d'un caractère
// maintenant c_str est invalide, et ne doit plus être utilisé !

// erreur 3 : invalidation de pointeur suite à une destruction


{

- 186 -
Les sources présentées sur cette pages sont libres de droits, et vous pouvez les utiliser à votre convenance. Par contre la page de présentation de ces sources
constitue une oeuvre intellectuelle protégée par les droits d'auteurs. Copyright © 2008 Developpez LLC. Tout droits réservés Developpez LLC. Aucune
reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents et images sans l'autorisation expresse de Developpez
LLC. Sinon vous encourez selon la loi jusqu'à 3 ans de prison et jusqu'à 300 000 E de dommages et intérêts.
http://cpp.developpez.com/faq/cpp/
// variable temporaire à ce bloc
string str2( "Temporaire" );
c_str = str2.c_str(); // ok
} // fin du bloc : str2 est détruite
// maintenant c_str est invalide car la chaîne pointée a été détruite !
}

Le code ci-dessus compile parfaitement, mais provoque un certain nombre d'erreurs d'exécution qui varie en fonction
de l'implémentation de string utilisée.

Quelle est la différence entre string::find() et string::find_first_of() ?


Auteurs : Aurélien Regat-Barrel ,
string (mais aussi <algorithm>) possède deux fonctions de recherche qui toutes les deux recherchent la première
occurrence d'un élément :

string s = "abcdef";
if ( s.find( 'c' ) == s.find_first_of( 'c' ) )
{
// ce test est vrai
}

La différence entre les deux est que find recherche la première occurrence d'un caractère ou d'une chaîne de caractères
dans une string tandis que find_first_of recherche le premier caractère de la chaîne qui soit égal à l'un de ceux donnés
en paramètre. Donc dans le cas de la recherche d'un caractère il n'y a pas de différence entre les deux, mais ces deux
fonctions sont très différentes si leur argument est une chaîne de caractères. find considèrera cette chaîne comme une
sous chaîne à rechercher, et find_first_of comme une liste de caractères à rechercher.

string s = "abcba";

cout << s.find( "ba" ) << '\n'; // affiche 3


cout << s.find_first_of( "ba" ) << '\n'; // affiche 0

L'appel à find retourne la position de la sous chaîne "ba" dans "abcba" (3° position). L'appel à find_first_of renvoie la
position de la première occurrence de n'importe lequel des caractères passés en paramètre, c'est-à-dire ici 0 (première
lettre de "abcba" = a qui figure bien dans la liste "ba").

Comment manipuler des chaînes de caractères Unicode ?


Auteurs : Aurélien Regat-Barrel ,
De la même manière que les chaînes ANSI, mais au moyen du type std::wstring qui est défini dans le fichier d'en-tête
standard <string>. Il s'agit d'une spécialisation de std::basic_string pour le type wchar_t (caractère Unicode) au même
titre que std::string l'est pour le type char.

#include <string>
#include <iostream>

int main()
{
std::wstring s = L"Chaîne unicode";
wchar_t c = s[ 0 ]; // c = L'C'
std::wcout << s;
}

- 187 -
Les sources présentées sur cette pages sont libres de droits, et vous pouvez les utiliser à votre convenance. Par contre la page de présentation de ces sources
constitue une oeuvre intellectuelle protégée par les droits d'auteurs. Copyright © 2008 Developpez LLC. Tout droits réservés Developpez LLC. Aucune
reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents et images sans l'autorisation expresse de Developpez
LLC. Sinon vous encourez selon la loi jusqu'à 3 ans de prison et jusqu'à 300 000 E de dommages et intérêts.
http://cpp.developpez.com/faq/cpp/
Notez qu'une version Unicode existe aussi pour les flux, afin de les rendre utilisables avec wstring et wchar_t. Ainsi, on
utilisera wcout pour l'affichage, wcin pour la saisie, wifstream pour la lecture de fichiers, ...
Cependant, en ce qui concerne les fichiers, les caractères wchar_t sont convertis de manière transparente en char au
moment de l'écriture. Le C++ standard ne permet pas en effet de manipuler des fichiers Unicode.

Pour réaliser les conversions string <-> wstring, voir Comment effectuer les conversions de texte ASCII <-> Unicode ?.

Comment manipuler des chaînes de caractères ne tenant pas compte de la casse ?


Auteurs : Laurent Gomila ,
Le type standard pour manipuler des chaînes en C++ est std::string. Mais si l'on regarde de plus près, on s'aperçoit
qu'il ne s'agit en fait que qu'un typedef :

namespace std
{
typedef basic_string<char, char_traits<char>, allocator<char> > string;
}

Le premier paramètre représente le type des caractères manipulés, le troisième est l'allocateur et nous importe peu ici.
Ce qui est intéressant, c'est le second paramètre qui définit les opérations de base (recherches, comparaisons, ...) sur le
type manipulé. Ainsi, pour créer des chaînes de caractères ne tenant pas compte de la casse (différence entre majuscules
et minuscules) il suffit de créer un char_traits perso :

struct ci_char_traits : public std::char_traits<char>


// on dérive du char_traits par défaut, ainsi on hérite des fonctions que nous ne voulons pas redéfinir
{
static bool eq(char c1, char c2)
{
return toupper(c1) == toupper(c2);
}

static bool ne(char c1, char c2)


{
return toupper(c1) != toupper(c2);
}

static bool lt(char c1, char c2)


{
return toupper(c1) < toupper(c2);
}

static int compare(const char* s1, const char* s2, size_t n)


{
return memicmp(s1, s2, n); // si disponible sur votre compilateur
}

static const char* find(const char* s, int n, char a)


{
while ((n-- > 0) && (toupper(*s) != toupper(a)))
++s;

return s;
}
};

Il ne nous reste plus ensuite qu'à créer le basic_string correspondant :

- 188 -
Les sources présentées sur cette pages sont libres de droits, et vous pouvez les utiliser à votre convenance. Par contre la page de présentation de ces sources
constitue une oeuvre intellectuelle protégée par les droits d'auteurs. Copyright © 2008 Developpez LLC. Tout droits réservés Developpez LLC. Aucune
reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents et images sans l'autorisation expresse de Developpez
LLC. Sinon vous encourez selon la loi jusqu'à 3 ans de prison et jusqu'à 300 000 E de dommages et intérêts.
http://cpp.developpez.com/faq/cpp/
typedef std::basic_string<char, ci_char_traits> ci_string;

L'avantage est que celui-ci se manipule exactement comme un std::string normal :

ci_string s1 = "salut";
ci_string s2 = "SAluT";

cout << (s1 == s2); // affiche "1" (vrai)

Pour des informations plus complètes, vous pouvez jeter un oeil à l'item n°29 des GOTW (Guru Of The Week).

Comment convertir une string en minuscules / majuscules ?


Auteurs : Aurélien Regat-Barrel , Laurent Gomila ,
D'une manière générale, le problème de la conversion d'une chaîne majuscule en minuscule (ou inversement) est bien
plus complexe qu'il n'y paraît. C'est un sujet qui débordre du cadre de cette FAQ, mais voici un rapide aperçu des
problèmes soulevés :

• Les caractères accentués posent problème. En général, on souhaite convertir le mot "fête" en "FETE" et non en
"FÊTE" (ce qui est assez simple à faire), et inversement le mot "FETE" en "fête", ce qui implique de convertir
chaque caractère en fonction du contexte et du mot dans lequel il apparait (notion d'orthographe). Quelques
fois, même par un humain, il est impossible de trancher (par exemple avec la phrase "UN INTERNE TUE A
L'ASILE PSYCHIATRIQUE", est-ce un interne / un interné qui tue / a été tué ?).
• Certains caractères ne doivent pas être convertis en minuscule, comme celui du début d'une phrase ou encore
d'un nom propre. Par exemple, la phrase "MERCI M. DUPONT" devrait normalement être convertie en
"Merci M. Dupont". Bien évidément c'est une tâche extrêmement complexe à réaliser de manière automatisée.
• Dans certaines langues, un caractère majuscule ne correspond pas forcément à un seul caractère minuscule,
et inversement. Ainsi, en Allemand par exemple, la combinaison de majuscules SS peut correspondre soit à la
combinaison de minuscule ss, soit à l'unique lettre minuscule ß. Et quelques fois les deux sont même permis
(Masse/Maße). Ajoutez à cela que les Suisses et les Autrichiens ont des règles différentes, et l'on comprend
mieux la complexité du problème.

Si l'on se recentre sur le C++, la solution classique (qui est aussi celle généralement attendue) est d'effectuer une
conversion in-place ("en place", c'est-à-dire directement sur la chaîne, caractère par caractère) au moyen de toupper
(pour mettre en majuscule) ou tolower (pour mettre en minuscule). std::transform est traditionnellement utilisé pour
appliquer une des ces fonctions à chaque caractère d'une chaîne.

Pour convertir une string en minuscules ou en majuscules, il faut appliquer la fonction std::tolower / std::toupper à
chaque caractère, ce que l'on réaliser par exemple grâce à std::transform.
Cependant, std::tolower et std::toupper attendent un paramètre compris dans l'intervalle [0...UCHAR_MAX] (ou
EOF), et un char peut potentiellement être négatif (les caractères accentués, par exemple). Utiliser directement ces
fonctions provoquerait donc un comportement indéfini par la norme, c'est pourquoi il faut ajouter un traitement
intermédiaire, afin de convertir les caractères dans le bon type (unsigned char ici).

#include <cctype> // pour tolower et toupper


#include <string> // pour string
#include <iostream> // pour cout
#include <algorithm> // pour transform

struct my_tolower

- 189 -
Les sources présentées sur cette pages sont libres de droits, et vous pouvez les utiliser à votre convenance. Par contre la page de présentation de ces sources
constitue une oeuvre intellectuelle protégée par les droits d'auteurs. Copyright © 2008 Developpez LLC. Tout droits réservés Developpez LLC. Aucune
reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents et images sans l'autorisation expresse de Developpez
LLC. Sinon vous encourez selon la loi jusqu'à 3 ans de prison et jusqu'à 300 000 E de dommages et intérêts.
http://cpp.developpez.com/faq/cpp/
{
char operator()(char c) const
{
return std::tolower(static_cast<unsigned char>(c));
}
};

struct my_toupper
{
char operator()(char c) const
{
return std::toupper(static_cast<unsigned char>(c));
}
};

int main()
{
std::string s("ABCDEF");

std::transform(s.begin(), s.end(), s.begin(), my_tolower());


std::cout << s; // affiche "abcdef"

std::transform(s.begin(), s.end(), s.begin(), my_toupper());


std::cout << s; // affiche "ABCDEF"

return 0;
}

Si vous n'êtes pas familier avec l'écriture utilisée pour my_tolower et my_toupper, voir Qu'est-ce qu'un foncteur ?.

Ceux qui recherchent des explications poussées et une solution complètement fonctionnelle pour la conversion
minuscules / majuscules, peuvent consulter cette discussion sur fr.comp.lang.c++.

Comment inverser le contenu d'une chaîne ?


Auteurs : Laurent Gomila ,
Le plus simple est d'utiliser la fonction std::reverse() sur la chaîne existante, ou d'en créer une nouvelle en passant un
itérateur inverse au constructeur de std::string.

#include <algorithm> // pour std::reverse()


#include <string>

std::string chaine = "Bonjour";

// Inversion directe de la chaîne


std::reverse(chaine.begin(), chaine.end());

// Autre méthode : création d'une chaîne inversée


std::string inverse( chaine.rbegin(), chaine.rend() );

Comment découper une chaîne en fonction d'un séparateur ?


Auteurs : Aurélien Regat-Barrel ,
std::string ne dispose pas de fonction équivalente à la fonction C standard strtok(). Il existe de nombreuses façons
de réaliser un équivalent à cette fonction. L'une des solutions les plus simples et de procéder au découpage sur un
istringstream au moyen de std::getline():

- 190 -
Les sources présentées sur cette pages sont libres de droits, et vous pouvez les utiliser à votre convenance. Par contre la page de présentation de ces sources
constitue une oeuvre intellectuelle protégée par les droits d'auteurs. Copyright © 2008 Developpez LLC. Tout droits réservés Developpez LLC. Aucune
reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents et images sans l'autorisation expresse de Developpez
LLC. Sinon vous encourez selon la loi jusqu'à 3 ans de prison et jusqu'à 300 000 E de dommages et intérêts.
http://cpp.developpez.com/faq/cpp/
#include <sstream>
#include <string>
#include <iostream>

using namespace std;

int main()
{
istringstream iss( "mot1;mot2;mot3;mot4" );
string mot;
while ( std::getline( iss, mot, ';' ) )
{
cout << mot << '\n';
}
}

Par défaut getline() sert à extraire des chaînes délimitées par un saut de ligne, mais comme le montre l'exemple précédent
il est possible de spécifier un autre séparateur (ici le caractère ';').
Attention : getline() va considérer tout ce qui se trouve entre deux ';' comme étant une ligne à extraire. Cela veut dire
que la phrase "mot1 mot2;;mot3" (notez le double point virgule entre mot2 et mot3) sera découpée en "mot1 mot2",
"" (chaîne vide) et "mot3". Cette utilisation de getline() ne permet aussi de spécifier qu'un seul séparateur. Si vos mots
sont séparés par de simples espaces, vous pouvez aussi vous inspirer de [Exemple] Comment manipuler un tableau de
string ?.

Pour quelque chose de plus évolué, vous pouvez vous tourner vers boost::tokenizer (voir Comment découper une
chaîne avec boost::tokenizer ?) ou encore réaliser votre propre fonction de découpage en vous inspirant de ceci :
Breaking a C++ string into tokens.

Dans quels cas ne faut-il pas utiliser string ?


Auteurs : Aurélien Regat-Barrel ,
<string> effectue en général une allocation dynamique afin de stocker la chaîne de caractères. Cette allocation peut
échouer si la mémoire fait défaut. Il faut donc être prudent avec l'utilisation de string et plus généralement des
conteneurs de la STL lors de la gestion d'une exception bad_alloc.

void Exemple()
{
try
{
// effectuer une allocation
}
catch ( std::bad_alloc & )
{
// ATTENTION : utiliser std::string ou tout autre conteneur standard
// peut déclencher une nouvelle exception std::bad_alloc
}
}

- 191 -
Les sources présentées sur cette pages sont libres de droits, et vous pouvez les utiliser à votre convenance. Par contre la page de présentation de ces sources
constitue une oeuvre intellectuelle protégée par les droits d'auteurs. Copyright © 2008 Developpez LLC. Tout droits réservés Developpez LLC. Aucune
reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents et images sans l'autorisation expresse de Developpez
LLC. Sinon vous encourez selon la loi jusqu'à 3 ans de prison et jusqu'à 300 000 E de dommages et intérêts.
http://cpp.developpez.com/faq/cpp/
C'est pour cela que la classe de base des exceptions std::exception n'utilise pas string pour sa fonction membre what
qui renvoie un message décrivant l'exception. Il ne faut donc pas chercher à le faire lorsque l'on crée sa propre classe
exception.

Comment tester des chaînes de caractères dans un bloc switch ?


Auteurs : Aurélien Regat-Barrel ,
On ne peut pas. Le code suivant ne compile pas :

// fonctions de traitement
void parametre_input();
void parametre_output();
void parametre_inconnu();

// appel des fonctions de traitement


void analyse_parametre( const char * Param )
{
switch ( Param )
{
case "/input":
parametre_input(); break;

case "/output":
parametre_output(); break;

default:
parametre_inconnu();
}
}

Il est possible d'arriver au résultat escompté en chaînant une série de if testant les différentes chaînes, mais une solution
élégante en C++ consiste à utiliser std::map défini dans l'en-tête standard <map>. Une map permet d'associer un élément
à une clé. Dans notre cas nous allons associer une fonction de traitement à une chaîne de caractères :

// fonctions de traitement
void parametre_input();
void parametre_output();
void parametre_inconnu();

// type pointeur de fonction de traitement


typedef void (*parametre_fct)();

void analyse_parametre( const string & Param )


{
static map<string, parametre_fct> param_map;

// initialiser la map si ce n'est pas fait


if ( param_map.empty() )
{
param_map[ "/input" ] = parametre_input;
param_map[ "/output" ] = parametre_output;
}

// rechercher la fonction associée à Param


map<string, parametre_fct>::const_iterator i = param_map.find( Param );
if ( i == param_map.end() )
{
// échec
parametre_inconnu();
}
else

- 192 -
Les sources présentées sur cette pages sont libres de droits, et vous pouvez les utiliser à votre convenance. Par contre la page de présentation de ces sources
constitue une oeuvre intellectuelle protégée par les droits d'auteurs. Copyright © 2008 Developpez LLC. Tout droits réservés Developpez LLC. Aucune
reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents et images sans l'autorisation expresse de Developpez
LLC. Sinon vous encourez selon la loi jusqu'à 3 ans de prison et jusqu'à 300 000 E de dommages et intérêts.
http://cpp.developpez.com/faq/cpp/
{
// appeler la fonction associée
(i->second)();
}
}

Comment effectuer les conversions de texte ASCII <-> Unicode ?


Auteurs : superspag ,
Les fonctions suivantes permettent, via l'utilisation de la bibliothèque standard, de transformer un texte Unicode en
ASCII (narrow), et vice-versa (widen).

#include <string>
#include <locale>
#include <vector>

std::string narrow(const std::wstring& ws)


{
std::vector<char> buffer(ws.size());
std::locale loc("english");
std::use_facet< std::ctype<wchar_t> >(loc).narrow(ws.data(), ws.data() +
ws.size(), '?', &buffer[0]);

return std::string(&buffer[0], buffer.size());


}

std::wstring widen(const std::string& s)


{
std::vector<wchar_t> buffer(s.size());
std::locale loc("english");
std::use_facet< std::ctype<wchar_t> >(loc).widen(s.data(), s.data() + s.size(), &buffer[0]);

return std::wstring(&buffer[0], buffer.size());


}

[Piège] Comment initialiser/affecter un nombre à une string ?


Auteurs : Aurélien Regat-Barrel ,
Le code suivant compile mais ne donne pas le résultat attendu :

string s = 0;

Ce code ne produit pas la chaîne "0". Il compile car le zéro est interprété comme un pointeur NULL. Certains
compilateurs permissifs acceptent même un nombre différent de zéro, ce qui peut avoir des conséquences désastreuses
(accès aléatoire à une zone mémoire).
Il n'est pas possible d'initialiser directement une string avec un nombre. Il faut convertir celui-ci en chaîne de caractères
puis utiliser ce résultat. La question Comment convertir un nombre en une string ? explique comment réaliser cela.

[Exemple] Comment supprimer des caractères d'une string ?


Auteurs : Laurent Gomila ,
#include <string>
#include <algorithm> // remove(), erase()

- 193 -
Les sources présentées sur cette pages sont libres de droits, et vous pouvez les utiliser à votre convenance. Par contre la page de présentation de ces sources
constitue une oeuvre intellectuelle protégée par les droits d'auteurs. Copyright © 2008 Developpez LLC. Tout droits réservés Developpez LLC. Aucune
reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents et images sans l'autorisation expresse de Developpez
LLC. Sinon vous encourez selon la loi jusqu'à 3 ans de prison et jusqu'à 300 000 E de dommages et intérêts.
http://cpp.developpez.com/faq/cpp/
#include <iostream>

// supprime toutes les occurrences du caractère C donné


void SupprimeTousLesCaracteres( std::string & Str, char C )
{
Str.erase(
std::remove( Str.begin(), Str.end(), C ),
Str.end() );
}

// supprime tous les caractères C situés au début de Str


std::string SupprimeLesPremiersCaractères( const std::string & Str, char C )
{
return Str.substr(
Str.find_first_not_of( C ) );
}

int main()
{
using namespace std;

// supprimer les quote entre les mots


string s1 = "'mot1' 'mot2' 'mot3' 'mot4'";
SupprimeTousLesCaracteres( s1, '\'' );
cout << s1 << '\n'; // affiche "mot1 mot2 mot3 mot4"

// enlever les espaces gênants au début de la chaîne


string s2 = " exemple";
cout << SupprimeLesPremiersCaractères( s2, ' ' ); // affiche "exemple"
}

Notez que le premier exemple (SupprimeTousLesCaracteres) est un cas typique d'utilisation de l'idiome erase-remove
qui consiste à combiner la fonction remove() avec la fonction erase() afin de supprimer les éléments répondant à un
certain critère (ici au fait d'être égal au caractère donné).

[Exemple] Comment manipuler un nom de fichier avec string ?


Auteurs : Aurélien Regat-Barrel ,
Le code suivant illustre des opérations courantes effectuées sur un nom de fichier :

#include <string>
#include <iostream>

int main()
{
using namespace std;

string file_name = "fichier.txt";

size_t ext_pos = file_name.find_last_of( '.' );


if ( ext_pos != string::npos )
{
// 3 manières d'extraire l'entension
string ext1 = file_name.substr( ext_pos );
string ext2( file_name, ext_pos );
string ext3;
ext3.assign( file_name, ext_pos, file_name.size() - ext_pos );

// 3 manières d'extraire le nom


string name1 = file_name.substr( 0, ext_pos );
string name2( file_name, 0, ext_pos );
string name3 = file_name;

- 194 -
Les sources présentées sur cette pages sont libres de droits, et vous pouvez les utiliser à votre convenance. Par contre la page de présentation de ces sources
constitue une oeuvre intellectuelle protégée par les droits d'auteurs. Copyright © 2008 Developpez LLC. Tout droits réservés Developpez LLC. Aucune
reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents et images sans l'autorisation expresse de Developpez
LLC. Sinon vous encourez selon la loi jusqu'à 3 ans de prison et jusqu'à 300 000 E de dommages et intérêts.
http://cpp.developpez.com/faq/cpp/
name3.resize( ext_pos );

// remplacer le nom
string file_name2 = file_name; // copie pour préserver l'original
file_name2.replace( 0, ext_pos, "remplace" );

// remplacer l'extension
string file_name3 = file_name; // copie pour préserver l'original
file_name3.replace( ext_pos + 1, file_name3.size() - 1, "dat" );

// insérer un mot entre le nom du fichier et son extension


string file_name4 = file_name; // copie pour préserver l'original
file_name4.insert( ext_pos, "-modif" );

// afficher les résultats


cout << ext1 << '\n'; // ".txt"
cout << ext2 << '\n'; // ".txt"
cout << ext3 << '\n'; // ".txt"
cout << name1 << '\n'; // "fichier"
cout << name2 << '\n'; // "fichier"
cout << name3 << '\n'; // "fichier"
cout << file_name2 << '\n'; // "remplace.txt"
cout << file_name3 << '\n'; // "fichier.dat"
cout << file_name4 << '\n'; // "fichier-modif.txt"
}
}

[Exemple] Comment manipuler un tableau de string ?


Auteurs : Aurélien Regat-Barrel ,
#include <vector>
#include <sstream>
#include <iostream>
#include <iterator>

int main()
{
using namespace std;

string str = "mot1 mot2 mot3 mot4 mot5 mot6";

vector<string> str_list; // liste de mots

// remplir la liste de mots


istringstream iss( str );
copy(
istream_iterator<string>( iss ),
istream_iterator<string>(),
back_inserter( str_list ) );

// afficher la liste de mots sur cout


copy(
str_list.begin(),
str_list.end(),
ostream_iterator<string>( cout, "\n" ) );

// reconstituer une string à partir de la liste de mots


ostringstream oss;
copy(
str_list.begin(),
str_list.end(),
ostream_iterator<string>( oss, " " ) );

string s = oss.str();

- 195 -
Les sources présentées sur cette pages sont libres de droits, et vous pouvez les utiliser à votre convenance. Par contre la page de présentation de ces sources
constitue une oeuvre intellectuelle protégée par les droits d'auteurs. Copyright © 2008 Developpez LLC. Tout droits réservés Developpez LLC. Aucune
reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents et images sans l'autorisation expresse de Developpez
LLC. Sinon vous encourez selon la loi jusqu'à 3 ans de prison et jusqu'à 300 000 E de dommages et intérêts.
http://cpp.developpez.com/faq/cpp/
cout << s << '\n'; // "mot1 mot2 mot3 mot4 mot5 mot6"
}

lien : Comment utiliser les itérateurs de flux ?

- 196 -
Les sources présentées sur cette pages sont libres de droits, et vous pouvez les utiliser à votre convenance. Par contre la page de présentation de ces sources
constitue une oeuvre intellectuelle protégée par les droits d'auteurs. Copyright © 2008 Developpez LLC. Tout droits réservés Developpez LLC. Aucune
reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents et images sans l'autorisation expresse de Developpez
LLC. Sinon vous encourez selon la loi jusqu'à 3 ans de prison et jusqu'à 300 000 E de dommages et intérêts.
http://cpp.developpez.com/faq/cpp/
Sommaire > Entrées / sorties avec les flux
A quoi sert std::endl ?
Auteurs : LFE , Aurélien Regat-Barrel ,
En plus de faire un retour à la ligne en ajoutant un caractère '\n', std::endl purge le buffer de sortie et force ainsi son
écriture en appelant ostream::flush() (cela a le même fonctionnement que la fonction fflush() du C).
Les deux lignes de code suivantes sont donc équivalentes :

std::cout << "coucou" << std::endl;


std::cout << "coucou\n" << std::flush;

Il faut donc être prudent avec son utilisation, notamment avec les fichiers, car une opération de flush n'est pas gratuite.
Son utilisation fréquente peut même sérieusement grever les performances en annulant tous les bénéfices d'une écriture
bufferisée.

Comment saisir une chaîne contenant des espaces ?


Auteurs : Laurent Gomila , Luc Hermitte ,
L'opérateur >> de saisie d'une string permet de saisir des mots et donc considère les espaces comme des séparateurs.
Pour saisir une chaîne entière contenant des espaces, il faut récupérer l'intégralité de la ligne au moyen de la fonction
libre std::getline() définie dans <string> (et non pas avec la fonction membre cin.getline() qui opère sur des char*).

#include <iostream>
#include <string>

std::string chaine;
std::getline( std::cin, chaine );

A noter que si une variable a été extraite à l'aide de l'opérateur >> avant l'appel à std::getline, le caractère de fin de
ligne peut subsister dans le flux d'entrée ; il peut donc être nécessaire de vider celui-ci avant d'extraire une nouvelle
ligne (voir Comment purger le buffer clavier ?).

Remarque : l'implémentation de la fonction getline de la bibliothèque standard fournie par Microsoft avec Visual C++
6.0 comporte un bug ; getline lit un caractère supplémentaire après la rencontre du délimiteur. Se référer au support
Microsoft pour la correction de ce bug.

Comment obtenir la représentation hexadécimale d'un entier ?


Auteurs : Laurent Gomila ,
Le principe est le même que celui de la question Comment convertir un nombre en une string ?, sauf que l'on précise
que l'on désire une représentation hexadécimale au moyen de std::hex.

#include <string>
#include <sstream>

int x = 127;
std::ostringstream oss;
oss << std::hex << x;

std::string Hex = oss.str();

- 197 -
Les sources présentées sur cette pages sont libres de droits, et vous pouvez les utiliser à votre convenance. Par contre la page de présentation de ces sources
constitue une oeuvre intellectuelle protégée par les droits d'auteurs. Copyright © 2008 Developpez LLC. Tout droits réservés Developpez LLC. Aucune
reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents et images sans l'autorisation expresse de Developpez
LLC. Sinon vous encourez selon la loi jusqu'à 3 ans de prison et jusqu'à 300 000 E de dommages et intérêts.
http://cpp.developpez.com/faq/cpp/
On peut de la même manière obtenir une représentation décimale (std::dec), ou octale (std::oct).

Comment obtenir un entier à partir de sa représentation hexadécimale ?


Auteurs : Laurent Gomila ,
Le principe est le même que celui de la question Comment convertir une string en un entier ?, sauf que l'on précise que
l'on manipule une représentation hexadécimale au moyen de std::hex.

#include <string>
#include <sstream>

std::string Hex = "7F";


int x;

std::istringstream iss( Hex );


iss >> std::hex >> x;

On peut de la même manière convertir une représentation décimale (std::dec), ou octale (std::oct).

Comment manipuler la représentation d'un nombre dans différentes bases ?


Auteurs : Laurent Gomila ,
Il existe dans la bibliothèque standard des manipulateurs de flux permettant de travailler dans l'une de ces trois bases :
• décimale (std::dec)
• hexadécimale (std::hex)
• octale (std::oct)

Pour voir un exemple d'utilisation de ces manipulateurs, lisez Comment obtenir la représentation hexadécimale d'un
entier ? et Comment obtenir un entier à partir de sa représentation hexadécimale ?.

Il n'existe aucun manipulateur pour la base 2 (binaire), mais on pourra y arriver au moyen de la classe std::bitset<>

#include <bitset>
#include <string>
#include <sstream>

int main()
{
// Conversion binaire -> décimal
std::bitset<8> b1( std::string( "01001011" ) );
unsigned long x = b1.to_ulong(); // x = 75

// Conversion décimal -> binaire


std::bitset<8> b2( 75 );

// Version 1 : un peu lourde à écrire


std::string s1 = b2.to_string<
char,
std::char_traits<char>,
std::allocator<char> >(); // s1 = "01001011"

// Version 2 : un peu plus sympa


std::ostringstream oss;
oss << b2;
std::string s2 = oss.str(); // s2 = "01001011"

- 198 -
Les sources présentées sur cette pages sont libres de droits, et vous pouvez les utiliser à votre convenance. Par contre la page de présentation de ces sources
constitue une oeuvre intellectuelle protégée par les droits d'auteurs. Copyright © 2008 Developpez LLC. Tout droits réservés Developpez LLC. Aucune
reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents et images sans l'autorisation expresse de Developpez
LLC. Sinon vous encourez selon la loi jusqu'à 3 ans de prison et jusqu'à 300 000 E de dommages et intérêts.
http://cpp.developpez.com/faq/cpp/
}

Comment formater l'affichage sur les flux ?


Auteurs : Laurent Gomila ,
Il est possible, comme cela se faisait avec printf, de formater les informations envoyées sur un flux en utilisant ce que
l'on appelle les manipulateurs de flux. Voici un petit topo (non exhaustif) des manipulateurs les plus utilisés :

Manipulateur Persistant Effet


Manipulateurs généraux
flush non Force le vidage du buffer du
flux et son affichage
endl non Envoie une fin de ligne ('\n')
ainsi qu'un flush
setw(n) non Spécifie que la prochaine
sortie s'effectuera sur n
caractères
setfill(c) oui Indique le caractère de
remplissage pour setw
left non Aligne la sortie à gauche lors
de l'utilisation de setw
right non Aligne la sortie à droite
lors de l'utilisation de setw
(comportement par défaut)
Formatage des nombres entiers
dec oui Injecte / extrait les nombres
sous forme décimale
oct oui Injecte / extrait les nombres
sous forme octale
hex oui Injecte / extrait les nombres
sous forme hexadécimale
uppercase oui Affiche les lettres de la
représentation hexadécimale
en majuscule
nouppercase oui Affiche les lettres de la
représentation hexadécimale
en minuscule (annule l'effet
d'uppercase)
Formatage des nombres flottants
setprecision(n) oui Spécifie le nombre de chiffres
après la virgule affichés pour
les nombres flottants non
entiers (6 par défaut)
fixed oui Affiche les nombres flottants
en notation décimale (opposé
de scientific)
scientific oui Affiche les nombres flottants
en notation scientifique
(opposé de fixed)
Formatage des booléens

- 199 -
Les sources présentées sur cette pages sont libres de droits, et vous pouvez les utiliser à votre convenance. Par contre la page de présentation de ces sources
constitue une oeuvre intellectuelle protégée par les droits d'auteurs. Copyright © 2008 Developpez LLC. Tout droits réservés Developpez LLC. Aucune
reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents et images sans l'autorisation expresse de Developpez
LLC. Sinon vous encourez selon la loi jusqu'à 3 ans de prison et jusqu'à 300 000 E de dommages et intérêts.
http://cpp.developpez.com/faq/cpp/
boolalpha oui Affiche les booléens sous
forme alphabétique ("true"
et "false" au lieu de "0" et
"1")
noboolalpha oui Affiche les booléens sous
forme de chiffre (annule
l'effet de boolalpha)

Exemple

#include <iomanip>
#include <iostream>

using namespace std;

// Un petit tableau

// En-têtes de colonnes
cout << setfill(' ')
<< setw(15) << left << "Colonne 1"
<< setw(10) << left << "Colonne 2"
<< endl;

// Ligne 1
cout << setprecision(2) << fixed
<< setw(15) << left << 158.82589
<< setw(10) << left << 456.10288
<< endl;

// Ligne 2
cout << hex << uppercase
<< setw(15) << left << 255
<< setw(10) << left << 128
<< endl;

// Ligne 3
cout << boolalpha
<< setw(15) << left << true
<< setw(10) << left << false
<< endl;

A noter qu'il existe dans boost une bibliothèque de formatage style printf, mais plus évoluée et tirant partie des avantages
offerts par le C++ (voir boost::format).

Comment utiliser les flux pour afficher ou saisir mes objets ?


Auteurs : Laurent Gomila ,
Pour injecter un objet de type quelconque dans un flux de sortie, il suffit de définir un opérateur << prenant en
paramètre le flux, ainsi que l'objet à injecter.

#include <ostream>
#include <string>

class MaClasse
{
friend std::ostream& operator <<(std::ostream&, const MaClasse&);

- 200 -
Les sources présentées sur cette pages sont libres de droits, et vous pouvez les utiliser à votre convenance. Par contre la page de présentation de ces sources
constitue une oeuvre intellectuelle protégée par les droits d'auteurs. Copyright © 2008 Developpez LLC. Tout droits réservés Developpez LLC. Aucune
reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents et images sans l'autorisation expresse de Developpez
LLC. Sinon vous encourez selon la loi jusqu'à 3 ans de prison et jusqu'à 300 000 E de dommages et intérêts.
http://cpp.developpez.com/faq/cpp/
private :

int Integer;
std::string String;
};

std::ostream& operator <<(std::ostream& Stream, const MaClasse& Obj)


{
Stream << Obj.Integer << " " << Obj.String;
return Stream; // N'oubliez pas de renvoyer le flux, afin de pouvoir chaîner les appels
}

MaClasse Obj;
std::cout << "Ma classe : " << Obj << std::endl;

Notez qu'ici notre surcharge d'opérateur est déclarée amie de la classe, car elle a besoin d'accéder aux données privées
de celle-ci. Cependant ce n'est pas obligatoire, notamment si les accesseurs adéquats ont été prévus dans la classe.

Le fonctionnement est le même pour l'extraction de valeurs à partir d'un flux d'entrée, via l'opérateur >> :

#include <istream>
#include <string>

class MaClasse
{
friend std::istream& operator >>(std::istream&, MaClasse&);

private :

int Integer;
std::string String;
};

std::istream& operator >>(std::istream& Stream, MaClasse& Obj)


{
Stream >> Obj.Integer >> Obj.String;
return Stream; // N'oubliez pas de renvoyer le flux, afin de pouvoir chaîner les appels
}

MaClasse Obj;
std::cin >> Obj;

Le fait de prendre en paramètre un ostream ou un istream permettra d'utiliser nos surcharges avec n'importe quel type
de flux (stringstream, fstream, ...) puisque ceux-ci dérivent tous des classes ostream (pour les flux d'entrée) et istream
(pour les flux de sortie).

Notez bien que ce genre de surcharge ne peut pas être membre de la classe, car celà impliquerait que l'opérande gauche
soit l'objet et non le flux.

Enfin, si vous devez gérer l'injection et l'extraction sur une hiérarchie d'objets polymorphes, il faudra mettre en place
une petite astuce (voir Comment surcharger correctement l'opérateur << pour afficher des objets polymorphes ?).

Comment surcharger correctement l'opérateur << pour afficher des objets polymorphes ?
Auteurs : Laurent Gomila ,
Habituellement, pour pouvoir envoyer un objet sur un flux on surcharge l'opérateur << entre un ostream et le type
de notre objet. Cependant, cette fonction ne peut être membre et de ce fait elle ne peut pas non plus être virtuelle.

- 201 -
Les sources présentées sur cette pages sont libres de droits, et vous pouvez les utiliser à votre convenance. Par contre la page de présentation de ces sources
constitue une oeuvre intellectuelle protégée par les droits d'auteurs. Copyright © 2008 Developpez LLC. Tout droits réservés Developpez LLC. Aucune
reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents et images sans l'autorisation expresse de Developpez
LLC. Sinon vous encourez selon la loi jusqu'à 3 ans de prison et jusqu'à 300 000 E de dommages et intérêts.
http://cpp.developpez.com/faq/cpp/
Par contre rien ne l'empêche d'appeler une fonction membre virtuelle de l'objet passé en paramètre. Ainsi, la manière
habituelle de surcharger l'opérateur << pour une hiérarchie de classes est la suivante :

#include <iostream>

class Base
{
friend std::ostream& operator << (std::ostream& O, const Base& B);

virtual void Print(std::ostream& O) const


{
O << "Je suis une base";
}
};

class Derivee : public Base


{
virtual void Print(std::ostream& O) const
{
O << "Je suis une derivee";
}
};

std::ostream& operator << (std::ostream& O, const Base& B)


{
B.Print(O);
return O;
}

Base* B1 = new Base,


Base* B2 = new Derivee;

std::cout << *B1 << std::endl; // "Je suis une base"


std::cout << *B2 << std::endl; // "Je suis une derivee"

Comment effacer le contenu d'un ostringstream ?


Auteurs : Laurent Gomila ,
La fonction membre ostringstream::str a deux surcharges : une sans paramètre qui renvoie le contenu du flux sous
forme de string, et une autre qui accepte une chaîne en paramètre pour réinitialiser le contenu du flux. Pour vider le
flux il suffit donc d'appeler cette fonction avec comme paramètre la chaîne vide.

#include <sstream>
#include <string>

std::ostringstream oss;
std::string Fichier[10];

// Génère une suite de noms de fichiers numérotés de 0 à 9


for (int i = 0; i < 10; ++i)
{
oss.str("");
oss << "Image_" << i << ".bmp";
Fichier[i] = oss.str();
}

- 202 -
Les sources présentées sur cette pages sont libres de droits, et vous pouvez les utiliser à votre convenance. Par contre la page de présentation de ces sources
constitue une oeuvre intellectuelle protégée par les droits d'auteurs. Copyright © 2008 Developpez LLC. Tout droits réservés Developpez LLC. Aucune
reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents et images sans l'autorisation expresse de Developpez
LLC. Sinon vous encourez selon la loi jusqu'à 3 ans de prison et jusqu'à 300 000 E de dommages et intérêts.
http://cpp.developpez.com/faq/cpp/
Note : on est souvent tenté d'utiliser la fonction clear, mais celle-ci n'a rien à voir : elle remet à zéro les bits d'erreur
du flux.

[Exemple] Comment convertir un tableau en chaîne ?


Auteurs : Laurent Gomila , Aurélien Regat-Barrel ,
De la même manière que dans Comment obtenir la représentation hexadécimale d'un entier ?, on peut obtenir la
représentation hexadécimale d'un buffer de caractères en représentation hexadécimale à l'aide du manipulateur
std::hex.

#include <algorithm>
#include <iomanip>
#include <sstream>
#include <string>

std::string array_to_string(const unsigned char* buffer, std::size_t size)


{
std::ostringstream oss;

// Afficher en hexadécimal et en majuscule


oss << std::hex << std::uppercase;

// Injecter tous les caractères sous forme d'entiers dans le flux


std::copy(buffer, buffer + size, std::ostream_iterator<int>(oss, ""));

return oss.str();
}

On utilise ici std::copy et std::ostream_iterator<int> pour envoyer les éléments du buffer un à un au flux, sous
forme d'entiers. Le flux étant en mode hexadécimal, ceux-ci seront chacun correctement convertis. Le manipulateur
std::uppercase sert quant à lui à obtenir une représentation des lettres hexadécimales en majuscule.

Si l'on souhaite personnaliser la représentation hexadécimale, on peut développer la boucle de copie comme ceci :

#include <algorithm>
#include <iomanip>
#include <sstream>
#include <string>

std::string array_to_string(const unsigned char* buffer, std::size_t size)


{
std::ostringstream oss;

// Afficher en hexadécimal et en majuscule


oss << std::hex << std::uppercase;

// Remplir les blancs avec des zéros


oss << std::setfill('0');

for (std::size_t i = 0; i < size; ++i)


{
// Séparer chaque octet par un espace
if (i != 0)
oss << ' ';

// Afficher sa valeur hexadécimale précédée de "0x"


// setw(2) permet de forcer l'affichage à 2 caractères
oss << "0x" << std::setw(2) << static_cast<int>(buffer[i]);

- 203 -
Les sources présentées sur cette pages sont libres de droits, et vous pouvez les utiliser à votre convenance. Par contre la page de présentation de ces sources
constitue une oeuvre intellectuelle protégée par les droits d'auteurs. Copyright © 2008 Developpez LLC. Tout droits réservés Developpez LLC. Aucune
reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents et images sans l'autorisation expresse de Developpez
LLC. Sinon vous encourez selon la loi jusqu'à 3 ans de prison et jusqu'à 300 000 E de dommages et intérêts.
http://cpp.developpez.com/faq/cpp/
}

return oss.str();
}

int main()
{
const std::size_t size = 5;
const unsigned char data[size] = {0x0C, 0x10, 0xFF, 0x00, 0xDA};
std::cout << array_to_string(data, size) << '\n';
}

Le code ci-dessus produit le résultat suivant :

0x0C 0x10 0xFF 0x00 0xDA

Comment rediriger l'entrée ou la sortie standard ?


Auteurs : Laurent Gomila ,
Afin de rediriger l'entrée ou l'une des sorties standard (cin, cout, cerr ou clog) vers un autre flux, il suffit de réaffecter
leur streambuf (le streambuf est l'objet qui fait le lien entre le flux et ce vers quoi il lit / écrit).

Afin d'accéder au streambuf d'un flux, il faut utiliser la fonction membre rdbuf (ses 2 surcharges pour être précis) :

// Récupérer le streambuf d'un flux


std::streambuf* std::ios::rdbuf();

// Changer le streambuf d'un flux


void std::ios::rdbuf(std::streambuf*);

Ainsi, pour rediriger cout vers un fichier par exemple, il suffira de procéder ainsi :

#include <fstream>
#include <iostream>

int main()
{
std::cout << "Affichage sur la console"<< std::endl;

// Redirection de std::cout vers le fichier Toto.txt


std::ofstream Out("Toto.txt");
std::streambuf* OldBuf = std::cout.rdbuf(Out.rdbuf());

std::cout << "Affichage dans Toto.txt"<< std::endl;

// Restauration du streambuf initial de std::cout (affichage sur la console)


std::cout.rdbuf(OldBuf);

return 0;
}

De cette manière vous n'êtes pas limité, cette méthode permet de rediriger n'importe quel flux vers n'importe quel autre.

- 204 -
Les sources présentées sur cette pages sont libres de droits, et vous pouvez les utiliser à votre convenance. Par contre la page de présentation de ces sources
constitue une oeuvre intellectuelle protégée par les droits d'auteurs. Copyright © 2008 Developpez LLC. Tout droits réservés Developpez LLC. Aucune
reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents et images sans l'autorisation expresse de Developpez
LLC. Sinon vous encourez selon la loi jusqu'à 3 ans de prison et jusqu'à 300 000 E de dommages et intérêts.
http://cpp.developpez.com/faq/cpp/
A noter également que si vous ne souhaitez effectuer une telle redirection que ponctuellement et pour toute la durée
de l'exécution, vous pouvez plus simplement utiliser une redirection au niveau de votre invite de commande (avec une
syntaxe dépendante de l'OS) :

>MonProgramme.exe > Toto.txt

Comment utiliser les itérateurs de flux ?


Auteurs : Laurent Gomila ,
La STL propose deux classes pour itérer sur les flux : istream_iterator et ostream_iterator (définies dans l'en-tête
<iterator>).

std::vector<int> Tab;
Tab.push_back(1);
Tab.push_back(3);
Tab.push_back(6);

// Plus besoin de boucle pour afficher les éléments du tableau :


std::copy(Tab.begin(), Tab.end(), std::ostream_iterator<int>(std::cout, "\n"));

Dans cet exemple, l'itérateur va parcourir les éléments du tableau un à un et utiliser l'opérateur << pour les injecter
dans le flux passé en paramètre, en l'occurrence ici std::cout.
Le second paramètre de l'itérateur est simplement le délimiteur qui sera placé entre chaque élément.

Vous pouvez également utiliser les stream_iterator avec des classes quelconques, pourvu que leur opérateur << soit
correctement défini.

Cela marche aussi dans l'autre sens : avec un std::istream_iterator vous pourrez extraire des valeurs / objets d'un flux
les unes après les autres, et les placer par exemple dans un tableau.

// On va extraire à partir d'un fichier, par exemple


std::ifstream File("Data.txt");

// Méthode 1 : std::copy
std::vector<MaClasse> Tab1;
std::copy(istream_iterator<MaClasse>(File), istream_iterator<MaClasse>(), std::back_inserter(Tab1));
// std::back_inserter va remplacer les affectations par des push_back

// Méthode 2 : utiliser le constructeur prenant une paire d'itérateurs


std::vector<MaClasse> Tab2(istream_iterator<MaClasse>(File), (istream_iterator<MaClasse>()));

Un istream_iterator construit par défaut (comme le second ici) représentera toujours la fin du flux.

La paire de parenthèses a priori inutile autour du second istream_iterator est nécessaire : sans cela le compilateur
croirait à une déclaration de fonction. Pour éviter cela vous pouvez également déclarer les variables correspondant aux
itérateurs en dehors de l'appel :

istream_iterator<MaClasse> Begin(File);
istream_iterator<MaClasse> End;
std::vector<MaClasse> Tab2(Begin, End);

- 205 -
Les sources présentées sur cette pages sont libres de droits, et vous pouvez les utiliser à votre convenance. Par contre la page de présentation de ces sources
constitue une oeuvre intellectuelle protégée par les droits d'auteurs. Copyright © 2008 Developpez LLC. Tout droits réservés Developpez LLC. Aucune
reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents et images sans l'autorisation expresse de Developpez
LLC. Sinon vous encourez selon la loi jusqu'à 3 ans de prison et jusqu'à 300 000 E de dommages et intérêts.
http://cpp.developpez.com/faq/cpp/
Pour les traitements sur des flux binaires (ie. ouvertes avec l'option binary), il vaudra mieux utiliser
std::istreambuf_iterator, qui n'appliquera pas de traitement spécial aux caractères spéciaux.

lien : [Exemple] Comment manipuler un tableau de string ?

- 206 -
Les sources présentées sur cette pages sont libres de droits, et vous pouvez les utiliser à votre convenance. Par contre la page de présentation de ces sources
constitue une oeuvre intellectuelle protégée par les droits d'auteurs. Copyright © 2008 Developpez LLC. Tout droits réservés Developpez LLC. Aucune
reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents et images sans l'autorisation expresse de Developpez
LLC. Sinon vous encourez selon la loi jusqu'à 3 ans de prison et jusqu'à 300 000 E de dommages et intérêts.
http://cpp.developpez.com/faq/cpp/
Sommaire > Entrées / sorties avec les flux > Manipulation de la console
Quelle est le différence entre #include <iostream.h> et #include <iostream> ?
Auteurs : Anomaly , Aurélien Regat-Barrel ,
Avant que le C++ ne soit normalisé, <iostream.h> était le seul fichier d'en-tête existant livré avec les compilateurs de
l'époque. La normalisation ISO du C++ en 1998 a défini que <iostream> serait l'en-tête standard pour les entrées-sorties.
L'absence de .h dans son nom indique qu'il s'agit désormais d'un en-tête standard, et donc que toutes ses définitions font
partie de l'espace de nommage standard std. Inclure <iostream.h> est donc obsolète depuis ce temps là (techniquement
parlant, <iostream.h> n'est pas obsolète car il n'a jamais fait partie du standard, mais son utilisation l'est).
Pour laisser le temps aux programmeurs de modifier leur code, les compilateurs ont fourni chacun de ces fichiers d'en-
tête. <iostream.h> n'est donc encore présent que dans un but de compatibilité.
Mais maintenant certains compilateurs comme Visual C++ 7.1 (2003) ne fournissent plus que l'en-tête standard
<iostream> et presque tous les autres émettent au moins un avertissement comme quoi utiliser <iostream.h> est obsolète.
En le faisant, la portabilité et la compatibilité future de votre code sont menacées.
Voilà pourquoi il faut remplacer toute inclusion de <iostream.h>

#include <iostream.h>

cout << "coucou" << endl;

par <iostream> et une utilisation du namespace std

#include <iostream>
using namespace std;

cout << "coucou" << endl;

ou

#include <iostream>

std::cout << "coucou" << std::endl;

ou encore

#include <iostream>
using std::cout;
using std::endl;

cout << "coucou" << endl;

Il est en de même avec tous les fichiers d'en-tête standards en C++, y compris avec ceux de la bibliothèque standard C.
Pour des raisons d'uniformisation, il faut désormais les inclure sans le .h et en préfixant leur nom par la lettre c (pour
souligner le fait qu'ils sont issus du C).
Par exemple

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

devient

#include <cstdlib>

- 207 -
Les sources présentées sur cette pages sont libres de droits, et vous pouvez les utiliser à votre convenance. Par contre la page de présentation de ces sources
constitue une oeuvre intellectuelle protégée par les droits d'auteurs. Copyright © 2008 Developpez LLC. Tout droits réservés Developpez LLC. Aucune
reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents et images sans l'autorisation expresse de Developpez
LLC. Sinon vous encourez selon la loi jusqu'à 3 ans de prison et jusqu'à 300 000 E de dommages et intérêts.
http://cpp.developpez.com/faq/cpp/
#include <cstdio>

cout n'est pas reconnu à la compilation, que se passe-t-il ?


Auteurs : LFE ,
cin, cout, ... sont des objets standards. Ils doivent être déclarés et utilisés de la façon suivante :

#include <iostream>

std::cout << "Test cout" << std::endl;

ou alors

#include <iostream>
using namespace std;

cout << "Test cout" << endl;

Comment purger le buffer clavier ?


Auteurs : LFE , Laurent Gomila ,
Pour supprimer une ligne saisie par l'utilisateur qui se trouve dans le buffer d'entrée, il faut utiliser ignore().

#include <iostream>
#include <limits>

std::cin.ignore( std::numeric_limits<std::streamsize>::max(), '\n' );

Le code précédent demande d'ignorer le maximum de caractères différents de '\n' possibles. Ce maximum possible est
obtenu grâce à numeric_limits.

Comment vérifier les valeurs saisies avec cin ?


Auteurs : Luc Hermitte , Aurélien Regat-Barrel ,
L'opérateur >> utilisé pour la saisie permet de vérifier la validité de celle-ci via le test suivant :

Exemple incorrecte de vérification de saisie


#include <iostream>

int main()
{
using namespace std;

cout << "Entrez un nombre : ";


int nombre;
while ( ! ( cin >> nombre ) )
{
cerr << "Erreur de saisie.\n";
}
cout << "Le nombre entré est " << nombre << ".\n";
}

- 208 -
Les sources présentées sur cette pages sont libres de droits, et vous pouvez les utiliser à votre convenance. Par contre la page de présentation de ces sources
constitue une oeuvre intellectuelle protégée par les droits d'auteurs. Copyright © 2008 Developpez LLC. Tout droits réservés Developpez LLC. Aucune
reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents et images sans l'autorisation expresse de Developpez
LLC. Sinon vous encourez selon la loi jusqu'à 3 ans de prison et jusqu'à 300 000 E de dommages et intérêts.
http://cpp.developpez.com/faq/cpp/
Pour savoir comment ce test fonctionne, lisez Comment fonctionne le test de réussite de conversion if ( str >> num ) ?.
Si vous testez cet exemple en entrant un mot au lieu d'un nombre le programme entrera dans une boucle infinie affichant
Erreur de saisie.. En effet, après une erreur de saisie, le flux d'entrée cin se retrouve dans un état invalide, et la chaîne
invalide qui a provoqué l'erreur est toujours dans le buffer puisque son extraction a échoué. Ainsi la tentative suivante
échoue à nouveau, ce qui provoque une boucle infinie dans l'exemple précédent.
Il faut donc supprimer la ligne invalide du buffer et restaurer l'objet cin dans un état valide. Ceci est fait grâce aux
deux lignes suivantes :

cin.clear(); // effacer les bits d'erreurs


cin.ignore( numeric_limits<streamsize>::max(), '\n' ); // supprimer la ligne erronée dans le buffer

Le code suivant corrige le précédent problème, et effectue différents tests en cas d'erreur afin d'identifier l'origine de
l'échec :

#include <iostream>
#include <limits>

using namespace std;

bool read_choice( int & N )


{
cout << "Entrez un chiffre entre 1 et 6 : " ;
while ( ! ( cin >> N ) || N < 1 || N > 6 )
{
if ( cin.eof() )
{
// ^D (^Z sous windows); Fin du flux d'entree !
return false;
}
else if ( cin.fail() )
{
cout << "Saisie incorrecte, recommencez : ";
cin.clear();
cin.ignore( numeric_limits<streamsize>::max(), '\n' );
}
else
{
cout << "Le chiffre n'est pas entre 1 et 6, recommencez : ";
}
}
return true; // succès
}

int main ()
{
int choix;
if ( read_choice( choix ) )
{
cout << "Vous avez choisi : " << choix << '\n';
}

return 0;
}

Le résultat produit est le suivant :

Entrez un chiffre entre 1 et 6 : abcdef


Saisie incorrecte, recommencez : -3
Le chiffre n'est pas entre 1 et 6, recommencez : 17
Le chiffre n'est pas entre 1 et 6, recommencez : 5

- 209 -
Les sources présentées sur cette pages sont libres de droits, et vous pouvez les utiliser à votre convenance. Par contre la page de présentation de ces sources
constitue une oeuvre intellectuelle protégée par les droits d'auteurs. Copyright © 2008 Developpez LLC. Tout droits réservés Developpez LLC. Aucune
reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents et images sans l'autorisation expresse de Developpez
LLC. Sinon vous encourez selon la loi jusqu'à 3 ans de prison et jusqu'à 300 000 E de dommages et intérêts.
http://cpp.developpez.com/faq/cpp/
Vous avez choisi : 5

lien : Message de James Kanze sur fr.comp.lang.c++ ayant inspiré cette solution

Comment faire une pause (attendre que l'utilisateur tape une touche) ?
Auteurs : Aurélien Regat-Barrel ,
Il n'y a pas de moyen en C++ standard pour attendre que l'utilisateur tape sur n'importe quelle touche. Ce dernier doit
en effet terminer sa saisie par un retour chariot (touche entrée). On peut donc faire une pause dans son programme en
invitant l'utilisateur à appuyer sur la touche entrée et en ignorant sa saisie en Comment purger le buffer clavier ?.

#include <iostream>
#include <limits>

using namespace std;

int main()
{
cout << "Appuyez sur entrée pour continuer...";
cin.ignore( numeric_limits<streamsize>::max(), '\n' );
}

Est-il possible de simuler une saisie clavier ?


Auteurs : LFE ,
La fonction putback du flux std::cin permet cela. Le caractère passé en paramètre de cette fonction est ajouté à la fin
du flux, comme si l'utilisateur l'avait tapé au clavier.

#include <iostream>

// simuler la saisie du mot 'Test';


std::cin.putback('T');
std::cin.putback('e');
std::cin.putback('s');
std::cin.putback('t');

- 210 -
Les sources présentées sur cette pages sont libres de droits, et vous pouvez les utiliser à votre convenance. Par contre la page de présentation de ces sources
constitue une oeuvre intellectuelle protégée par les droits d'auteurs. Copyright © 2008 Developpez LLC. Tout droits réservés Developpez LLC. Aucune
reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents et images sans l'autorisation expresse de Developpez
LLC. Sinon vous encourez selon la loi jusqu'à 3 ans de prison et jusqu'à 300 000 E de dommages et intérêts.
http://cpp.developpez.com/faq/cpp/
Sommaire > Entrées / sorties avec les flux > Manipulation des fichiers
Comment tester l'existence d'un fichier ?
Auteurs : HRS , Aurélien Regat-Barrel ,
Le C++ standard ne permet pas de tester de manière fiable qu'un fichier existe (celui-ci peut exister sans qu'il soit
possible de le lire). Une solution portable consiste à utiliser la fonction exists de la bibliothèque boost::filesystem. Il est
en revanche possible de tester si un fichier existe et est accessible en lecture ou non, ce qui est suffisant dans la plupart
des cas. Il est cependant important de noter qu'il est possible que le fichier soir créé / effacé entre le moment où l'on
teste son accès et le moment où l'on crée / accède au fichier.

#include <fstream>
#include <string>
#include <iostream>

bool is_readable( const std::string & file )


{
std::ifstream fichier( file.c_str() );
return !fichier.fail();
}

void Exemple()
{
using std::cout;
if ( is_readable( "fichier.txt" ) )
{
cout << "Fichier existant et lisible.\n";
}
else
{
cout << "Fichier inexistant ou non lisible.\n";
}
}

L'appel à fichier.fail() permet de tester si l'ouverture du flux s'est bien déroulée, ou autrement dit, si le fichier est
accessible en lecture.
On aurait également pu écrire return fichier, qui fait appel à la conversion implicite en void*, et qui n'est rien d'autre
qu'une syntaxe allegée pour fichier.fail(). Pour plus d'informations à ce sujet, consultez Comment fonctionnent les tests
d'ouverture de fichier if ( fichier ) et if ( !fichier ) ?.

Comment savoir si la lecture / écriture dans un fichier a réussi ?


Auteurs : Aurélien Regat-Barrel ,
En cas d'échec de lecture / écriture, des indicateurs d'erreurs du flux sont positionnés et il suffit alors de tester l'état
de ce dernier de la même manière que dans le cas du Comment vérifier les valeurs saisies avec cin ? ou du Comment
fonctionnent les tests d'ouverture de fichier if ( fichier ) et if ( !fichier ) ?.

void test_lecture()
{
ifstream file( "fichier.txt" );
if ( !file )
{
cerr << "Erreur d'ouverture\n";
return;
}

string line;
if ( ! ( file >> line ) )
{

- 211 -
Les sources présentées sur cette pages sont libres de droits, et vous pouvez les utiliser à votre convenance. Par contre la page de présentation de ces sources
constitue une oeuvre intellectuelle protégée par les droits d'auteurs. Copyright © 2008 Developpez LLC. Tout droits réservés Developpez LLC. Aucune
reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents et images sans l'autorisation expresse de Developpez
LLC. Sinon vous encourez selon la loi jusqu'à 3 ans de prison et jusqu'à 300 000 E de dommages et intérêts.
http://cpp.developpez.com/faq/cpp/
cerr << "Erreur de lecture\n";
return;
}
cout << "Ligne lue : " << line;
}

void test_ecriture()
{
ofstream file( "fichier.txt" );
if ( !file )
{
cerr << "Erreur de création\n";
return;
}

file << "Une ligne\n";


if ( ! file )
{
cerr << "Erreur d'écriture\n";
return;
}
cout << "L'écriture a réussi\n";
}

Comment écrire à la suite d'un fichier existant ?


Auteurs : Aurélien Regat-Barrel ,
La valeur par défaut du mode d'ouverture du fichier dans le constructeur de std::ofstream est ios_base::out combiné
à ios_base::trunc (pour truncate = tronquer). Ce dernier a pour conséquence d'écraser le contenu original du fichier
ouvert. Pour éviter cela, il suffit de spécifier un autre mode d'ouverture, par exemple ios_base::app (pour append =
ajouter à la suite):

#include <fstream>

void AjouterUneLigne()
{
// std::ios_base::out est automatiquement ajouté par le
// constructeur de std::ofstream
std::ofstream file( "fichier.txt", std::ios_base::app );
file << "Une ligne\n";
}

int main()
{
// création du fichier et écriture d'une ligne
AjouterUneLigne();
// ouverture du fichier existant et rajout d'une nouvelle ligne
AjouterUneLigne();
// "fichier.txt" contient 2 lignes
}

Comment détecter la fin de fichier lors de la lecture ?


Auteurs : Aurélien Regat-Barrel ,
Si une lecture échoue parce que la fin de fichier a été atteinte, alors les indicateurs d'erreur du flux sont positionnés et il
est possible de détecter la fin de fichier simplement en Comment savoir si la lecture / écriture dans un fichier a réussi ?.
Pour avoir plus de précisions sur l'origine de l'échec, il est possible d'utiliser istream::eof() pour savoir si l'erreur est
due à une fin de fichier, comme cela est fait dans la question Comment vérifier les valeurs saisies avec cin ?.

- 212 -
Les sources présentées sur cette pages sont libres de droits, et vous pouvez les utiliser à votre convenance. Par contre la page de présentation de ces sources
constitue une oeuvre intellectuelle protégée par les droits d'auteurs. Copyright © 2008 Developpez LLC. Tout droits réservés Developpez LLC. Aucune
reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents et images sans l'autorisation expresse de Developpez
LLC. Sinon vous encourez selon la loi jusqu'à 3 ans de prison et jusqu'à 300 000 E de dommages et intérêts.
http://cpp.developpez.com/faq/cpp/
Il convient cependant d'être prudent avec cette fonction car elle ne signale la fin de fichier qu'une fois qu'elle a été
atteinte. Ainsi le code suivant est erroné:

Exemple erroné de détection de fin de fichier


ifstream file( "fichier.txt" );

// compter le nombre de lignes


int count = 0;
while ( ! file.eof() )
{
string line;
file >> line;
++count;
}

Lorsque la dernière ligne sera lue, eof() renverra false si cette dernière ligne est terminée par un retour chariot car la
lecture se sera arrêtée d'elle même sur ce caractère de fin de ligne (comportement de getline()) au lieu d'être arrêtée
par la fin de fichier. Ainsi, l'exemple précédent va compter une ligne en trop dans un tel cas.
Voici maintenant le code corrigé :

ifstream file( "fichier.txt" );

// compter le nombre de lignes


int count = 0;
while ( true )
{
string line;
getline( file, line );
if ( file.eof() )
{
break;
}
++count;
}

Ce code fonctionne, mais une telle écriture est plus contraignante que le test direct de la réussite de lecture (qui de plus
ne se limite pas à l'erreur fin de fichier). Aussi on préfère ne pas utiliser eof() et simplement écrire :

ifstream file( "fichier.txt" );

// compter le nombre de lignes


int count = 0;

string line;
while ( getline( file, line ) )
{
++count;
}

Comment calculer la taille d'un fichier ?


Auteurs : dj.motte , Aurélien Regat-Barrel ,
Il n'y a pas de fonction faisant partie du standard C++ qui permette d'obtenir la taille d'un fichier (sans l'ouvrir). Il
est cependant possible et facile d'y arriver en ouvrant le fichier, en se positionnant à sa fin, et en récupérant la nouvelle
position courante.

long GetFileSize( std::ifstream & Fichier )


{

- 213 -
Les sources présentées sur cette pages sont libres de droits, et vous pouvez les utiliser à votre convenance. Par contre la page de présentation de ces sources
constitue une oeuvre intellectuelle protégée par les droits d'auteurs. Copyright © 2008 Developpez LLC. Tout droits réservés Developpez LLC. Aucune
reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents et images sans l'autorisation expresse de Developpez
LLC. Sinon vous encourez selon la loi jusqu'à 3 ans de prison et jusqu'à 300 000 E de dommages et intérêts.
http://cpp.developpez.com/faq/cpp/
// sauvegarder la position courante
long pos = Fichier.tellg();
// se placer en fin de fichier
Fichier.seekg( 0 , std::ios_base::end );
// récupérer la nouvelle position = la taille du fichier
long size = Fichier.tellg() ;
// restaurer la position initiale du fichier
Fichier.seekg( pos, std::ios_base::beg ) ;
return size ;
}

Comment fonctionnent les tests d'ouverture de fichier if ( fichier ) et if ( !fichier ) ?


Auteurs : Aurélien Regat-Barrel ,
Pour que l'expression suivante compile :

std::ifstream fichier( "fichier.txt" ); // ou n'importe quel objet dérivant de std::ios


if ( fichier ) // ce test échoue si le fichier n'est pas ouvert
{
// OK
}

le compilateur recherche un moyen de convertir l'objet std::ifstream en un booléen. Ce dernier ne définit pas
d'opérateur de conversion vers bool, mais en définit un pour void * :

operator void*( ) const;

Le but de cet opérateur est de permettre de caster un flux vers un pointeur afin justement d'indiquer un succès. Ce
dernier renvoie une valeur nulle si un des drapeaux d'erreur est positionné (failbit ou badbit). Cette valeur nulle est à
nouveau implicitement convertie en booléen par le compilateur. Sa valeur est false en cas de pointeur nul, true pour
toute autre valeur. Les opérations implicites effectuées par le compilateur sont donc équivalentes à écrire soi-même :

std::ifstream fichier( "fichier.txt" ); // ou n'importe quel objet dérivant de std::ios


if ( fichier.operator void*() != 0 ) // ce test échoue si le fichier n'est pas ouvert
{
// OK
}

L'expression

std::ifstream fichier( "fichier.txt" ); // ou n'importe quel objet dérivant de std::ios


if ( !fichier ) // ce test est vrai si l'ouverture échoue
{
// ERREUR
}

est elle un peu plus simple à comprendre. Il n'y a pas de conversion implicite, mais un simple appel à l'opérateur

bool operator!() const;

- 214 -
Les sources présentées sur cette pages sont libres de droits, et vous pouvez les utiliser à votre convenance. Par contre la page de présentation de ces sources
constitue une oeuvre intellectuelle protégée par les droits d'auteurs. Copyright © 2008 Developpez LLC. Tout droits réservés Developpez LLC. Aucune
reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents et images sans l'autorisation expresse de Developpez
LLC. Sinon vous encourez selon la loi jusqu'à 3 ans de prison et jusqu'à 300 000 E de dommages et intérêts.
http://cpp.developpez.com/faq/cpp/
qui renvoie directement un booléen qui vaut true si un des drapeaux d'erreur est positionné (failed state) et false si
l'objet est dans un état valide (good state).

Comment faire pour lire un fichier ligne par ligne ?


Auteurs : LFE , Aurélien Regat-Barrel ,
La classe std::istream dont hérite std::ifstream possède une fonction getline() mais cette dernière n'est pas pratique
et est à utiliser avec précaution car elle demande un pointeur sur un tableau de char (char *) à remplir. Aussi est-
il préférable d'utiliser std::getline (déclaré dans <string>) qui permet d'extraire une ligne d'un istream sous la forme
d'une string. Il n'est donc plus nécessaire de se préoccuper de la taille de celle-ci. Le petit programme ci-dessous lit un
fichier ligne par ligne et l'affiche à l'écran.

#include <string>
#include <fstream>
#include <iostream>

int main()
{
// le constructeur de ifstream permet d'ouvrir un fichier en lecture
std::ifstream fichier( "fichier.txt" );

if ( fichier ) // ce test échoue si le fichier n'est pas ouvert


{
std::string ligne; // variable contenant chaque ligne lue

// cette boucle s'arrête dès qu'une erreur de lecture survient


while ( std::getline( fichier, ligne ) )
{
// afficher la ligne à l'écran
std::cout << ligne << std::endl;
}
}
}

std::getline peut aussi accepter un troisième paramètre qui permet de spécifier le délimiteur à utiliser comme marqueur
de fin de ligne. Si ce dernier n'est pas spécifié, le caractère '\n' est utilisé.

Comment lire l'intégralité d'un fichier texte dans un buffer ?


Auteurs : ovh , Aurélien Regat-Barrel ,
Pour charger l'intégralité d'un fichier en mémoire, il est judicieux d'utiliser un std::stringstream comme buffer ce qui
permet de s'affranchir d'une allocation dynamique et de toute manipulation de pointeur.

#include <fstream>
#include <iostream>
#include <sstream>

int main()
{
// le constructeur de ifstream permet d'ouvrir un fichier en lecture
std::ifstream fichier( "fichier.txt" );

if ( fichier ) // ce test échoue si le fichier n'est pas ouvert


{
std::stringstream buffer; // variable contenant l'intégralité du fichier
// copier l'intégralité du fichier dans le buffer
buffer << fichier.rdbuf();

- 215 -
Les sources présentées sur cette pages sont libres de droits, et vous pouvez les utiliser à votre convenance. Par contre la page de présentation de ces sources
constitue une oeuvre intellectuelle protégée par les droits d'auteurs. Copyright © 2008 Developpez LLC. Tout droits réservés Developpez LLC. Aucune
reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents et images sans l'autorisation expresse de Developpez
LLC. Sinon vous encourez selon la loi jusqu'à 3 ans de prison et jusqu'à 300 000 E de dommages et intérêts.
http://cpp.developpez.com/faq/cpp/
// nous n'avons plus besoin du fichier !
fichier.close();
// manipulations du buffer...
std::cout << "Taille du buffer : " << buffer.str().size() << '\n';
}
}

La taille du buffer peut être différente de celle du fichier si le fichier n'a pas été ouvert en mode texte (en utilisant le
mode std::ios_base::binary). Il est aussi possible d'utiliser la fonction read de std::ifstream en ayant au préalable alloué
un buffer suffisamment grand. Pour connaître la taille à allouer, consulter la question Comment calculer la taille d'un
fichier ?.

Comment compter le nombre de lignes d'un fichier ?


Auteurs : Aurélien Regat-Barrel , Laurent Gomila ,
Voici deux exemples qui illustrent comment arriver à ce résultat :

// utiliser ignore
#include <fstream>
#include <limits>

int main()
{
std::ifstream file( "fichier.txt" );
if ( file )
{
int lines = 0;
while ( file.ignore( std::numeric_limits<int>::max(), '\n' ) )
{
++lines;
}
}
}

// utiliser std::count et std::istreambuf_iterator


#include <fstream>
#include <algorithm>

int main()
{
std::ifstream file( "fichier.txt" );

if ( file )
{
int lines = std::count(
std::istreambuf_iterator<char>( file ),
std::istreambuf_iterator<char>(),
'\n' );
}
}

Pourquoi n'ai-je pas le nombre de caractères attendus avec mon fichier ?


Auteurs : Aurélien Regat-Barrel ,
Sous Windows, vous pouvez être surpris de lire un peu moins de caractères dans un fichier texte, ou d'en avoir écris
plus que ce que vous avez demandé. Vous constatez cela en comparant la taille de votre flux (ou la taille théorique de ce
que vous avez écris) avec la taille réelle du fichier indiquée par Windows. Si votre programme n'est pas en cause, alors

- 216 -
Les sources présentées sur cette pages sont libres de droits, et vous pouvez les utiliser à votre convenance. Par contre la page de présentation de ces sources
constitue une oeuvre intellectuelle protégée par les droits d'auteurs. Copyright © 2008 Developpez LLC. Tout droits réservés Developpez LLC. Aucune
reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents et images sans l'autorisation expresse de Developpez
LLC. Sinon vous encourez selon la loi jusqu'à 3 ans de prison et jusqu'à 300 000 E de dommages et intérêts.
http://cpp.developpez.com/faq/cpp/
l'explication est simple : il s'agit du traitement particulier opéré par ofstream et ifstream lorsqu'ils travaillent sur des
fichiers ouverts en mode texte, ce qui est le cas par défaut.
Ceci n'a généralement pas d'influence, sauf sous DOS et Windows où un saut de ligne est matérialisé par la séquence des
2 caractères '\r' et '\n'. Pour assurer une meilleure portabilité du code, le langage C++ (ainsi que le langage C) considère
qu'une fin de ligne est identifiée par un unique caractère '\n'. Pour cette raison, en mode texte sous Windows, l'objet
ifstream va ignorer le caractère '\r' lorsqu'il rencontre une fin de ligne, et ofstream va insérer un '\r' avant chaque '\n'
qu'on lui a demandé d'écrire.
Ainsi, le fait que le fichier sur disque contiennent des caractère '\r' que vous n'avez pas lu / écris devrait expliquer votre
différence de taille constatée. Pour lire ces caractères / ne pas générer leur écriture, il faut travailler en mode binaire
en spécifiant le flag std::ios_base::binary.

#include <fstream>

using namespace std;

int main()
{
ofstream file_txt( "fichier_txt.txt" );
file_txt << "a\n" "b\n" "c\n";
ofstream file_bin( "fichier_bin.txt", ios_base::binary );
file_bin << "a\n" "b\n" "c\n";
}

Dans cet exemple, sous DOS/Windows, file_txt fait 9 octets, et file_bin en fait 6.

Comment effectuer des lectures / écritures binaires dans un fichier ?


Auteurs : Laurent Gomila ,
Il est possible d'effectuer une lecture ou une écriture en mode texte dans un flux fstream, via les opérateurs >> et <<.
Mais il est également possible d'effectuer des lectures / écritures binaires via les fonctions membres read et write, un
peu à la façon de fread et fwrite.

#include <fstream>
#include <iostream>

std::ofstream FileOut("Toto.txt", std::ios_base::binary);


int xout = 24;
FileOut.write(reinterpret_cast<const char*>(&xout), sizeof(int));
FileOut.close();

std::ifstream FileIn("Toto.txt", std::ios_base::binary);


int xin;
FileIn.read(reinterpret_cast<char*>(&xin), sizeof(int));
FileIn.close();

std::cout << xin << std::endl; // Affiche "24";

Pour faciliter l'utilisation de ces fonctions, et notamment être certain de ne pas se tromper sur le second paramètre (la
taille de la donnée à lire / écrire), on peut construire des fonctions templates :

template<typename T>
std::ostream& write(std::ostream& os, const T& value)
{
return os.write(reinterpret_cast<const char*>(&value), sizeof(T));
}

- 217 -
Les sources présentées sur cette pages sont libres de droits, et vous pouvez les utiliser à votre convenance. Par contre la page de présentation de ces sources
constitue une oeuvre intellectuelle protégée par les droits d'auteurs. Copyright © 2008 Developpez LLC. Tout droits réservés Developpez LLC. Aucune
reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents et images sans l'autorisation expresse de Developpez
LLC. Sinon vous encourez selon la loi jusqu'à 3 ans de prison et jusqu'à 300 000 E de dommages et intérêts.
http://cpp.developpez.com/faq/cpp/
template<typename T>
std::istream & read(std::istream& is, T& value)
{
return is.read(reinterpret_cast<char*>(&value), sizeof(T));
}

// Nos appels deviennent donc


write(FileOut, xout);
read(FileIn, xin);

Il ne faut pas oublier que la gestion de données binaires n'est pas portable : selon les plateformes on aura des différences
d'endianess, ou de taille des types primitifs. Pour lire / ecrire des données de manière portable, concentrez-vous sur le
mode texte si vous le pouvez.

Si vous devez sérialiser des données plus complexes (gestion des données dynamiques, des conteneurs, des objets
polymorphes, etc), il existe des mécanismes plus élaborés et plus efficaces, comme par exemple boost::serialization ou
CArchive dans les MFC.

- 218 -
Les sources présentées sur cette pages sont libres de droits, et vous pouvez les utiliser à votre convenance. Par contre la page de présentation de ces sources
constitue une oeuvre intellectuelle protégée par les droits d'auteurs. Copyright © 2008 Developpez LLC. Tout droits réservés Developpez LLC. Aucune
reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents et images sans l'autorisation expresse de Developpez
LLC. Sinon vous encourez selon la loi jusqu'à 3 ans de prison et jusqu'à 300 000 E de dommages et intérêts.
http://cpp.developpez.com/faq/cpp/
Sommaire > Les templates
Qu'est-ce qu'un template ?
Auteurs : Aurélien Regat-Barrel , JolyLoic ,
Les templates (modèles en français, ou encore patrons) sont la base de la généricité en C++. Il s'agit en fait de modèles
génériques de code qui permettent de créer automatiquement des fonctions (dans le cas de fonctions templates) ou des
classes (classes templates) à partir d'un ou plusieurs paramètres. Le fait de fournir un paramètre à un modèle générique
s'appelle la spécialisation. Elle aboutit en effet à la création d'un code spécialisé pour un type donné à partir d'un modèle
générique. Pour cette raison on surnomme aussi les templates des types paramétrés (parameterized types en anglais).
Ces modèles manipulent généralement un type abstrait qui est remplacé par un vrai type C++ au moment de la
spécialisation. Ce type abstrait est fourni sous forme de paramètre template qui peut être un type C++, une valeur
(entier, enum, pointeur, ...) ou même un autre template.
La spécialisation d'un template est transparente et invisible. Elle est effectuée lors de la compilation, de manière interne
au compilateur, en fonction des arguments donnés au template (il n'y a pas de code source généré quelque part).
Par exemple, vous pouvez réaliser une fonction template renvoyant le plus grand de deux objets de même type pour peu
que ce dernier possède un opérateur de comparaison operator > (la fonction standard std::max procède ainsi). Cette
fonction template va accepter en argument le type des objets à comparer, appelé type T dans l'exemple suivant :

// renvoie le plus grand entre A et B


template<typename T>
const T & Max( const T & A, const T & B )
{
return A > B ? A : B;
}

Si vous appelez cette fonction en fournissant deux int, le compilateur va spécialiser la fonction Max pour le type int,
ce qui reviendrait à avoir écrit :

const int & Max( const int & A, const int & B )
{
return A > B ? A : B;
}

Si vous faites de même avec deux float cette fois-ci, une nouvelle spécialisation de la fonction pour le type float va être
générée.

const float & Max( const float & A, const float & B )
{
return A > B ? A : B;
}

Tout se passe comme si vous aviez écrit deux fois la même fonction, une fois pour le type int et une fois pour le type
float. Mais vous n'avez bien qu'une seule fonction template Max, qui opère sur un type abstrait déclaré au moyen du
mot-clé typename.
Les templates permettent donc de réutiliser facilement du code source, sans devoir utiliser le préprocesseur, ce qui le
rend plus lisible et plus rigoureux notamment envers les types manipulés.
Notez qu'il est possible de créer des fonctions membres templates. L'exemple suivant crée une classe permettant de
construire une chaîne de caractères au moyen de sa fonction membre template Append.

#include <iostream>
#include <sstream>

class StringBuilder
{
public:
template<typename T>

- 219 -
Les sources présentées sur cette pages sont libres de droits, et vous pouvez les utiliser à votre convenance. Par contre la page de présentation de ces sources
constitue une oeuvre intellectuelle protégée par les droits d'auteurs. Copyright © 2008 Developpez LLC. Tout droits réservés Developpez LLC. Aucune
reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents et images sans l'autorisation expresse de Developpez
LLC. Sinon vous encourez selon la loi jusqu'à 3 ans de prison et jusqu'à 300 000 E de dommages et intérêts.
http://cpp.developpez.com/faq/cpp/
void Append( const T & t )
{
this->oss << t;
}

std::string GetString() const


{
return this->oss.str();
}

private:
std::ostringstream oss;
};

int main()
{
StringBuilder sb;
sb.Append( 10 );
sb.Append( '\t' );
sb.Append( "coucou " );
sb.Append( 54.12 );
std::cout << sb.GetString() << '\n';
}

Pour que le code précédent compile sans la fonction membre template, il aurait fallu écrire 4 fonctions membres pour
les 4 types utilisés : int, char, const char * et double. Ces 4 fonctions utiliseraient strictement le même code. Grâce à
l'utilisation des templates, le compilateur a fait ce travail pour nous. Bien que l'on ait écrit une seule fonction nommée
Append, celle-ci n'existe pas en réalité dans le code compilé, mais le compilateur en a généré (spécialisé) quatre. Il en
aurait spécialisé dix si dix types différents avaient été utilisés.

Comment créer une fonction template ?


Auteurs : Aurélien Regat-Barrel ,
Prenons comme exemple la fonction suivante qui renvoie le plus grand des deux entiers qui lui sont donnés :

int Max( int A, int B )


{
return ( A >= B ) ? ( A ) : ( B );
}

Cet exemple est un cas typique de fonction qu'il est intéressant de rendre générique au moyen des templates. Pour cela,
il faut s'affranchir du type int que l'on va remplacer par un type abstrait nommé T grâce aux mots clés template et
typename :

template<typename T>
T Max( T A, T B )
{
return ( A >= B ) ? ( A ) : ( B );
}

Notez qu'on aurait pu utiliser des références constantes comme cela est fait dans la fonction standard std::max, mais
il s'agit ici d'un exemple.
Le mot clé template indique que la fonction qui suit est une fonction template, et typename dans ce contexte sert à
déclarer un nouveau type paramétré pour notre nouvelle fonction template. Il est aussi possible d'utiliser le mot-clé
class à la place de typename pour la déclaration des paramètres du template.
Nous venons de créer une fonction template Max possédant un seul type paramétré nommé T. Lorsque nous créons une
instance de cette fonction Max de cette manière :

- 220 -
Les sources présentées sur cette pages sont libres de droits, et vous pouvez les utiliser à votre convenance. Par contre la page de présentation de ces sources
constitue une oeuvre intellectuelle protégée par les droits d'auteurs. Copyright © 2008 Developpez LLC. Tout droits réservés Developpez LLC. Aucune
reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents et images sans l'autorisation expresse de Developpez
LLC. Sinon vous encourez selon la loi jusqu'à 3 ans de prison et jusqu'à 300 000 E de dommages et intérêts.
http://cpp.developpez.com/faq/cpp/
Max<int>( 1, 2 );

Nous demandons au compilateur de spécialiser la fonction Max pour le type int. Ce dernier va en quelque sorte
remplacer toutes les occurrences de T par int. Il va d'ailleurs à cette occasion vérifier la validité de l'utilisation de ce
type dans le contexte de cette fonction. Avec int pas de problèmes, mais prenons l'exemple suivant :

class Test
{
};

Test a;
Test b;

Max<Test>( a, b );

La classe Test ne possédant pas d'opérateur de comparaison operator >=, la compilation va échouer sur l'utilisation de
ce dernier. Les compilateurs récents émettent un message d'erreur assez explicite :

In function `T Max(T, T) [with T = Test]': no match for 'operator>=' in 'A >= B'

ou encore

error C2676: '>=' : 'Test' binaire ne définit pas cet opérateur ou une conversion vers un type acceptable pour l'opérateur
prédéfini

Sachez enfin qu'il n'est pas toujours nécessaire de préciser le type du paramètre pour notre template, et que celui-ci peut
être déterminé automatiquement par le compilateur (voir Qu'est-ce que la détermination automatique des paramètres
templates ?).

Comment créer une classe template ?


Auteurs : Laurent Gomila ,
L'écriture de classes templates pose souvent des problèmes de syntaxe ou de conception, voici un exemple illustrant
leur écriture :

#include <iostream>

template <typename T>


class Exemple
{
public :

Exemple(const T& Val = T());

template <typename U>


Exemple(const Exemple<U>& Copy);

const T& Get() const;

template <typename T2>


friend std::ostream& operator <<(std::ostream& Stream, const Exemple<T2>& Ex);

private :

T Value;
};

- 221 -
Les sources présentées sur cette pages sont libres de droits, et vous pouvez les utiliser à votre convenance. Par contre la page de présentation de ces sources
constitue une oeuvre intellectuelle protégée par les droits d'auteurs. Copyright © 2008 Developpez LLC. Tout droits réservés Developpez LLC. Aucune
reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents et images sans l'autorisation expresse de Developpez
LLC. Sinon vous encourez selon la loi jusqu'à 3 ans de prison et jusqu'à 300 000 E de dommages et intérêts.
http://cpp.developpez.com/faq/cpp/
template <typename T>
Exemple<T>::Exemple(const T& Val) :
Value(Val)
{

template <typename T>


template <typename U>
Exemple<T>::Exemple(const Exemple<U>& Copy) :
Value(static_cast<T>(Copy.Get()))
{
// Attention, ceci n'est pas le constructeur par copie !
}

template <typename T>


const T& Exemple<T>::Get() const
{
return Value;
}

template <typename T>


std::ostream& operator <<(std::ostream& Stream, const Exemple<T>& Ex)
{
return Stream << Ex.Value;
}

int main()
{
Exemple<int> A(3);
Exemple<float> B(A);

std::cout << A << std::endl;


std::cout << B << std::endl;

return 0;
}

Qu'est-ce que la spécialisation de template ?


Auteurs : Laurent Gomila ,
Une fonction ou une classe template peut être spécialisée pour certains types de paramètres, c'est ce qu'on appelle la
spécialisation. Cela permet entre autre d'avoir un comportement spécifique à certains types de paramètres, à des fins
d'optimisation ou pour s'adapter à un comportement particulier par exemple. Lors de l'utilisation d'un template avec
un type donné, le compilateur recherche s'il existe une spécialisation du template pour ce type. S'il en trouve une il
utilise cette version spécialisée, sinon il se rabat sur la version générique de base du template. On peut spécialiser une
fonction, une fonction membre template de classe, ou une classe toute entière.
Voici la syntaxe à utiliser pour effectuer une spécialisation (attention l'ordre est important : la version générique doit
apparaître en premier) :

// Version générique
template <typename T>
void QuiSuisJe( const T & x )
{
std::cout << "Je ne sais pas" << std::endl;
}

// Spécialisation pour les int


template <>
void QuiSuisJe<int>( const int & x )
{
std::cout << "Je suis un int" << std::endl;

- 222 -
Les sources présentées sur cette pages sont libres de droits, et vous pouvez les utiliser à votre convenance. Par contre la page de présentation de ces sources
constitue une oeuvre intellectuelle protégée par les droits d'auteurs. Copyright © 2008 Developpez LLC. Tout droits réservés Developpez LLC. Aucune
reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents et images sans l'autorisation expresse de Developpez
LLC. Sinon vous encourez selon la loi jusqu'à 3 ans de prison et jusqu'à 300 000 E de dommages et intérêts.
http://cpp.developpez.com/faq/cpp/
}

// Spécialisation pour ma classe


template <>
void QuiSuisJe<MaClasse>( const MaClasse & x )
{
std::cout << "Je suis un MaClasse" << std::endl;
}

// Test
MaClasse Test1;
int Test2;
float Test3;

QuiSuisJe( Test1 ); // "Je suis un MaClasse"


QuiSuisJe( Test2 ); // "Je suis un int"
QuiSuisJe( Test3 ); // "Je ne sais pas"

La spécialisation de classe est elle plus contraignante car il faut redéfinir la totalité de celle-ci.

template<typename T>
struct Modele
{
void QuiSuisJe()
{
std::cout << "Je suis un Modele<inconnu>" << std::endl;
}
};

template<>
struct Modele<int>
{
void QuiSuisJe()
{
std::cout << "Je suis un Modele<int>" << std::endl;
}

void CestQuoiCetteFonction()
{
// On peut tout à fait ajouter des fonctions
// en fait le contenu de la classe peut être totalement différent !
}
};

Modele<float> M1;
Modele<int> M2;

M1.QuiSuisJe(); // "Je suis un Modele<inconnu>"


M2.QuiSuisJe(); // "Je suis un Modele<int>"

M1.CestQuoiCetteFonction(); // Erreur : 'fonction inconnnue'


M2.CestQuoiCetteFonction(); // OK

Attention, un template ne peut être spécialisé qu'à l'intérieur d'un namespace, et pas dans une classe.

Qu'est-ce que la détermination automatique des paramètres templates ?


Auteurs : Laurent Gomila ,
Lorsque vous appelez une fonction template, vous n'avez pas toujours besoin d'indiquer explicitement le type de vos
paramètres templates : le compilateur est souvent capable de le faire pour vous.

- 223 -
Les sources présentées sur cette pages sont libres de droits, et vous pouvez les utiliser à votre convenance. Par contre la page de présentation de ces sources
constitue une oeuvre intellectuelle protégée par les droits d'auteurs. Copyright © 2008 Developpez LLC. Tout droits réservés Developpez LLC. Aucune
reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents et images sans l'autorisation expresse de Developpez
LLC. Sinon vous encourez selon la loi jusqu'à 3 ans de prison et jusqu'à 300 000 E de dommages et intérêts.
http://cpp.developpez.com/faq/cpp/
template <typename T>
void Fonction( T x )
{
}

Fonction<double>(5.2f);
// Equivalent à
Fonction( 5.2 );

Ceci n'est pas toujours possible, il existe certaines situations où l'on est obligé de spécifier explicitement le type des
paramètres manipulés (lorsque le compilateur ne peut les déduire ou bien pour lever une ambiguïté par exemple).

template <typename T>


T Fonction()
{
return T(); // renvoie le type
}

int x = Fonction(); // Erreur : 'impossible de déduire l'argument de modèle'


int x = Fonction<int>(); // OK

template <typename T>


void Fonction( T x1, T x2 )
{
}

int x1 = 5; // premier argument de type int


double x2 = 6.5; // second argument de type double

Fonction( x1, x2 ); // Erreur : 'paramètre ambigu'


Fonction<double>( x1, x2 ); // OK
Fonction( static_cast<double>( x1 ), x2 ); // OK

La détermination automatique des paramètres ne peut s'appliquer que sur des fonctions templates. Pour les classes
templates il faut systématiquement les expliciter.

Pourquoi mes templates ne sont-ils pas reconnus à l'édition des liens ?


Auteurs : Aurélien Regat-Barrel ,
Le standard C++ permet de séparer la déclaration d'une classe / fonction template de son implémentation au moyen
du mot-clé export. En théorie, il est donc possible de déclarer sa classe / fonction template dans un fichier .h, et de
l'implémenter dans un .cpp, comme on le fait traditionnelement avec les fonctions / classes non template.

Mais en pratique, c'est une fonctionnalité que seuls (à ce jour) quelques compilateurs basés sur le front-end d'EDG
implémentent (Comeau, Intel...). Qui plus est, il s'agit d'une fonctionnalité du langage qui a été controversée à un
moment ce qui explique le délai de mise en place dans certains compilateurs.
On peut donc considérer que même lorsque c'est possible, il n'est pas encore raisonnable de séparer l'implémentation
d'un template de sa déclaration dans l'état actuel des choses. Autrement dit, tout son code doit figurer dans le .h.

On peut cependant conserver la logique de la séparation interface/implémentation en la simulant de cette manière :

// exemple.h

#ifndef EXEMPLE_H
#define EXEMPLE_H

template <typename T>

- 224 -
Les sources présentées sur cette pages sont libres de droits, et vous pouvez les utiliser à votre convenance. Par contre la page de présentation de ces sources
constitue une oeuvre intellectuelle protégée par les droits d'auteurs. Copyright © 2008 Developpez LLC. Tout droits réservés Developpez LLC. Aucune
reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents et images sans l'autorisation expresse de Developpez
LLC. Sinon vous encourez selon la loi jusqu'à 3 ans de prison et jusqu'à 300 000 E de dommages et intérêts.
http://cpp.developpez.com/faq/cpp/
class Exemple
{
public:
Exemple();
};

#include "exemple.tpp" // <-- astuce ici !!!


#endif

// exemple.tpp

template <typename T>


Exemple<T>::Exemple()
{
}

L'astuce consiste à inclure à la fin du .h le fichier contenant le corps du template.

Notez l'utilisation de l'extension .tpp au lieu du classique .cpp, afin de faire la distinction avec les fichiers cpp classiques
(pouvant être compilés, contrairement au code template qui doit d'abord être spécialisé avant de pouvoir être compilé).
Il n'y a pas vraiment de convention, on trouve de nombreuses autres extensions : .htt, .tcc, .tpl, ... Libre à vous de choisir
celle que vous préférez.

A quoi sert le mot-clé typename ?


Auteurs : Laurent Gomila , Aurélien Regat-Barrel , Luc Hermitte ,
En plus de l'utilisation qu'on lui connaît pour définir un type en tant que paramètre template, ou il est possible aussi
d'utiliser class :

template <typename T>


class MaClasse
{
...
};

Le mot-clé typename possède une seconde utilité : il sert à indiquer au compilateur qu'un identifiant est un type,
dans certains contextes manipulant des templates pour lesquels il ne peut pas le deviner automatiquement. (Nous
utiliserons class ici pour introduire les parametres templates type pour éviter la confusion avec la première utilisation,
naturellement typename est aussi possible)

Prenez cet exemple incorrect :

template <typename T>


class MaClasse
{
public :
typedef int MonType;
};

template <typename T>


void MaFonction(T x)
{
MaClasse<T>::MonType t;
...
}

- 225 -
Les sources présentées sur cette pages sont libres de droits, et vous pouvez les utiliser à votre convenance. Par contre la page de présentation de ces sources
constitue une oeuvre intellectuelle protégée par les droits d'auteurs. Copyright © 2008 Developpez LLC. Tout droits réservés Developpez LLC. Aucune
reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents et images sans l'autorisation expresse de Developpez
LLC. Sinon vous encourez selon la loi jusqu'à 3 ans de prison et jusqu'à 300 000 E de dommages et intérêts.
http://cpp.developpez.com/faq/cpp/
Dans ce cas vous savez que T::MonType est bien un type, mais le compilo lui ne peut pas le déduire. La raison en est la
suivante : imaginez que l'on spécialise MaClasse (voir Q/R spécialisation) et que l'on définisse MonType autrement :

template <>
class MaClasse<int>
{
public :
static const int MonType = 5;
};

template <typename T>


void MaFonction(T x)
{
MaClasse<T>::MonType t; // Que se passe-t-il ici si T est int ??
...
}

Bien que l'exemple ci-dessus compile sur certains compilateurs sans avoir recours au mot-clé typename, le standard
exige sa présence, et les compilateurs modernes vont dans ce sens. Il convient donc de l'utiliser même si votre compilateur
sait s'en passer.
La syntaxe correcte est donc :

template <typename T>


void MaFonction(T x)
{
typename MaClasse<T>::MonType t;
...
}

Ce genre d'erreur peut arriver plus souvent que vous ne le pensez, par exemple si vous manipulez des conteneurs
standards dans une classe template :

template <typename T> // à priori, on ne sait rien du type T


class MaClasse
{
public :
typedef std::list<T>::iterator Iter;
// Erreur avec certains compilateurs (ou warning, ou rien)
typedef typename std::list<T>::iterator Iter2; // Ok
};

Peut-on créer un alias (typedef) sur des templates ?


Auteurs : Aurélien Regat-Barrel ,
Malheureusement non, dans le standard C++ actuel on ne peut pas écrire quelque chose comme cela :

#include <vector>

typedef std::vector Tableau; // erreur de compilation

Tableau<int> t;

Une solution qui peut parfois convenir consiste à utiliser une struct template :

#include <vector>

- 226 -
Les sources présentées sur cette pages sont libres de droits, et vous pouvez les utiliser à votre convenance. Par contre la page de présentation de ces sources
constitue une oeuvre intellectuelle protégée par les droits d'auteurs. Copyright © 2008 Developpez LLC. Tout droits réservés Developpez LLC. Aucune
reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents et images sans l'autorisation expresse de Developpez
LLC. Sinon vous encourez selon la loi jusqu'à 3 ans de prison et jusqu'à 300 000 E de dommages et intérêts.
http://cpp.developpez.com/faq/cpp/
template <typename T>
struct Tableau
{
typedef std::vector<T> type;
};

Tableau<int>::type t;

lien : Merci aux contributeurs du newsgroup fr.comp.lang.c++

Qu'est-ce qu'une classe de trait ? Comment l'utiliser ?


Auteurs : Laurent Gomila ,
Une classe de trait (trait class), généralement template, définit des caractéristiques ou des fonctions associées à un type
donné. Cela permet donc d'ajouter de l'information à des types que l'on ne peut pas modifier.

Une classe de trait n'est généralement pas destinée à être instanciée, ses membres étant typiquement statiques.

Le template std::numeric_limits<T> de la STL est une classe de traits : elle permet d'ajouter aux types de base des
informations telles que les valeurs min / max, l'epsilon, etc.

Voici un exemple d'une classe de traits qui fournit une valeur nulle appropriée pour chaque type :

template <typename T> struct ValeurNulle;

typename <> struct ValeurNulle<int> {static int Zero() {return 0;}};


typename <> struct ValeurNulle<std::string> {static std::string Zero() {return "";}};
typename <> struct ValeurNulle<MaClasse> {static MaClasse Zero() {return MaClasse(-1);}};
// ...

template <typename T>


void Fonction(T Valeur)
{
T Ret = ValeurNulle<T>::Zero();
// ...
}

lien : Qu'est-ce qu'une classe de politique ? Comment l'utiliser ?


lien : Character Types and Character Traits

Qu'est-ce qu'une classe de politique ? Comment l'utiliser ?


Auteurs : Laurent Gomila ,
Les classes de politique (policy classes) sont assez similaires aux Qu'est-ce qu'une classe de trait ? Comment l'utiliser ?,
mais contrairement à celles-ci qui ajoutent des informations à des types, les classes de politiques servent à définir des
comportements.

"Les classes de politique ont beaucoup en commun avec les traits, mais en diffèrent du fait
qu'elles mettent moins l'accent sur les types et plus sur les comportements".

Andrei Alexandrescu, Modern C++ Design

Voici par exemple une fonction qui accumule des éléments et en renvoie la somme, à la manière de std::accumulate :

- 227 -
Les sources présentées sur cette pages sont libres de droits, et vous pouvez les utiliser à votre convenance. Par contre la page de présentation de ces sources
constitue une oeuvre intellectuelle protégée par les droits d'auteurs. Copyright © 2008 Developpez LLC. Tout droits réservés Developpez LLC. Aucune
reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents et images sans l'autorisation expresse de Developpez
LLC. Sinon vous encourez selon la loi jusqu'à 3 ans de prison et jusqu'à 300 000 E de dommages et intérêts.
http://cpp.developpez.com/faq/cpp/
template <typename T>
T Accumulation(const T* Debut, const T* Fin)
{
T Resultat = 0;
for ( ; Debut != Fin; ++Debut)
Resultat += *Debut;

return Resultat;
}

Ici l'accumulation sera quoiqu'il arrive une somme. En utilisant une classe de politique pour personnaliser l'opération
effectuée, nous pouvons rendre cette fonction beaucoup plus générique :

template <typename T>


struct Addition
{
static void Accumuler(T& Resultat, const T& Valeur)
{
Resultat += Valeur;
}
};

template <typename T, typename Operation>


T Accumulation(const T* Debut, const T* Fin)
{
T Resultat = 0;
for ( ; Debut < Fin; ++Debut)
Operation::Accumuler(Resultat, *Debut);

return Resultat;
}

On voit ici une propriété typique des classes de politique : Addition est "orthogonale" aux autres paramètres templates
de la fonction, c'est-à-dire ici qu'elle ne dépend pas du type T qu'elle manipule. Celui-ci peut être int tout comme
std::string, notre classe de politique n'y verra aucune différence.

Pour modifier le comportement de la fonction Accumulation pour par exemple multiplier les éléments, il suffirait
d'écrire une classe politique Multiplication qui remplacerait += par *=, et la passer en paramètre à Accumulation.
On pourrait également imaginer utiliser Accumulation pour extraire un minimum, ou pour faire encore beaucoup
d'autres choses.

Une fonction qui prend en paramètre une classe de politique aura généralement une valeur par défaut assez évidente
(par exemple ici la politique Addition). Cependant, les fonctions n'acceptant pas les paramètres templates par défaut
(cela sera certainement corrigé dans une future norme du langage), il faudra remplacer votre fonction non membre
par une fonction statique encapsulée dans une classe. Bien sûr ensuite rien ne vous empêche de fournir des fonctions
qui encapsulent l'appel à cette fonction membre.

template <typename T, typename Operation = Addition<T> >


struct Accumulation
{
static T Accumule(const T* Debut, const T* Fin)
{
T Resultat = 0;
for ( ; Debut < Fin; ++Debut)
Operation::Accumuler(Resultat, *Debut);

return Resultat;
}

- 228 -
Les sources présentées sur cette pages sont libres de droits, et vous pouvez les utiliser à votre convenance. Par contre la page de présentation de ces sources
constitue une oeuvre intellectuelle protégée par les droits d'auteurs. Copyright © 2008 Developpez LLC. Tout droits réservés Developpez LLC. Aucune
reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents et images sans l'autorisation expresse de Developpez
LLC. Sinon vous encourez selon la loi jusqu'à 3 ans de prison et jusqu'à 300 000 E de dommages et intérêts.
http://cpp.developpez.com/faq/cpp/
};

template <typename T>


T Accumule(const T* Debut, const T* Fin)
{
return Accumulation<T>::Accumule(Debut, Fin);
}

template <typename T, typename Operation>


T Accumule(const T* Debut, const T* Fin)
{
return Accumulation<T, Operation>::Accumule(Debut, Fin);
}

Enfin, pour faire le lien entre politiques et traits, on peut remarquer que notre fonction d'accumulation possède quelques
défauts. Par exemple, la valeur zéro du type T ne sera pas forcément 0 (ce sera par exemple "" pour les std::string).
Ainsi nous pouvons utiliser la classe de traits définie dans Qu'est-ce qu'une classe de trait ? Comment l'utiliser ? pour
l'améliorer :

template <typename T, typename Operation = Addition<T> >


struct Accumulation
{
static T Accumule(const T* Debut, const T* Fin)
{
T Resultat = ValeurNulle<T>::Zero();
for ( ; Debut < Fin; ++Debut)
Operation::Accumuler(Resultat, *Debut);

return Resultat;
}
};

Les classes de politique sont utilisées intensivement dans la bibliothèque Loki, et de ce fait très bien décrites dans le
livre Modern C++ Design d'Andrei Alexandrescu.

Les classes de traits et de politique sont également décrites et comparées dans C++ templates - the complete guide de
David Vandevoorde et Nicolai M. Josuttis.

Comment dériver une classe d'une classe template ?


Auteurs : Alp Mestan , screetch ,
Un exemple sera plus parlant que des mots :

template <typename T>


class Mere
{
/* ... */
};

// soit la classe fille doit aussi être template comme ici


template <typename T>
class Fille1 : public /*ou private/protected */ Mere<T>
{
/* ... */
};

// soit on fixe le paramètre template


class Fille2 : public Mere<int> // où n'importe quel type à la place de int

- 229 -
Les sources présentées sur cette pages sont libres de droits, et vous pouvez les utiliser à votre convenance. Par contre la page de présentation de ces sources
constitue une oeuvre intellectuelle protégée par les droits d'auteurs. Copyright © 2008 Developpez LLC. Tout droits réservés Developpez LLC. Aucune
reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents et images sans l'autorisation expresse de Developpez
LLC. Sinon vous encourez selon la loi jusqu'à 3 ans de prison et jusqu'à 300 000 E de dommages et intérêts.
http://cpp.developpez.com/faq/cpp/
{
/ * ... */
};

// ou encore
class Fille3 : public /* ... */ Mere<Fille2>
{
/* ... */
};

Soit dit en passant, le dernier héritage (Fille3) est une application du principe de Qu'est-ce que le CRTP ?.

Qu'est-ce que le CRTP ?


Auteurs : Alp Mestan ,
Le CRTP (Curiously Recurring Template Pattern) correspond simplement à la situation suivante, à laquelle on se
retrouve souvent confronté lorsque l'on conçoit des architectures logicielles C++ génériques :

template <class Derived>


class Base
{
/* ... */
};

class Fille : public Base<Fille>


{
/* ... */
};

Pour un exemple de situation qui tire parti du CRTP, il y a le classique compteur d'instances de classes, qui définit un
modèle de classe counter, de sorte que counter<X> et counter<Y> soient deux classes (instances du modèle counter)
différentes. Ainsi, les variables statiques ne seront pas partagées entre counter<X> et counter<Y>. La seule chose qu'il
y a à faire est de définir X et Y comme héritant de counter<X> et counter<Y>.

template <typename T>


struct counter
{
counter()
{
objects_created++;
objects_alive++;
}

virtual ~counter()
{
--objects_alive;
}
static int objects_created;
static int objects_alive;
};
template <typename T> int counter<T>::objects_created( 0 );
template <typename T> int counter<T>::objects_alive( 0 );

class X : counter<X>
{
// ...
};

class Y : counter<Y>

- 230 -
Les sources présentées sur cette pages sont libres de droits, et vous pouvez les utiliser à votre convenance. Par contre la page de présentation de ces sources
constitue une oeuvre intellectuelle protégée par les droits d'auteurs. Copyright © 2008 Developpez LLC. Tout droits réservés Developpez LLC. Aucune
reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents et images sans l'autorisation expresse de Developpez
LLC. Sinon vous encourez selon la loi jusqu'à 3 ans de prison et jusqu'à 300 000 E de dommages et intérêts.
http://cpp.developpez.com/faq/cpp/
{
// ...
};

Imaginons avoir une implémentation d'une classe widget, paramétrée par ce qui sera la véritable classe représentant
un composant graphique donné.

template <class derived_widget>


class widget
{
public:
// ...
void show() { static_cast<derived_widget*>(this)->show(); }
// ...
};

Il s'agit désormais de définir des widget bien précis et concrets, comme button ou textfield.

class button : public widget<button>


{
public:
void show() { std::cout << "Button" << std::endl; }
// ...
};

class textfield : public widget<textfield>


{
public:
void show() { std::cout << "Textfield" << std::endl; }
// ...
};

Ainsi, nous avons l'équivalent d'une fonction virtuelle, ici show(), que l'on aurait mis dans la classe widget sans
pour autant avoir le surcoût à l'exécution entrainé par l'utilisation de la virtualité. Pourtant, appeler show() sur un
widget<button> ou un widget<textfield> affichera bien ce que l'on veut, sans avoir déclaré show comme virtuelle.

button b("Cliquez ici");


textfield t("Entrez du texte ici");
b.show(); // affiche "Button"
t.show(); // affiche "Textfield"

Cela permet donc de simuler le polymorphisme d'héritage, en disposant de fonctions que l'on pourrait croire virtuelles.
Cela s'avèrera toutefois gênant si vous voulez stocker, ici, des widget<T>, avec différents types pour T. Il vous faudra
alors ruser, et notamment regarder le principe de Type Erasure.

lien : Mariage de la Programmation Orientée Objet et de la Programmation Générique : Type Erasure

Qu'est-ce que SFINAE ?


Auteurs : Alp Mestan ,
SFINAE, acronyme de Substitution Failure Is Not An Error, est un principe C++ qui entre en jeu lors de la résolution
des surcharges de fonctions.
Le principe est assez simple. Lorsque vous disposez d'un modèle de fonction (function template), si l'une des
instanciations (remplacement d'un paramètre par un type ou une valeur précise) conduit à un type d'argument ou

- 231 -
Les sources présentées sur cette pages sont libres de droits, et vous pouvez les utiliser à votre convenance. Par contre la page de présentation de ces sources
constitue une oeuvre intellectuelle protégée par les droits d'auteurs. Copyright © 2008 Developpez LLC. Tout droits réservés Developpez LLC. Aucune
reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents et images sans l'autorisation expresse de Developpez
LLC. Sinon vous encourez selon la loi jusqu'à 3 ans de prison et jusqu'à 300 000 E de dommages et intérêts.
http://cpp.developpez.com/faq/cpp/
un type de retour incorrect, alors le compilateur, au lieu d'indiquer une erreur, passera sous silence cela si une autre
fonction (template ou non) du même nom colle à l'appel.
Le code classique qui accompagne une introduction à SFINAE :

struct Test
{
typedef int type;
};

template < typename T >


void f( typename T::type ) {} // definition #1

template < typename T >


void f( T ) {} // definition #2

f< Test > ( 10 ); //appelle #1


f< int > ( 10 ); //appelle #2 sans erreur grace a SFINAE

Ici, aucun problème pour l'appel 1. Pour l'appel 2, si l'on remplace T par int dans la définition #1, on a alors un argument
de type int::Type, ce qui est invalide, int n'étant ni un namespace, ni une structure/classe, mais un type fondamental.
Toutefois, au lieu de nous indiquer une erreur tout simplement, le compilateur voit une autre fonction du même nom
dont la signature colle à l'appel, et c'est celle-ci qu'il choisit d'appeler. Voilà le principe de SFINAE.
Concrètement, qu'est-ce que cela signifie ? Vous savez probablement que l'on ne peut pas spécialiser partiellement une
fonction template. En particulier, il est hors de question de pouvoir spécialiser les fonctions selon les propriétés que les
types des arguments qu'on leur fournit ont. Justement, avec SFINAE, il est désormais possible de le faire. Selon qu'une
classe/structure A possède par exemple un type A::type, nous pouvons donc appeler une certaine fonction ou une autre,
de même nom, mais qui ne demande pas d'avoir cette propriété.

Comment puis-je créer une classe dont la structure peut varier ?


Auteurs : Alp Mestan ,

Il s'agit d'utiliser les techniques relatives aux classes de politique qui sont exposées dans l'article Classes de Traits
et de Politiques en C++.
Si Foo est la classe dont nous voulons rendre la structure variable, nous allons devoir la paramétrer par une politique.
Par exemple, si nous voulons qu'une implémentation de la politique fournisse une interface plus complète, permettant
plus d'opérations, nous le pouvons tout à fait ! On peut ainsi rajouter des fonctions en combinant les politiques à
l'héritage, comme on peut le voir dans l'exemple ci-dessous.

template <class PolicyT>


class Host : public PolicyT
{
public:
void foo() { std::cout << 42 << std::endl; }
};

class PolicyTImpl1
{
public:
void bar() { std::cout << "Forty-Two" << std::endl; }
};

class PolicyTImpl2
{
public:
void bar() { std::cout << "Chuck Norris" << std::endl; }

- 232 -
Les sources présentées sur cette pages sont libres de droits, et vous pouvez les utiliser à votre convenance. Par contre la page de présentation de ces sources
constitue une oeuvre intellectuelle protégée par les droits d'auteurs. Copyright © 2008 Developpez LLC. Tout droits réservés Developpez LLC. Aucune
reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents et images sans l'autorisation expresse de Developpez
LLC. Sinon vous encourez selon la loi jusqu'à 3 ans de prison et jusqu'à 300 000 E de dommages et intérêts.
http://cpp.developpez.com/faq/cpp/
void foobar() { std::cout << "C++" << std::endl; }
};

// ...
Host<PolicyTImpl1> h1;
h1.foo(); // affiche 42
h1.bar(); // affiche "Forty-Two"
h1.foobar(); // ne compile pas

Host<PolicyTImpl2> h2;
h2.foo(); // affiche 42
h1.bar(); // affiche "Chuck Norris"
h1.foobar(); // affiche "C++"

Ici, sur base d'une même classe, nous avons pu exposer des éléments variables en fonction de la politique donnée lors de
l'instanciation. Ce genre de pratiques s'avère très utile lorsque l'on paramètre par exemple une classe par des politiques
pouvant donner des optimisations selon la plateforme.
Pour rendre la compréhension plus facile, nous avons toutefois dû prendre le problème à l'envers. En effet, ce genre de
technique n'est utile que pour certains problèmes. Il n'est utile de faire varier une partie de la classe (points de variation
de la classe) que pour résoudre un problème, il ne faut pas chercher un problème à résoudre avec cette technique, qui
n'a dans le cas contraire aucun sens.
Un exemple de situation où cela peut-être bénéfique... Imaginons devoir réaliser un programme de calcul scientifique
qui doit être multiplateforme. Imaginons de plus que pour un système d'exploitation A, on dispose d'une bibliothèque
classique ainsi que d'une bibliothèque utilisant des appels bien plus rapides pour les calculs, spécifique à ce système
toutefois. Le système d'exploitation B lui ne dispose que de la première bibliothèque. L'objectif est donc de n'exposer
pour B que les opérations fournies par la bibliothèque classique, et d'exposer au choix l'une ou l'autre pour le système
d'exploitation A. Cela ressemblerait au code suivant en utilisant la technique exposée ci-dessus.

template <class ComputingLibraryPolicy>


class Computing : public ComputingLibraryPolicy
{
public:
// ...
};

class FastComputingLibrary
{
public:
float sin(float);
float cos(float);
float fast_sin(float);
float fast_cos(float);
// ...
};

class ComputingLibrary
{
public:
float sin(float);
float cos(float);
};

// ...

#ifdef SYS_EXPLOITATION_A
typedef Computing<FastComputingLibrary> computing_type;
#else
typedef Computing<ComputingLibrary> computing_type;
#endif

// ...

- 233 -
Les sources présentées sur cette pages sont libres de droits, et vous pouvez les utiliser à votre convenance. Par contre la page de présentation de ces sources
constitue une oeuvre intellectuelle protégée par les droits d'auteurs. Copyright © 2008 Developpez LLC. Tout droits réservés Developpez LLC. Aucune
reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents et images sans l'autorisation expresse de Developpez
LLC. Sinon vous encourez selon la loi jusqu'à 3 ans de prison et jusqu'à 300 000 E de dommages et intérêts.
http://cpp.developpez.com/faq/cpp/
computing_type comp;
comp.fastsin(0); // compilera sur l'OS A uniquement
comp.sin(0); // compilera sur les deux plateformes

On a ainsi pu optimiser selon la plateforme simplement en introduisant de la variabilité sur les fonctions utilisées, de
manière élégante, via les politiques.

lien : Classes de Traits et de Politiques en C++.

Comment varier le comportement au moment de l'écriture de code (template) ?


Auteurs : Alp Mestan , 3DArchi ,
Imaginez que vous ayez conçu une classe qui encapsule un calcul très lourd, au point que vous ayez mis sur pieds
deux implémentations, l'une monothreadée, l'autre multithreadée. Il serait dommage de les faire hériter d'une classe
abstraite et d'en hériter pour chacune des versions, induisant un coût à cause de la virtualité, qui est ici superflue. Vous
avez une possibilité qui vous permettra de tout gérer à la compilation, en utilisant les templates. Nous allons illustrer
avec une fonction qui mesure le temps pris par le calcul.

template <class Computation>


void do_something()
{
std::time_t start = time(NULL);
Computation::compute();
std::time_t end = time(NULL);
std::cout << end - start << " seconds." << std::endl;
}

struct SingleThreadedComputation
{
static void compute()
{
// implémentation monothread
}
};

struct MultiThreadedComputation
{
static void compute()
{
// implémentation multithread
}
};

// par exemple :
#ifdef STCOMPUTATION
do_something<SingleThreadedComputation>();
#elif defined MTCOMPUTATION
do_something<MultiThreadedComputation>();
#endif

// comportement que l'on peut choisir soit avec un #define,


// soit avec l'option de compilation -DSTCOMPUTATION ou -DMTCOMPUTATION

Ainsi, on a factorisé la variabilité (multithreading ou pas) de notre calcul dans une fonction paramétrée, Compute, sans
ajouter la surcharge induite par l'utilisation de la virtualité. Le "défaut" est que la variabilité est statique, c'est-à-dire
fixée à la compilation, tandis que le polymorphisme lié à l'héritage nous permet d'avoir une variabilité à l'exécution.

- 234 -
Les sources présentées sur cette pages sont libres de droits, et vous pouvez les utiliser à votre convenance. Par contre la page de présentation de ces sources
constitue une oeuvre intellectuelle protégée par les droits d'auteurs. Copyright © 2008 Developpez LLC. Tout droits réservés Developpez LLC. Aucune
reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents et images sans l'autorisation expresse de Developpez
LLC. Sinon vous encourez selon la loi jusqu'à 3 ans de prison et jusqu'à 300 000 E de dommages et intérêts.
http://cpp.developpez.com/faq/cpp/
Cette façon de faire s'approche de ce que l'on appelle le Policy Based Design, qui permet de paramétrer de
manière très flexible le comportement, dès la compilation, avec une utilisation intelligente des templates. C'est une sorte
d'équivalent du Design Pattern Strategy, à la sauce C++ et templates.

- 235 -
Les sources présentées sur cette pages sont libres de droits, et vous pouvez les utiliser à votre convenance. Par contre la page de présentation de ces sources
constitue une oeuvre intellectuelle protégée par les droits d'auteurs. Copyright © 2008 Developpez LLC. Tout droits réservés Developpez LLC. Aucune
reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents et images sans l'autorisation expresse de Developpez
LLC. Sinon vous encourez selon la loi jusqu'à 3 ans de prison et jusqu'à 300 000 E de dommages et intérêts.
http://cpp.developpez.com/faq/cpp/
Sommaire > La STL
Qu'est-ce que la STL ?
Auteurs : LFE , Luc Hermitte , Aurélien Regat-Barrel ,
Le C++ possède une bibliothèque standard (on parle aussi de SL pour Standard Library) qui est composée, entre autre,
d'une bibliothèque de flux, de la bibliothèque standard du C, de la gestion des exceptions, ..., et de la STL (Standard
Template Library : bibliothèque de modèles standard). En fait, STL est une appellation historique communément
acceptée et comprise. Dans la norme on ne parle que de SL.
Comme son nom l'indique cette bibliothèque utilise fortement les templates C++ et le concept de généricité. Elle définit
ainsi de nombreux modèles de classes et de fonctions génériques parmi lesquelles les conteneurs (tableau, liste, pile,
ensemble, ...) et les algorithmes (copie, tri, recherche, ...) les plus courants. Une particularité importante est son approche
orientée itérateurs, ce qui permet par exemple d'utiliser ses algorithmes sur d'autres conteneurs que ceux qu'elle fournit.
L'un des avantages de la STL par rapport aux autres bibliothèques remplissant (plus ou moins) le même rôle est qu'elle
est standard, et donc théoriquement portable (cependant les limites de certains compilateurs compliquent la chose). Un
autre avantage important est son polymorphisme paramétrique qui assure un typage fort sans exigence syntaxique à
l'utilisation, c'est-à-dire par exemple que l'on peut très simplement créer un tableau de ce que l'on veut sans devoir
downcaster depuis un horrible type commun tel que void *.
Utiliser la STL permet donc d'augmenter de manière significative sa productivité en C++ en écrivant du code fiable,
performant et portable.

Qu'est-ce qu'un conteneur ?


Auteurs : LFE , Aurélien Regat-Barrel , Luc Hermitte ,
Un conteneur (container) est, comme son nom l'indique, un objet qui contient d'autres objets. Il fournit un moyen de
gérer les objets contenus (au minimum ajout / suppression, parfois insertion, tri, recherche, ...) ainsi qu'un accès à
ces objets qui dans le cas de la STL consiste très souvent en un itérateur. Les itérateurs permettent de parcourir une
collection d'objets sans avoir à se préoccuper de la manière dont ils sont stockés. Ceci permet aussi d'avoir une interface
de manipulation commune, et c'est ainsi que la STL fournit des algorithmes génériques qui s'appliquent à la majorité
de ses conteneurs (tri, recherche, ...).
Parmi les conteneurs disponibles dans la STL on trouve les tableaux (vector), les listes (list), les ensembles (set), les piles
(stack), et beaucoup d'autres.

Qu'est-ce qu'un itérateur ?


Auteurs : LFE ,
Les itérateurs (iterator) sont une généralisation des pointeurs : ce sont des objets qui pointent sur d'autres objets. Comme
son nom l'indique, les itérateurs sont utilisés pour parcourir une série d'objets de telle façon que si on incrémente
l'itérateur, il désignera l'objet suivant de la série.

Comment créer et utiliser un tableau avec std::vector ?


Auteurs : Aurélien Regat-Barrel ,
#include <iostream>
#include <vector>
#include <algorithm>
#include <numeric>

using std::cout;

- 236 -
Les sources présentées sur cette pages sont libres de droits, et vous pouvez les utiliser à votre convenance. Par contre la page de présentation de ces sources
constitue une oeuvre intellectuelle protégée par les droits d'auteurs. Copyright © 2008 Developpez LLC. Tout droits réservés Developpez LLC. Aucune
reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents et images sans l'autorisation expresse de Developpez
LLC. Sinon vous encourez selon la loi jusqu'à 3 ans de prison et jusqu'à 300 000 E de dommages et intérêts.
http://cpp.developpez.com/faq/cpp/
int main()
{
// créer un tableau d'entiers vide
std::vector<int> v;

// ajouter l'entier 10 à la fin


v.push_back( 10 );

// afficher le premier élément (10)


cout << v.front() << '\n';

// afficher le dernier élément (10)


cout << v.back() << '\n';

// enlever le dernier élément


v.pop_back(); // supprime '10'

// le tableau est vide


if ( v.empty() )
{
cout << "Tout est normal : tableau vide\n";
}

// redimensionner le tableau
// resize() initialise tous les nouveaux entiers à 0
v.resize( 10 );

// quelle est sa nouvelle taille ?


cout << v.size() << '\n'; // affiche 10

// sa taille est de 10 : on peut accéder directement aux


// 10 premiers éléments
v[ 9 ] = 5;

// intitialiser tous les éléments à 100


std::fill( v.begin(), v.end(), 100 );

// vider le tableau
v.clear(); // size() == 0

// on va insérer 50 éléments
// réserver (allouer) de la place pour au moins 50 éléments
v.reserve( 50 );

// véridier que la taille n'a pas bougé (vide)


cout << v.size() << '\n';

// capacité du tableau = nombre d'éléments qu'il peut stocker


// sans devoir réallouer (modifié grâce à reserve())
cout << v.capacity() << '\n'; // au moins 50, sûrement plus

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


{
// grâce à reserve() on économise de multiples réallocations
// du fait que le tableau grossit au fur et à mesure
v.push_back( i );
}

// afficher la nouvelle taille


cout << v.size() << '\n'; // affiche 50

// rechercher l'élément le plus grand (doit être 49)


cout << *std::max_element( v.begin(), v.end() ) << '\n';

// tronquer le tableau à 5 éléments


v.resize( 5 );

// les trier par ordre croissant

- 237 -
Les sources présentées sur cette pages sont libres de droits, et vous pouvez les utiliser à votre convenance. Par contre la page de présentation de ces sources
constitue une oeuvre intellectuelle protégée par les droits d'auteurs. Copyright © 2008 Developpez LLC. Tout droits réservés Developpez LLC. Aucune
reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents et images sans l'autorisation expresse de Developpez
LLC. Sinon vous encourez selon la loi jusqu'à 3 ans de prison et jusqu'à 300 000 E de dommages et intérêts.
http://cpp.developpez.com/faq/cpp/
std::sort( v.begin(), v.end() );

// parcourir le tableau
for ( size_t i = 0, size = v.size(); i < size; ++i )
{
// attention : utilisation de l'opérateur []
// les accès ne sont pas vérifiés, on peut déborder !
cout << v[ i ] << '\t';
}
cout << '\n';

// utilisation de at() : les accès sont vérifiés


try
{
v.at( v.size() ) = 10; // accès en dehors des limites !
}
catch ( const std::out_of_range & )
{
cout << "at() a levé une exception std::out_of_range\n";
}

// parcours avec un itérateur en inverse


for ( std::vector<int>::reverse_iterator i = v.rbegin();
i != v.rend();
++i )
{
cout << *i << '\t';
}
cout << '\n';

// on crée un tableau v2 de taille 10


std::vector<int> v2( 10 );
v2.at( 9 ) = 5; // correct, le tableau est de taille 10

// on crée un tableau v3 de 10 éléments initialisés à 20


std::vector<int> v3( 10, 20 );

// faire la somme de tous les éléments de v3


// on doit obtenir 200 (10 * 20)
cout << std::accumulate( v3.begin(), v3.end(), 0 ) << '\n';

// on recopie v3 dans v
v = v3;

// on vérifie la recopie
if ( std::equal( v.begin(), v.end(), v3.begin() ) )
{
cout << "v est bien une copie conforme de v3\n";
}
}

Dois-je effacer ce que j'ai stocké dans un vecteur ?


Auteurs : LFE ,
La réponse dépend de la nature de ce qui est stocké dans un vecteur.
S'il s'agit d'un objet, il n'est pas utile de le détruire, il le sera lorsqu'il est retiré du vecteur, ou lorsque le vecteur est
détruit.

- 238 -
Les sources présentées sur cette pages sont libres de droits, et vous pouvez les utiliser à votre convenance. Par contre la page de présentation de ces sources
constitue une oeuvre intellectuelle protégée par les droits d'auteurs. Copyright © 2008 Developpez LLC. Tout droits réservés Developpez LLC. Aucune
reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents et images sans l'autorisation expresse de Developpez
LLC. Sinon vous encourez selon la loi jusqu'à 3 ans de prison et jusqu'à 300 000 E de dommages et intérêts.
http://cpp.developpez.com/faq/cpp/
Par contre, s'il s'agit d'un pointeur sur un objet, il faut le détruire car un pointeur n'est pas un objet. Si cette destruction
n'est pas faite, le programme présentera une fuite de mémoire.

Quel conteneur choisir pour stocker mes objets ?


Auteurs : Laurent Gomila ,
La panoplie de conteneurs proposée par la STL est conséquente : conteneurs ordonnés, associatifs, listes chaînées, ...
Le choix de la bonne structure dépend principalement des opérations que l'on va effectuer sur nos données : suppression,
ajout, recherche, ...
Voici un schéma récapitulatif qui vous aidera à y voir plus clair :

Pour stocker des chaînes de caractères, std::string sera le conteneur le plus approprié. Il fournit des fonctions de
recherche, de découpage, et des opérateurs surchargés pour une manipulation plus intuitive (voir Y a-t-il un type chaîne
de caractères en C++ ?). std::string n'étant qu'un typedef sur std::basic_string<char>, pour manipuler d'autres types
de caractères (unicode par exemple, ou ne tenant pas compte de la casse) il suffit d'utiliser le type approprié (voir
Comment manipuler des chaînes de caractères Unicode ? et Comment manipuler des chaînes de caractères ne tenant
pas compte de la casse ?).

A noter qu'il existe également une classe pour manipuler les champs de bits : std::bitset.

On peut aussi trouver dans certaines versions de la STL d'autres conteneurs, non standards mais bien connus : tables de
hachage, listes simplements chaînées, etc. Leur utilisation est en général similaire à celle des autres conteneurs standards,
mais pensez tout de même à bien vous documenter avant de les utiliser !

Comment supprimer correctement des éléments d'un conteneur ?


Auteurs : Laurent Gomila ,
Effacer des éléments d'un conteneur est une tâche fréquente, mais souvent mal réalisée.

L'approche la plus naïve est de parcourir le conteneur à l'aide d'une boucle, et de supprimer (via la fonction membre
erase) les éléments concernés. Mais attention, il faut veiller à bien manipuler l'itérateur afin de ne pas oublier d'éléments
ou de pointer en dehors du conteneur.
Le méthode correcte est la suivante :

#include <vector>

std::vector<int> s;
for (std::vector<int>::iterator it = s.begin(); it != s.end(); )
{
if (*it == 5)
it = s.erase(it);
else
++it;
}

A noter que cette méthode ne fonctionne pas avec les conteneurs associatifs (set, map, multiset, multimap) : leur fonction
erase ne renvoie rien.

Il existe cependant des fonctions plus appropriées pour chaque conteneur.

- 239 -
Les sources présentées sur cette pages sont libres de droits, et vous pouvez les utiliser à votre convenance. Par contre la page de présentation de ces sources
constitue une oeuvre intellectuelle protégée par les droits d'auteurs. Copyright © 2008 Developpez LLC. Tout droits réservés Developpez LLC. Aucune
reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents et images sans l'autorisation expresse de Developpez
LLC. Sinon vous encourez selon la loi jusqu'à 3 ans de prison et jusqu'à 300 000 E de dommages et intérêts.
http://cpp.developpez.com/faq/cpp/
Pour vector et string, on utilise ce qu'on appelle l'idiome erase-remove : on appelle std::remove (ou std::remove_if qui va
prendre en paramètre un prédicat plutôt qu'une valeur -- voir Qu'est-ce qu'un prédicat ?) qui va déplacer les éléments
à supprimer à la fin du conteneur, puis la fonction membre erase qui va les supprimer définitivement.

#include <string>
#include <vector>
#include <algorithm>

std::string s = "blah blah";


s.erase(std::remove(s.begin(), s.end(), 'h'), s.end());

std::vector v;
v.erase(std::remove_if(v.begin(), v.end(), std::bind2nd(std::greater<int>(), 3)), v.end());

Pour list, on utilise plus simplement la fonction membre remove (ou remove_if) qui contrairement à std::remove va
bien supprimer les éléments.

std::list<int> l;
l.remove(5);
l.remove_if(std::bind2nd(std::greater<int>(), 3));

Pour les [multi]set et les [multi]map il existe la fonction membre erase, qui remplit exactement le même rôle que
list::remove (attention aux noms, cela peut facilement porter à confusion). A noter que pour les [multi]map, erase prend
en paramètre la clé à effacer, non la valeur.

#include <map>
#include <string>

std::map<std::string, int> m;
m.erase("toto");

Comment utiliser correctement les conteneurs standards avec du code C ?


Auteurs : Laurent Gomila ,
L'utilisation de code C dans un projet C++ est parfois inévitable, si l'on doit manipuler des bibliothèques écrites en C
ou encore des fonctions codées dans un mauvais style. Il convient dans ce cas de prendre quelques précautions si l'on
manipule des tableaux.

- Utilisez std::vector si vous devez passer un tableau à une fonction C, car seul ce conteneur garantit la contiguïté des
données en mémoire.

void Fonction_C(const int* Tableau, unsigned int Taille);

std::vector<int> v;
Fonction_C(&v[0], v.size());

- Si vous devez alimenter un conteneur avec les données d'un tableau C, la STL fournit tout ce qu'il faut pour le faire
proprement.

int* Fonction_C(int* Taille); // Renvoie un tableau et sa taille

- 240 -
Les sources présentées sur cette pages sont libres de droits, et vous pouvez les utiliser à votre convenance. Par contre la page de présentation de ces sources
constitue une oeuvre intellectuelle protégée par les droits d'auteurs. Copyright © 2008 Developpez LLC. Tout droits réservés Developpez LLC. Aucune
reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents et images sans l'autorisation expresse de Developpez
LLC. Sinon vous encourez selon la loi jusqu'à 3 ans de prison et jusqu'à 300 000 E de dommages et intérêts.
http://cpp.developpez.com/faq/cpp/
int Taille = 0;
int* Tableau = Fonction_C(&Taille);

// Méthode 1 : utilisation du constructeur prenant en paramètre une paire d'itérateurs


std::set<int> s(Tableau, Tableau + Taille);

// Méthode 2 : utilisation de la fonction membre assign


std::list<int> l;
l.assign(Tableau, Tableau + Taille);

// Méthode 3 : utilisation de std::copy


std::vector<int> v;
std::copy(Tableau, Tableau + Taille, std::back_inserter(v));

- Pour les chaînes de caractères, std::string permet d'obtenir une chaîne C (caractères contigüs et '\0' final) via sa
fonction membre c_str(). Attention cependant : la chaîne renvoyée peut très bien être temporaire et ne pas survivre à
l'appel, il ne faut donc jamais stocker le pointeur retourné pour l'utiliser ultérieurement.
Voir Comment convertir un char* en un string ? et Comment convertir une string en char* ?.

Qu'est-ce qu'un foncteur ?


Auteurs : Laurent Gomila ,
Toute classe surchargeant l'opérateur d'appel de fonction operator() est qualifiée de classe foncteur (functor class). Les
objets instanciés d'une telle classe sont appelés objets fonctionnels (function objects) ou foncteurs (functors). La STL
utilise beaucoup ce concept pour personnaliser le comportement de ses classes/fonctions.
Notez qu'on peut tout à fait utiliser des pointeurs sur fonction au lieu de foncteurs (il suffit de pouvoir appliquer
l'opérateur () à l'objet passé en paramètre), mais on préfèrera les foncteurs car ils offrent plus de performances et
de flexibilité. En effet operator() peut être entièrement inliné, et on peut passer des paramètres au foncteur via son
constructeur pour plus de souplesse.
Voici un exemple typique de foncteurs tiré de la question [Exemple] Comment trier une séquence selon un critère
personnalisé ?:

struct A
{
int Number;
std::string String;
};

// Définition du foncteur servant à trier nos objets selon le nombre


struct SortByNumber
{
bool operator ()( const A & a1, const A & a2 ) const
{
return a1.Number < a2.Number;
}
};

// Définition du foncteur servant à trier nos objets selon la chaîne


struct SortByString
{
bool operator ()( const A & a1, const A & a2 ) const
{
return a1.String < a2.String;
}
};

- 241 -
Les sources présentées sur cette pages sont libres de droits, et vous pouvez les utiliser à votre convenance. Par contre la page de présentation de ces sources
constitue une oeuvre intellectuelle protégée par les droits d'auteurs. Copyright © 2008 Developpez LLC. Tout droits réservés Developpez LLC. Aucune
reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents et images sans l'autorisation expresse de Developpez
LLC. Sinon vous encourez selon la loi jusqu'à 3 ans de prison et jusqu'à 300 000 E de dommages et intérêts.
http://cpp.developpez.com/faq/cpp/
Les foncteurs sont en particulier utilisés dans le cadre des prédicats (lire Qu'est-ce qu'un prédicat ?).

Qu'est-ce qu'un prédicat ?


Auteurs : Aurélien Regat-Barrel ,
Les prédicats (predicate) sont un type particulier de foncteurs qui renvoient un booléen ou quelque chose de convertible
en booléen ce qui les rend utilisables dans des expressions logiques. Il existe un certain nombre de prédicats prédéfinis
dans la STL, en particulier en ce qui concerne les opérateurs logiques et arithmétiques élémentaires :

int a = 2;
if ( std::greater<int>()( a, 1 ) )
{
// ce test est vrai car a > 1
}

L'intérêt des prédicats est qu'ils peuvent être utilisés comme critère d'évaluation dans bon nombre d'algorithmes et
conteneurs de la STL. Par exemple le conteneur std::set utilise par défaut le prédicat std::less afin d'organiser ses
éléments de manière croissante. Il est facile de changer ce critère pour créer un std::set organisé de manière décroissante
en spécifiant std::greater.

std::set< std::greater<int> > entiersTriesParOrdreDecroissant;

Mais les prédicats révèlent toute leur puissance lorsqu'ils sont utilisés conjointement avec les foncteurs réducteurs
std::bind1st et std::bind2nd. Pour plus d'informations lire A quoi servent les fonctions bind1st et bind2nd ?.

A quoi servent les fonctions bind1st et bind2nd ?


Auteurs : Aurélien Regat-Barrel ,
std::bind1st et std::bind2nd sont un type particulier de foncteurs appelés foncteurs réducteurs. Ils permettent de
facilement créer un prédicat unaire (c.a.d acceptant un seul paramètre) à partir d'un prédicat binaire (en acceptant 2
paramètres). Ceci est fait en figeant la valeur d'un des deux paramètres du prédicat binaire (c.a.d à le remplacer par
une valeur fixe).
Soit l'exemple suivant où le prédicat plus_grand_que_10 est créé afin de compter grâce à std::count_if le nombre de
valeurs supérieures à 10 dans un conteneur :

#include <iostream>
#include <vector>
#include <algorithm>

// prédicat personnalisé permettant de tester si un


// entier est strictement supérieur à 10
struct plus_grand_que_10
{
bool operator ()( int N ) const
{
return N > 10;
}
};

int main()
{
std::vector<int> v;
v.push_back( 0 );
v.push_back( 5 );

- 242 -
Les sources présentées sur cette pages sont libres de droits, et vous pouvez les utiliser à votre convenance. Par contre la page de présentation de ces sources
constitue une oeuvre intellectuelle protégée par les droits d'auteurs. Copyright © 2008 Developpez LLC. Tout droits réservés Developpez LLC. Aucune
reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents et images sans l'autorisation expresse de Developpez
LLC. Sinon vous encourez selon la loi jusqu'à 3 ans de prison et jusqu'à 300 000 E de dommages et intérêts.
http://cpp.developpez.com/faq/cpp/
v.push_back( 10 );
v.push_back( 15 );
v.push_back( 20 );

// compter le nombre de valeurs > 10


int n = std::count_if(
v.begin(),
v.end(),
plus_grand_que_10() ); // utilisation de notre prédicat

std::cout << n << '\n'; // affiche 2


}

plus_grand_que_10 est un prédicat unaire obtenu en figeant le second argument de l'opérateur de comparaison
supérieur à la valeur 10. On aurait pu aussi obtenir le même résultat en figeant le premier paramètre de l'opérateur
inférieur cette fois-ci:

struct plus_grand_que_10
{
bool operator ()( int N ) const
{
return 10 < N;
}
};

bind1st et bind2nd permettent de simplifier cette tâche fastidieuse de définition d'un prédicat binaire.
bind1st permet de figer le premier argument d'un prédicat binaire.
bind2nd permet de figer le second argument d'un prédicat binaire.
Ils s'utilisent de cette manière :

<bind>( <foncteur>, <valeur du paramètre à figer> );

L'exemple précédent peut alors être simplifié ainsi:

#include <iostream>
#include <vector>
#include <algorithm>
#include <functional>

int main()
{
std::vector<int> v;
v.push_back( 0 );
v.push_back( 5 );
v.push_back( 10 );
v.push_back( 15 );
v.push_back( 20 );

// compter le nombre de valeurs > 10

// méthode 1 : utilisation de bind2nd pour


// la création d'un prédicat x > 10
int n = std::count_if(
v.begin(),
v.end(),
std::bind2nd( std::greater<int>(), 10 ) );

std::cout << n << '\n'; // affiche 2

// méthode 2 : utilisation de bind1st pour


// la création d'un prédicat 10 < x
n = std::count_if(
v.begin(),

- 243 -
Les sources présentées sur cette pages sont libres de droits, et vous pouvez les utiliser à votre convenance. Par contre la page de présentation de ces sources
constitue une oeuvre intellectuelle protégée par les droits d'auteurs. Copyright © 2008 Developpez LLC. Tout droits réservés Developpez LLC. Aucune
reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents et images sans l'autorisation expresse de Developpez
LLC. Sinon vous encourez selon la loi jusqu'à 3 ans de prison et jusqu'à 300 000 E de dommages et intérêts.
http://cpp.developpez.com/faq/cpp/
v.end(),
std::bind1st( std::less<int>(), 10 ) );

std::cout << n << '\n'; // affiche 2


}

[Exemple] Comment trier une séquence selon un critère personnalisé ?


Auteurs : Laurent Gomila ,
#include <vector>
#include <algorithm>
#include <iostream>
#include <string>
#include <set>

// Définition de notre structure à trier


struct A
{
A(int i, std::string s) : Number(i), String(s) {}

int Number;
std::string String;
};

// Surcharge de l'opérateur << pour afficher nos A


std::ostream& operator <<(std::ostream& Stream, const A& a)
{
return Stream << a.Number << " " << a.String;
}

// Définition du foncteur servant à trier nos objets selon le nombre


struct SortByNumber
{
bool operator ()(const A& a1, const A& a2) const
{
return a1.Number < a2.Number;
}
};

// Définition du foncteur servant à trier nos objets selon la chaîne


struct SortByString
{
bool operator ()(const A& a1, const A& a2) const
{
return a1.String < a2.String;
}
};

int main()
{
// Création d'un tableau de A
std::vector<A> v;
v.push_back(A(1, "salut"));
v.push_back(A(5, "hello"));
v.push_back(A(2, "buenos dias"));

// Tri selon leur numéro


std::sort(v.begin(), v.end(), SortByNumber());
std::copy(v.begin(), v.end(), std::ostream_iterator<A>(std::cout, "\n"));
std::cout << std::endl;

// Tri selon leur chaîne de caractères


std::sort(v.begin(), v.end(), SortByString());
std::copy(v.begin(), v.end(), std::ostream_iterator<A>(std::cout, "\n"));

- 244 -
Les sources présentées sur cette pages sont libres de droits, et vous pouvez les utiliser à votre convenance. Par contre la page de présentation de ces sources
constitue une oeuvre intellectuelle protégée par les droits d'auteurs. Copyright © 2008 Developpez LLC. Tout droits réservés Developpez LLC. Aucune
reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents et images sans l'autorisation expresse de Developpez
LLC. Sinon vous encourez selon la loi jusqu'à 3 ans de prison et jusqu'à 300 000 E de dommages et intérêts.
http://cpp.developpez.com/faq/cpp/
std::cout << std::endl;

// On peut également personnaliser le comportement des conteneurs triés


std::set<A, SortByString> s;
s.insert(A(1, "salut"));
s.insert(A(5, "hello"));
s.insert(A(2, "buenos dias"));

// Les éléments du conteneurs sont automatiquement triés selon leur chaîne grâce à notre foncteur
std::copy(s.begin(), s.end(), std::ostream_iterator<A>(std::cout, "\n"));

return 0;
}

[Exemple] Comment détruire les pointeurs d'un conteneur ?


Auteurs : Laurent Gomila ,

#include <list>
#include <algorithm>

// Foncteur servant à libérer un pointeur - applicable à n'importe quel type


struct Delete
{
template <class T> void operator ()(T*& p) const
{
delete p;
p = NULL;
}
};

int main()
{
// Création d'une liste de pointeurs
std::list<int*> l;
l.push_back(new int(5));
l.push_back(new int(0));
l.push_back(new int(1));
l.push_back(new int(6));

// Destruction de la liste : attention il faut bien libérer les pointeurs avant la liste !
std::for_each(l.begin(), l.end(), Delete());

return 0;
}

Voir aussi la question [Exemple] Comment détruire les pointeurs d'un conteneur en utilisant Boost ?.

Pourquoi ne puis-je pas trier une liste avec std::sort ?


Auteurs : Laurent Gomila ,
La fonction std::sort de la bibliothèque standard n'est compatible qu'avec les itérateurs à accès direct (ceux pour lesquels
les opérateurs + et - sont définis), c'est-à-dire les itérateurs de std::vector, std::string, std::deque et les pointeurs bruts.
En effet, l'accès direct est l'un des prérequis de l'algorithme de tri.

Pour trier un conteneur de type std::list, il faut utiliser la fonction membre std::list::sort :

- 245 -
Les sources présentées sur cette pages sont libres de droits, et vous pouvez les utiliser à votre convenance. Par contre la page de présentation de ces sources
constitue une oeuvre intellectuelle protégée par les droits d'auteurs. Copyright © 2008 Developpez LLC. Tout droits réservés Developpez LLC. Aucune
reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents et images sans l'autorisation expresse de Developpez
LLC. Sinon vous encourez selon la loi jusqu'à 3 ans de prison et jusqu'à 300 000 E de dommages et intérêts.
http://cpp.developpez.com/faq/cpp/
std::list<int> mylist;
mylist.sort();

// Ou alors, avec un foncteur :


std::list<int> mylist;
mylist.sort(MonFoncteur());

Notez que cette version particulière sera plus lente que la version générique.

Pour les utilisateurs de Visual C++ 6, la STL de ce dernier ne définit pas la version prenant en paramètre un foncteur
(à cause du manque de support des fonctions membres templates). Dans ce cas, une solution peut être de passer par
un vecteur :

// Copie de la liste dans un nouveau vecteur


std::vector v(mylist.begin(), mylist.end());

// Tri du vecteur
std::sort(v.begin(), v.end(), MonFoncteur());

// Recopie du vecteur dans la liste originale


mylist.clear();
std::copy(v.begin(), v.end(), std::back_inserter(mylist));

Comment fonctionnent les algorithmes de la STL ?


Auteurs : r0d ,
Tout cela est expliqué dans le tutoriel de r0d : http://r0d.developpez.com/articles/algos-stl/

- 246 -
Les sources présentées sur cette pages sont libres de droits, et vous pouvez les utiliser à votre convenance. Par contre la page de présentation de ces sources
constitue une oeuvre intellectuelle protégée par les droits d'auteurs. Copyright © 2008 Developpez LLC. Tout droits réservés Developpez LLC. Aucune
reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents et images sans l'autorisation expresse de Developpez
LLC. Sinon vous encourez selon la loi jusqu'à 3 ans de prison et jusqu'à 300 000 E de dommages et intérêts.
http://cpp.developpez.com/faq/cpp/
Sommaire > Boost
Qu'est-ce que Boost ?
Auteurs : Aurélien Regat-Barrel ,
Boost est un ensemble de bibliothèques C++ gratuites et portables dont certaines seront intégrées au prochain standard
C++ (voir Qu'est-ce que le Library Technical Report (tr1 / tr2) ?). On y retrouve donc naturellement les concepts de
la bibliothèque standard actuelle, et en particulier ceux de la STL avec laquelle elle se mélange parfaitement. Boost est
très riche : elle fournit notamment des bibliothèques pour :

• les threads (Boost.Thread)


• les matrices (uBLAS) et les tableaux à dimensions multiples (Boost.MultiArray)
• les expressions régulières (Boost.Regex)
• la méta-programmation Boost.Mpl
• l'utilisation de Qu'est-ce qu'un foncteur ? (Boost.lambda, Boost.bind)
• la date et l'heure (Boost.Date_Time)
• les fichiers et les répertoires (Boost Filesystem)
• gérer la mémoire avec des pointeurs intelligents (Smart Pointers)
• faire de la sérialisation en binaire / texte / XML, en particulier sur les conteneurs standards (Boost Serialization)
• manipuler des graphes mathématiques (Boost Graph)
• manipuler les chaînes de caractères (Boost String Algorithms)
• la création de parsers (Boost.spirit)
• et bien d'autres...

La liste complète des bibliothèques classées par catégories est disponible ici : http://www.boost.org/libs/libraries.htm.

La plupart de ces bibliothèques tentent d'exploiter au maximum les possibilités du langage C++. En fait, Boost se
veut un laboratoire d'essais destiné à expérimenter de nouvelles bibliothèques pour le C++. Il s'agit donc aussi d'une
communauté d'experts (dont plusieurs sont membres du comité ISO de normalisation du C++) qui mettent un point
d'honneur à ce qu'un maximum de compilateurs et de systèmes soient supportés. Ils débattent aussi de l'acceptation de
nouvelles bibliothèques et l'évolution de celles déjà existantes, préfigurant ainsi ce que à quoi ressemblera certainement
la prochaine bibliothèque standard du C++ (voir Qu'est-ce que C++0x ?).

C'est donc là que réside le grand intérrêt de Boost. Outre son excellence technique et sa license très permissive
(compatible avec la GPL) qui permet de l'utiliser gratuitement dans des projets commerciaux, Boost est aussi un choix
très viable sur le long terme. En effet, on peut légétimement espérer qu'un nombre important de ses bibliothèques soient
un jour standardisées, ce qui en fait un outil dans lequel on peut investir du temps (et donc de l'argent) sans craindre
de tout perdre au bout de quelques années faute de support ou d'évolution.

Comment installer boost ?


Auteurs : Aurélien Regat-Barrel ,
Une bonne partie des bibliothèques qui composent Boost peuvent être utilisées directement, sans nécessiter aucune
compilation. Si, dans un premier temps, votre utilisation de Boost se limite à ce genre de bibliothèque, installer Boost
consiste simplement à rendre ses fichiers d'en-tête accessibles à votre compilateur (INCLUDE PATH). Référez vous à
la documentation de ce dernier pour savoir comment procéder. Quant aux autres bibliothèques bâties sur des appels
système telles que boost::filesystem, boost::date_time, elles nécessitent auparavant d'être compilées. Pour cela, Boost
utilise son propre système de génération de type make : Boost.Jam, ou plus simplement bjam. Il faut d'abord compiler ce
dernier (ou récupérer une version compilée), avant de l'utiliser pour compiler la bibliothèque. Pour plus d'information
sur la compilation de Boost, référez-vous à la documentation disponible en ligne ou encore dans le répertoire /tools/
build/. Pensez aussi à effectuer une recherche sur nos forums.

- 247 -
Les sources présentées sur cette pages sont libres de droits, et vous pouvez les utiliser à votre convenance. Par contre la page de présentation de ces sources
constitue une oeuvre intellectuelle protégée par les droits d'auteurs. Copyright © 2008 Developpez LLC. Tout droits réservés Developpez LLC. Aucune
reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents et images sans l'autorisation expresse de Developpez
LLC. Sinon vous encourez selon la loi jusqu'à 3 ans de prison et jusqu'à 300 000 E de dommages et intérêts.
http://cpp.developpez.com/faq/cpp/
Enfin, les utilisateurs de Visual C++ peuvent utiliser la version prête à l'emploi gracieusement mise à leur disposition
par : Boost Consulting. Vous pouvez lire à ce sujet le tutorial Installer et utiliser Boost sous Windows avec Visual C
++ 2005.

Où trouver de la documentation sur Boost ?


Auteurs : Aurélien Regat-Barrel ,
La principale source d'information sur Boost est la documentation officielle de chaque bibliothèque disponible sur le
site de Boost. En dehors de cela, il existe malheureusement assez peu de références sur le sujet.

Citons néanmoins les tutoriels de Miles, en français : Un aperçu des possibilités des bibliothèques de Boost.

Ainsi que quelques livres (en anglais) :

• Beyond the C++ Standard Library An Introduction to Boost dont 1 chapitre est disponible ici : Library 9 -
Bind.
• C++ Template Metaprogramming : Concepts, Tools, And Techniques From Boost And Beyond dont 2 chapitres
sont disponibles ici : Table of Contents and Sample Chapters.
• The Boost Graph Library: User Guide and Reference Manual.

Et bien sûr la présente FAQ qui comporte une section consacrée à Boost.

Comment utiliser les pointeurs intelligents de Boost ?


Auteurs : Aurélien Regat-Barrel , Laurent Gomila ,

Boost met à notre dispositions plusieurs types de pointeurs intelligents (voir Boost Smart Pointers). Les plus
couramment utilisés sont boost::shared_ptr et boost::shared_array (pour les tableaux) qui sont des pointeurs
intelligents fonctionnant par comptage de référence :

#include <iostream>
#include <string>
#include <boost/shared_ptr.hpp>

class Test
{
public:
Test( const char * Name ):
name( Name )
{
}

~Test()
{
std::cout << "Destruction de " << this->name << '\n';
}

void printName()
{
std::cout << this->name << '\n';
}

private:
std::string name;
};

- 248 -
Les sources présentées sur cette pages sont libres de droits, et vous pouvez les utiliser à votre convenance. Par contre la page de présentation de ces sources
constitue une oeuvre intellectuelle protégée par les droits d'auteurs. Copyright © 2008 Developpez LLC. Tout droits réservés Developpez LLC. Aucune
reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents et images sans l'autorisation expresse de Developpez
LLC. Sinon vous encourez selon la loi jusqu'à 3 ans de prison et jusqu'à 300 000 E de dommages et intérêts.
http://cpp.developpez.com/faq/cpp/
// déclaration du type pointeur intelligent sur Test
typedef boost::shared_ptr<Test> TestPtr;

int main()
{
TestPtr ptr; // pointeur initialisé à NULL

{
// pointeur temporaire détruit à la fin du bloc
TestPtr ptr_tmp( new Test( "objet1" ) );

// initialiser ptr avec ptr_tmp


ptr = ptr_tmp;
} // ici, ptr_tmp est détruit, mais ptr reste valide

ptr->printName(); // OK, affiche "objet1"

// réinitialiser le pointeur avec un nouvel objet


// objet1 est détruit, objet2 est créé
ptr.reset( new Test( "objet2" ) );

ptr->printName(); // OK, affiche "objet2"

// copie du pointeur sur objet2


TestPtr ptr2 = ptr;

// mise à NULL de ptr


ptr.reset(); // rien ne se passe

// mise à NULL de ptr2


ptr2.reset(); // objet2 est détruit

// utilisation du pointeur NULL : erreur en mode Debug


ptr->printName(); // Assertion failed: px != 0
}

Ce programme produit le résultat suivant s'il est compilé pour ne pas ignorer les assertions :

objet1 Destruction de objet1 objet2 Destruction de objet2 Assertion failed: px != 0

Si la donnée manipulée est une ressource un peu particulière, et ne doit pas être libérée via delete, on peut spécifier via
un foncteur le comportement à adopter lors de la libération du pointeur intelligent :

struct Delete
{
void operator ()(Test*& Ptr) const
{
cout << "Destruction";
delete Ptr;
}
};

int main()
{
shared_ptr<Test> Ptr(new Test(), Delete());
}

- 249 -
Les sources présentées sur cette pages sont libres de droits, et vous pouvez les utiliser à votre convenance. Par contre la page de présentation de ces sources
constitue une oeuvre intellectuelle protégée par les droits d'auteurs. Copyright © 2008 Developpez LLC. Tout droits réservés Developpez LLC. Aucune
reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents et images sans l'autorisation expresse de Developpez
LLC. Sinon vous encourez selon la loi jusqu'à 3 ans de prison et jusqu'à 300 000 E de dommages et intérêts.
http://cpp.developpez.com/faq/cpp/
Voir Qu'est-ce qu'un foncteur ?

lien : Les pointeurs intelligents en C++ par Loïc Joly

Quelles sont les possibilités de casting sur un shared_ptr ?


Auteurs : Aurélien Regat-Barrel ,
Elles sont très nombreuses, et équivalentes à celles sur les pointeurs bruts dans leur grande majorité.

Prenons l'exemple de base suivant :

#include <boost/shared_ptr.hpp>

class Base
{
public:
virtual ~Base() {};
};

class Derived : public Base {};

typedef boost::shared_ptr<Base> BasePtr;


typedef boost::shared_ptr<const Base> BaseConstPtr;
typedef boost::shared_ptr<Derived> DerivedPtr;
typedef boost::shared_ptr<const Derived> DerivedConstPtr;

L'upcasting est bien évidemment implicite, comme il le serait pour un pointeur brut :

void implicit_upcasting()
{
// casting implicite sur des pointeurs bruts
{
Derived *d = new Derived;
Base *b1 = d;
const Base *b2 = d;
delete d;
}
// équivalent avec des pointeurs intelligents
{
DerivedPtr d( new Derived );
BasePtr b1 = d;
BaseConstPtr b2 = d;
}
}

Concernant le downcasting et le constcasting, il est nécessaire de recourir à des fonctions libres spécialisées :

void down_casting()
{
// downcasting sur des pointeurs bruts
{
Base *b = new Derived;
Derived *d1 = static_cast<Derived*>( b );
Derived *d2 = dynamic_cast<Derived*>( b );
assert( d2 != 0 );
delete b;
}
// équivalent avec des pointeurs intelligents
{
BasePtr b( new Derived );

- 250 -
Les sources présentées sur cette pages sont libres de droits, et vous pouvez les utiliser à votre convenance. Par contre la page de présentation de ces sources
constitue une oeuvre intellectuelle protégée par les droits d'auteurs. Copyright © 2008 Developpez LLC. Tout droits réservés Developpez LLC. Aucune
reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents et images sans l'autorisation expresse de Developpez
LLC. Sinon vous encourez selon la loi jusqu'à 3 ans de prison et jusqu'à 300 000 E de dommages et intérêts.
http://cpp.developpez.com/faq/cpp/
DerivedPtr d1 = boost::static_pointer_cast<Derived>( b );
DerivedPtr d2 = boost::dynamic_pointer_cast<Derived>( b );
assert( d2 != 0 );
}
}

void const_casting()
{
// constcasting sur des pointeurs bruts
{
const Base *b_const = new Base;
Base *b2 = const_cast<Base*>( b_const );
delete b_const;
}
// équivalent avec des pointeurs intelligents
{
BaseConstPtr b_const( new Base );
BasePtr b2 = boost::const_pointer_cast<Base>( b_const );
}
}

Les trois fonctions de conversions présentées ci-dessus :

• const_pointer_cast ;
• static_pointer_cast ;
• dynamic_pointer_cast.

ont été ratifiées par le commité de normalisation ISO et incluses dans le Technical Report 1 (tr1). Ce n'est pas le cas
de quatre autres fonctions, qui ont été déclarées obsolètes :

• shared_static_cast ;
• shared_dynamic_cast ;
• shared_polymorphic_cast ;
• shared_polymorphic_downcast.

Les deux premières sont respectivement équivalentes à static_pointer_cast et dynamic_pointer_cast, et leur usage est
donc fortement découragé. Les deux dernières en revanche n'auront pas d'équivalent dans le prochain standard.
Elles correspondent en fait à boost::polymorphic_cast et boost::polymorphic_downcast appliqués aux shared_ptr (voir
Comment utiliser les pointeurs intelligents de Boost ?).

L'exemple suivant illustre une possible utilisation de ces deux fonctions :

void deprecated_casting()
{
// downcasting sur des pointeurs bruts
{
Base *b = new Derived;
try
{
// downcasting levant std::bad_cast en cas d'échec
Derived &d1 = dynamic_cast<Derived&>( *b );
}
catch ( const std::bad_cast & )
{
}
// downcasting provoquant une erreur uniquement en debug
#ifdef _DEBUG
Derived *d2 = dynamic_cast<Derived*>( b );
assert( d2 );
#else
Derived *d2 = static_cast<Derived*>( b );

- 251 -
Les sources présentées sur cette pages sont libres de droits, et vous pouvez les utiliser à votre convenance. Par contre la page de présentation de ces sources
constitue une oeuvre intellectuelle protégée par les droits d'auteurs. Copyright © 2008 Developpez LLC. Tout droits réservés Developpez LLC. Aucune
reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents et images sans l'autorisation expresse de Developpez
LLC. Sinon vous encourez selon la loi jusqu'à 3 ans de prison et jusqu'à 300 000 E de dommages et intérêts.
http://cpp.developpez.com/faq/cpp/
#endif
delete b;
}
// équivalent avec des pointeurs intelligents
{
BasePtr b( new Derived );
try
{
// downcasting levant std::bad_cast en cas d'échec
DerivedPtr d1 = boost::shared_polymorphic_cast<Derived>( b );
}
catch ( const std::bad_cast & )
{
}
// downcasting provoquant une erreur uniquement en debug
DerivedPtr d2 = boost::shared_polymorphic_downcast<Derived>( b );
}
}

La décision d'utiliser ou non ces deux fonctions vous incombe. Soyez simplement conscient que si vous le faites, vous
rendrez votre code plus difficile à migrer le jour où vous souhaiterez utiliser les shared_ptr standards.

Pour terminer, rappelons qu'il est possible de construire un shared_ptr à partir d'un std::auto_ptr (qui est alors invalidé
par le shared_ptr construit), ce qui peut s'apparenter en quelque sorte à un cast d'auto_ptr en shared_ptr.

std::auto_ptr<int> p1( new int );


boost::shared_ptr<int> p2( p1 );
// ici, p1 est invalide

Quels sont les possibilités de conversion (casting) proposées par Boost ?


Auteurs : Aurélien Regat-Barrel ,
boost::conversion introduit quatre types de cast sous forme de fonctions templates libres :

• polymorphic_cast ;
• polymorphic_downcast ;
• lexical_cast ;
• numeric_cast.

polymorphic_cast s'utilise comme dynamic_cast, mais contrairement à ce dernier qui possède un comportement
différent en fonction du type casté (en cas d'erreur), polymorphic_cast lève systématiquement une exception
std::bad_cast en cas d'échec. Son comportement est donc le même que celui de dynamic_cast en cas de conversion de
références, et c'est précisément pourquoi polymorphic_cast n'est pas prévu pour être utilisé avec ces dernière.
Notez que polymorphic_cast peut être utilisé pour effectuer du Qu'est-ce que le cross-casting ?. Si vous utilisez
dynamic_cast pour effectuer du downcasting (ou crosscasting) qui ne devrait jamais échouer, pensez à utiliser
polymorphic_cast qui vous économisera de tester le résultat du cast et permet aussi de mieux signaler dans le code
l'intention d'effectuer un cast qui ne devrait pas échouer.

Si l'utilisation de dynamic_cast vous procure des problème de performance dans votre programme, (ce qui devrait
traduire un problème de conception, voir Pourquoi l'utilisation du downcasting est-il souvent une pratique à éviter ?) la
solution habituelle est d'utiliser static_cast en remplacement. Ce dernier est bien plus performant, mais aussi beaucoup
plus risqué dans la mesure où le compilateur vous fait pleinement confiance, et est incapable de vous signaler une erreur
(ce que dynamic_cast ou polymorphic_cast savent faire).

- 252 -
Les sources présentées sur cette pages sont libres de droits, et vous pouvez les utiliser à votre convenance. Par contre la page de présentation de ces sources
constitue une oeuvre intellectuelle protégée par les droits d'auteurs. Copyright © 2008 Developpez LLC. Tout droits réservés Developpez LLC. Aucune
reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents et images sans l'autorisation expresse de Developpez
LLC. Sinon vous encourez selon la loi jusqu'à 3 ans de prison et jusqu'à 300 000 E de dommages et intérêts.
http://cpp.developpez.com/faq/cpp/
polymorphic_downcast est une sorte de compromis entre ces deux choix. Compilé en version de développement
(DEBUG), polymorphic_downcast se comporte un peu comme polymorphic_cast, sauf qu'en cas d'échec une assertion
failure est déclenchée au lieu d'une exception. Dans le code de production (RELEASE), son appel est remplacé par un
simple appel à static_cast, permettant ainsi d'obtenir un programme final performant sans trop pénaliser la fiabilité.

polymorphic_downcast est malgré tout à utiliser avec retenu, en tant qu'optimisation après qu'un problème de
performances ait été identifié, et si ce dernier ne peut pas être résolu en reconsidérant le design de l'application.

A noter aussi, que contrairement à dynamic_cast et donc polymorphic_cast, polymorphic_downcast ne peut pas être
utilisé pour du crosscasting.

Comment découper une chaîne avec boost::tokenizer ?


Auteurs : Aurélien Regat-Barrel ,
Le programme suivant illustre comment utiliser boost::tokenizer pour découper une chaîne de caractères selon des
séparateurs par défaut, ou selon une liste de séparateurs bien précis :

#include <iostream>
#include <boost/tokenizer.hpp>

// découpe la chaine avec les séparateurs par défaut


void split( const std::string & Msg )
{
// utiliser le tokenizer par défaut
boost::tokenizer<> tok( Msg );

// itérer la séquence de tokens


for ( boost::tokenizer<>::const_iterator i = tok.begin();
i != tok.end();
++i )
{
// afficher chaque token extrait
std::cout << *i << '\n';
}
}

// découpe la chaine selon les séparateurs donnés


void split( const std::string & Msg, const std::string & Separators )
{
// typedef pour alléger l'écriture
typedef boost::tokenizer<boost::char_separator<char> > my_tok;

// séparateur personnalisé
boost::char_separator<char> sep( Separators.c_str() );

// construire le tokenizer personnalisé


my_tok tok( Msg, sep );

// itérer la séquence de tokens


for ( my_tok::const_iterator i = tok.begin();
i != tok.end();
++i )
{
// afficher chaque token extrait
std::cout << *i << '\n';
}
}

int main()
{

- 253 -
Les sources présentées sur cette pages sont libres de droits, et vous pouvez les utiliser à votre convenance. Par contre la page de présentation de ces sources
constitue une oeuvre intellectuelle protégée par les droits d'auteurs. Copyright © 2008 Developpez LLC. Tout droits réservés Developpez LLC. Aucune
reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents et images sans l'autorisation expresse de Developpez
LLC. Sinon vous encourez selon la loi jusqu'à 3 ans de prison et jusqu'à 300 000 E de dommages et intérêts.
http://cpp.developpez.com/faq/cpp/
std::cout << "-- exemple 1 --\n";

split( "mot1;mot2; ;mot3;;mot4;mot5;" );

std::cout << "-- exemple 2 --\n";

split( "mot-compose1;mot,compose2;[mot][compose3];mot compose4;<mot><compose><5>", ";" );


}

Ce programme produit le résultat suivant :

-- exemple 1 --
mot1
mot2
mot3
mot4
mot5
-- exemple 2 --
mot-compose1
mot,compose2
[mot][compose3]
mot compose4
<mot><compose><5>

Notez que les token vides (";;" par exemple) ne sont pas pris en compte.

[Exemple] Comment détruire les pointeurs d'un conteneur en utilisant Boost ?


Auteurs : Aurélien Regat-Barrel ,
La question Comment supprimer correctement des éléments d'un conteneur ? illustre comment supprimer les pointeurs
d'un conteneur au moyen de std::for_each et d'un foncteur fait sur mesure. Voici deux autres possibilités équivalentes
utilisant Boost, afin de vous faire une idée de ses possibilités.

La première combine std::for_each avec un foncteur de Boost : boost::checked_deleter, et la seconde utilise


Boost.Foreach :

Utilisation de checked_deleter
#include <list>
#include <algorithm>
#include <boost/checked_delete.hpp>

int main()
{
// Création d'une liste de pointeurs
std::list<int*> l;
l.push_back(new int(5));
l.push_back(new int(0));
l.push_back(new int(1));
l.push_back(new int(6));

// Destruction de la liste : attention il faut bien libérer les pointeurs avant la liste !
std::for_each(l.begin(), l.end(), boost::checked_deleter<int>());

return 0;
}

Utilisation de BOOST_FOREACH
#include <list>

- 254 -
Les sources présentées sur cette pages sont libres de droits, et vous pouvez les utiliser à votre convenance. Par contre la page de présentation de ces sources
constitue une oeuvre intellectuelle protégée par les droits d'auteurs. Copyright © 2008 Developpez LLC. Tout droits réservés Developpez LLC. Aucune
reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents et images sans l'autorisation expresse de Developpez
LLC. Sinon vous encourez selon la loi jusqu'à 3 ans de prison et jusqu'à 300 000 E de dommages et intérêts.
http://cpp.developpez.com/faq/cpp/
Utilisation de BOOST_FOREACH
#include <algorithm>
#include <boost/foreach.hpp>

int main()
{
// Création d'une liste de pointeurs
std::list<int*> l;
l.push_back(new int(5));
l.push_back(new int(0));
l.push_back(new int(1));
l.push_back(new int(6));

// Destruction de la liste : attention il faut bien libérer les pointeurs avant la liste !
BOOST_FOREACH( int *pi, l )
{
delete pi;
}

return 0;
}

Qu'est-ce que Boost.Variant ?


Auteurs : Alp Mestan ,
En programmation, dans certains langages, on a ce que l'on appelle les types somme. Il s'agit en fait de décomposer un
type T en plusieurs sous-types T1, T2, ..., TN. Une instance de T peut être obtenue par une valeur de type T1 ou T2 ou
T3, mais pas deux types à la fois. Cela correspond vaguement aux unions présentes en C et en C++.
Par exemple, si vous réalisez un interpréteur d'expressions mathématiques du type '1+2-4', alors vous construirez
généralement un arbre d'expressions, une expression étant soit un nombre, soit une opération (+, -, ...) mettant en
relation deux nombres, qui elle-même résultera en un nombre. Toutefois une expression ne peut pas être à la fois un
nombre et une opération mettant en relation deux expressions.
Dans les deux cas, nous décomposons notre type 'expression' qui peut-être vu comme une union disjointe de deux
types. C'est ce à quoi sert le type union en C et C++, toutefois il ne permet pas de gérer des classes dès qu'elles ont un
constructeur par exemple.
En C++, un telle décomposition est rendue possible (bien que moins puissante et absolument pas intégrée au langage
lui-même) grâce à Boost.Variant. En effet, nous pouvons définir un type C++ qui représente également l'union disjointe
de deux ensembles.

class A { };
class B { };
class C {};
class D {};

boost::variant<A,B,C,D,std::string,int> v;
v = A(); // v contient une valeur de type A
v = B(); // v contient une valeur de type B
v = C(); // v contient une valeur de type C
v = D(); // v contient une valeur de type D
v = "Salut"; // v contient une valeur de type std::string
v = 42; // v contient une valeur de type int

Un début de piste pour notre interpréteur d'expressions arithmétiques serait :

struct Op
{
enum op_type { ADD, SUB };

- 255 -
Les sources présentées sur cette pages sont libres de droits, et vous pouvez les utiliser à votre convenance. Par contre la page de présentation de ces sources
constitue une oeuvre intellectuelle protégée par les droits d'auteurs. Copyright © 2008 Developpez LLC. Tout droits réservés Developpez LLC. Aucune
reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents et images sans l'autorisation expresse de Developpez
LLC. Sinon vous encourez selon la loi jusqu'à 3 ans de prison et jusqu'à 300 000 E de dommages et intérêts.
http://cpp.developpez.com/faq/cpp/
double e1, e2;
op_type op;
};

double compute_op(const Op& o)


{
switch(o.op)
{
case Op::ADD:
return o.e1 + o.e2; break;
case Op::SUB:
return o.e1 - o.e2; break;
}
}

typedef boost::variant<double, Op> expression;

double compute_expression(const expression& e)


{
if( double* d = boost::get<double>(&e) )
{
return d;
}
Op* o = boost::get<Op>(&e);
return compute_op(*o);
}

lien : Comment récupérer la valeur contenue dans un boost::variant ?


lien : Qu'est-ce que boost::any et boost::variant et quand les utiliser ?

Comment récupérer la valeur contenue dans un boost::variant ?


Auteurs : Alp Mestan ,
La fonction template boost::get, définie dans <boost/variant/get.hpp>, est un premier moyen de récupérer la valeur d'un
boost::variant. Il en existe 4 versions :

template<typename U, typename T1, typename T2, ..., typename TN>


U * get(variant<T1, T2, ..., TN> * operand); // (1)
template<typename U, typename T1, typename T2, ..., typename TN>
const U * get(const variant<T1, T2, ..., TN> * operand); // (2)
template<typename U, typename T1, typename T2, ..., typename TN>
U & get(variant<T1, T2, ..., TN> & operand); // (3)
template<typename U, typename T1, typename T2, ..., typename TN>
const U & get(const variant<T1, T2, ..., TN> & operand); // (4)

• La première version travaillera sur un pointeur vers boost::variant pour vous retourner un pointeur vers la
valeur voulue.
• La seconde version travaillera sur un pointeur vers un boost::variant constant pour vous retourner un pointeur
vers la valeur constante voulue.
• La troisième version travaillera sur une référence vers un boost::variant pour vous retourner une référence sur
la valeur voulue.
• La quatrième version travaillera sur une référence sur un boost::variant constant pour vous retourner une
référence sur la valeur constante voulue.

Dans le cas de (1) et (2), si le get échoue (si votre variant contient un int et que vous appelez get<string>(v) par exemple),
alors la fonction vous retourne un pointeur nul.

- 256 -
Les sources présentées sur cette pages sont libres de droits, et vous pouvez les utiliser à votre convenance. Par contre la page de présentation de ces sources
constitue une oeuvre intellectuelle protégée par les droits d'auteurs. Copyright © 2008 Developpez LLC. Tout droits réservés Developpez LLC. Aucune
reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents et images sans l'autorisation expresse de Developpez
LLC. Sinon vous encourez selon la loi jusqu'à 3 ans de prison et jusqu'à 300 000 E de dommages et intérêts.
http://cpp.developpez.com/faq/cpp/
Dans le cas de (3) et (4), si le get échoue, la fonction lance une exception bad_get (qui dérive de std::exception et définit
donc la fonction what() décrivant ce qu'il s'est passé).
De manière générale, la fonction get échouera (retournera un pointeur nul pour (1) et (2), lancera une exception bad_get
pour (3) et (4)) si la valeur courante contenue dans votre boost::variant n'est pas du type demandé explicitement avec get.
Pour terminer, un petit exemple d'utilisation :

#include <iostream>
#include <boost/variant.hpp>

int main()
{
boost::variant<int, std::string> v;
v = 42;

int* i = boost::get<int>(&v); // l'entier pointé par i vaut 42.


assert(*i == 42);
*i = 84; // cela a également modifié la valeur contenue dans v
std::string* s = boost::get<std::string>(&v);
assert(s == NULL); // s vaut effectivement le pointeur nul

int& i2 = boost::get<int>(v);
assert(i2 == 84);
try
{
std::string& s = boost::get<std::string>(v);
}
catch (std::exception& e)
{
std::cout << "Exception ! " << e.what() << std::endl;
}
return 0;
}

Ce code provoquera donc l'affichage suivant : Exception ! boost::bad_get : failed value get using boost::get.

Qu'est-ce que boost::any et boost::variant et quand les utiliser ?


Auteurs : Herb Sutter ,
Il y deux moyens pour simuler un typage faible en C++. On parle bien de simulation, le langage reste typé statiquement.
Le premier d'entre eux est Boost variant :

boost::variant< int, string >


x; // déclare une variable de type boost::variant en précisant les types autorisés.
x = 42; //x contient un entier
x = "hello, world"; // x contient une chaine de caractères
x = new Widget(); //erreur, x ne peut pas contenir un Widget.

Contrairement à une union, boost::variant peut inclure n'importe quel type, mais vous devez spécifier quels types sont
autorisés.
Vous pouvez même simuler un comportement Qu'est-ce que le polymorphisme ? (surcharge de fonctions) avec
boost::apply_visitor qui sera en plus vérifié à la compilation.
L'autre moyen est boost::any :

boost::any x;

- 257 -
Les sources présentées sur cette pages sont libres de droits, et vous pouvez les utiliser à votre convenance. Par contre la page de présentation de ces sources
constitue une oeuvre intellectuelle protégée par les droits d'auteurs. Copyright © 2008 Developpez LLC. Tout droits réservés Developpez LLC. Aucune
reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents et images sans l'autorisation expresse de Developpez
LLC. Sinon vous encourez selon la loi jusqu'à 3 ans de prison et jusqu'à 300 000 E de dommages et intérêts.
http://cpp.developpez.com/faq/cpp/
x = 42; // x contient un entier
x = "hello, world"; //x contient une chaine de caractères
x = new Widget(); // x contient un widget, pas d'erreur

Contrairement aux unions, boost::any accepte n'importe quel type. A l'inverse de boost::variant, boost::any ne
vous laisse pas préciser les types autorisés, ce qui peut être une bonne ou mauvaise chose en fonction du degré de
laxisme souhaité. Aussi, vous n'avez aucun moyen de simuler une surcharge de fonctions et l'objet doit être alloué
dynamiquement (sur le tas).
De façon intéressante, ceci montre comme le C++ suit de façon ferme et efficace un schéma de typage statique quand
c'est possible et dynamique quand c'est nécessaire.
Quand avez vous besoin de quoi ?
Utilisez boost::variant quand vous voulez :

• un objet capable de stocker les valeurs d'un nombre fini de types ;


• une vérification à la compilation du type visité ;
• une allocation efficace, qui se trouve sur la pile ;
• et vous pouvez vivre avec d'horribles messages d'erreur quand le type attribué n'est pas le bon.

Utilisez boost::any quand vous voulez :

• la flexibilité offerte par un objet capable de stocker virtuellement n'importe quel type ;
• la flexibilité offerte par any_cast ;
• la garantie qu'il n'y aura pas d'exceptions lancées durant un swap.

- 258 -
Les sources présentées sur cette pages sont libres de droits, et vous pouvez les utiliser à votre convenance. Par contre la page de présentation de ces sources
constitue une oeuvre intellectuelle protégée par les droits d'auteurs. Copyright © 2008 Developpez LLC. Tout droits réservés Developpez LLC. Aucune
reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents et images sans l'autorisation expresse de Developpez
LLC. Sinon vous encourez selon la loi jusqu'à 3 ans de prison et jusqu'à 300 000 E de dommages et intérêts.
http://cpp.developpez.com/faq/cpp/
Sommaire > Système
Comment charger explicitement une bibliothèque dynamique ?
Auteurs : Laurent Gomila ,
Il arrive que l'on veuille charger une bibliothèque dynamique (.dll, .so) explicitement depuis le code C++, que ce soit
pour implémenter un système de plugins ou tout simplement car on ne dispose pas des fichiers de lien statique (.lib, .a).
Cette manipulation est spécifique à chaque système, mais on peut cependant remarquer que les principes et les fonctions
mis en jeu sont pratiquement équivalents, au nom près.
En l'occurrence, cela se fait en trois étapes :

• Charger la bibliothèque dynamique


• Récupérer les fonctions qu'elle exporte
• Décharger la bibliothèque dynamique

Voici un code qui met en oeuvre ce procédé, avec de simples macros pour prendre en charge plusieurs systèmes
(Windows et Linux) et ainsi obtenir un code plus ou moins portable :

// Version pour Windows


#if defined(_WIN32) || defined(__WIN32__)
#include <windows.h>
#define DYNLIB_HANDLE HMODULE
#define DYNLIB_LOAD( a ) LoadLibrary( a )
#define DYNLIB_GETSYM( a, b ) GetProcAddress( a, b )
#define DYNLIB_UNLOAD( a ) !FreeLibrary( a )
#define DYNLIB_ERROR( ) "Unknown Error"

// Version pour Linux


#elif defined(linux) || defined(__linux)
#include <dlfcn.h>
#define DYNLIB_HANDLE void*
#define DYNLIB_LOAD( a ) dlopen( a, RTLD_LAZY )
#define DYNLIB_GETSYM( a, b ) dlsym( a, b )
#define DYNLIB_UNLOAD( a ) dlclose( a )
#define DYNLIB_ERROR( ) dlerror( )
#endif

#include <iostream>

int main()
{
// Chargement de la bibliothèque
DYNLIB_HANDLE Lib = DYNLIB_LOAD("library");
if (!Lib)
{
std::cerr << DYNLIB_ERROR() << std::endl;
return EXIT_FAILURE;
}

// Importation de la fonction qui nous intéresse


typedef int (*FuncType)(float);
FuncType Func = static_cast<FuncType>(DYNLIB_GETSYM(Lib, "Function"));
if (!Func)
{
std::cerr << DYNLIB_ERROR() << std::endl;
return EXIT_FAILURE;
}

// Appel de la fonction importée


int x = Func(5.f);

// Déchargement de la bibliothèque

- 259 -
Les sources présentées sur cette pages sont libres de droits, et vous pouvez les utiliser à votre convenance. Par contre la page de présentation de ces sources
constitue une oeuvre intellectuelle protégée par les droits d'auteurs. Copyright © 2008 Developpez LLC. Tout droits réservés Developpez LLC. Aucune
reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents et images sans l'autorisation expresse de Developpez
LLC. Sinon vous encourez selon la loi jusqu'à 3 ans de prison et jusqu'à 300 000 E de dommages et intérêts.
http://cpp.developpez.com/faq/cpp/
if (DYNLIB_UNLOAD(Lib))
{
std::cerr << DYNLIB_ERROR() << std::endl;
return EXIT_FAILURE;
}

return EXIT_SUCCESS;
}

La plupart des bibliothèques graphiques encapsulent ces fonctions, comme par exemple wxWidgets
(wxDynamicLibrary), Qt (QLibrary), SDL (SDL_LoadObject), etc.

Pourquoi la lecture de mon fichier ne fonctionne-t-elle plus après une lecture complète ?
Auteurs : Laurent Gomila ,
Une fois la fin de fichier atteinte par une première lecture, le flux se met dans un état invalide, ce qui empêche la réussite
de toute lecture ultérieure.

Afin de pouvoir lire à nouveau le même flux, il faut donc :

• le remettre dans un état valide (avec la fonction clear) ;


• le rembobiner (avec la fonction seekg).

#include <fstream>
#include <iostream>
#include <limits>

int CountLines(std::ifstream& File)


{
int Count = 0;
while (File.ignore(std::numeric_limits<int>::max(), '\n'))
++Count;

return Count;
}

int main()
{
std::ifstream File("fichier.txt");

std::cout << CountLines(File) << std::endl;

File.clear();
File.seekg(0, std::ios::beg);

std::cout << CountLines(File) << std::endl;

return 0;
}

- 260 -
Les sources présentées sur cette pages sont libres de droits, et vous pouvez les utiliser à votre convenance. Par contre la page de présentation de ces sources
constitue une oeuvre intellectuelle protégée par les droits d'auteurs. Copyright © 2008 Developpez LLC. Tout droits réservés Developpez LLC. Aucune
reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents et images sans l'autorisation expresse de Developpez
LLC. Sinon vous encourez selon la loi jusqu'à 3 ans de prison et jusqu'à 300 000 E de dommages et intérêts.
http://cpp.developpez.com/faq/cpp/
Sommaire > Divers
Comment exécuter une commande système ou un autre programme ?
Auteurs : Aurélien Regat-Barrel ,
La fonction standard std::system() définie dans <cstdlib> permet d'exécuter la commande qui lui est fournie en
paramètre au moyen de l'interpréteur de commande du système d'exploitation. Son comportement est donc spécifique
à chaque OS.

#include <cstdlib>

int main()
{
// exécute la commande système "dir"
// le texte affiché dépend de l'OS
std::system( "dir" );
// exécute le programme "toto.exe"
std::system( "toto.exe" );
}

L'appel à cette fonction ne rend pas la main tant que la commande n'a pas terminé son exécution. Si vous souhaitez un
comportement différent (que la fonction rende la main immédiatement, qu'une console ne soit pas créée, rediriger les
flux standard, ...), il faut vous tourner vers une solution spécifique à votre système d'exploitation.
Voir les fonctions CreateProcess / ShellExecute[Ex] sous Windows, et les fonctions de type exec*() et spawn() définies
dans les headers <unistd.h> / <process.h> pour les systèmes UNIX/POSIX (Notez qu'elles sont aussi disponibles avec
un underscore préfixé à leur nom ( _exec et _spawn sous Visual C++)

Qu'est-ce que C++0x ?


Auteurs : Aurélien Regat-Barrel , Laurent Gomila ,
C++0x est le nom de code de la prochaine version de l'actuel standard ISO C++ 98, qui porte le nom de C++98. Cette
future norme devrait être ratifiée d'ici 2009, et le 'x' dans son nom de code sera alors remplacé par l'année en cours
(par exemple, C++09 si cela a lieu en 2009).

Faire évoluer un langage tel que le C++ est une tâche complexe qui nécessite du temps. Conscient que C++0x ne serait
pas ratifié avant la fin de la décénie, le commité de normalisation ISO a entrepris et achevé en 2005 la rédaction d'un
document intermédiaire limité au développement d'extensions à la bibliothèque standard du C++. Vous pouvez lire à
ce sujet Qu'est-ce que le Library Technical Report (tr1 / tr2) ?.

Voici quelques liens vers des documents sur le sujet :


Possible Directions for C++0x.

The Design of C++0x.

Une interview TechNetCast de Bjarne Stroustrup.

- 261 -
Les sources présentées sur cette pages sont libres de droits, et vous pouvez les utiliser à votre convenance. Par contre la page de présentation de ces sources
constitue une oeuvre intellectuelle protégée par les droits d'auteurs. Copyright © 2008 Developpez LLC. Tout droits réservés Developpez LLC. Aucune
reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents et images sans l'autorisation expresse de Developpez
LLC. Sinon vous encourez selon la loi jusqu'à 3 ans de prison et jusqu'à 300 000 E de dommages et intérêts.
http://cpp.developpez.com/faq/cpp/
On peut également trouver des brouillons du prochain standard à cette adresse : C++ Standards Committee Papers.

Peut-on tester C++0x ?


Auteurs : Alp Mestan ,
Il est déjà possible de tester quelques nouvelles fonctionnalités de C++0x grâce au travail énorme fourni par les équipes
de développement de 2 compilateurs majeurs : Microsoft Visual C++ et g++.

En effet, depuis quelques années, nous avons pu voir le compilateur g++ implémenter des fonctionnalités de C++0x. En
ce qui concerne les concepts de C++0x, ils sont dans une grande mesure utilisables par le biais de ConceptGCC.
Parallèlement, g++ intègre peu à peu des fonctionnalités du monde C++0x au fil de ses versions 4.x et l'on peut suivre
ce développement sur le site officiel de gcc/g++.

Enfin, Visual C++ commence également à intégrer des fonctionnalités de C++0x. En effet, Visual C++ 2010
Community Technology Preview propose déjà l'utilisation des fonctionnalités suivantes :

• lambda Expressions ;
• rvalue References ;
• static_assert ;
• mot clé auto, mais dans sa nouvelle version (il fait partie des fonctionnalités relatives à l'inférence de type en C+
+).

lien : Qu'est-ce que C++0x ?

Qu'est-ce que le Library Technical Report (tr1 / tr2) ?


Auteurs : Aurélien Regat-Barrel ,
Le Technical Report on C++ Library Extensions est le premier Technical Report (TR1) consacré à la bibliothèque
standard du C++. Il a été réalisé en 2005, et constitue une sorte de brouillon officiel décrivant un ensemble d'extensions
à la bibliothèque standard actuelle suceptibles d'être adoptées dans Qu'est-ce que C++0x ?. Un deuxième document du
même type est actuellement à l'étude (TR2).

Un Technical Report n'est pas normatif, c'est-à-dire qu'il ne fait pas officiellement partie de la norme, et ne garantit pas
que ce sera le cas dans le futur. Les distributeurs de compilateurs ne sont pas obligés non plus de développer et fournir
ces extensions. Un autre élément important est que les extensions proposées s'appuient sur les capacités actuelles du
langage C++, et non sur les futures possibilités de C++0x.

Cependant, un TR (Technical Report) préfigure fortement ce que sera la prochaine norme C++, et l'objectif d'un tel
document est double :

• fournir dès maintenant aux programmeurs C++ de nouvelles bibliothèques préstandards ;


• obtenir des retours d'expériences sur ces ajouts afin d'y apporter des améliorations avant leur normalisation
définitive.

Au niveau pratique, toutes les nouveautés introduites par le TR1 le sont au sein d'un nouveau namespace tr1 membre
du namespace standard std. De nombreux éléments sont directement issus de la Qu'est-ce que Boost ?. On retrouve par
exemple les fameux shared_ptr (voir Comment utiliser les pointeurs intelligents de Boost ?), qui ont été intégrés aux
côtés de std::auto_ptr dans le fichier d'en-tête <memory> (voir Pourquoi faut-il se méfier de std::auto_ptr ?):

- 262 -
Les sources présentées sur cette pages sont libres de droits, et vous pouvez les utiliser à votre convenance. Par contre la page de présentation de ces sources
constitue une oeuvre intellectuelle protégée par les droits d'auteurs. Copyright © 2008 Developpez LLC. Tout droits réservés Developpez LLC. Aucune
reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents et images sans l'autorisation expresse de Developpez
LLC. Sinon vous encourez selon la loi jusqu'à 3 ans de prison et jusqu'à 300 000 E de dommages et intérêts.
http://cpp.developpez.com/faq/cpp/
#include <memory>

int main()
{
std::tr1::shared_ptr<int> p( new int );
}

Les principales extensions issues de boost et incluses dans le TR1 sont :

• enrichissement de <memory> avec des pointeurs intelligents (shared_ptr, weak_ptr, ...) ;


• enrichissement de <functional> (bind, ref, reference_wrapper, mem_fn, result_of, ...) ;
• enrichissement de <utility> (tuple_size, tuple_element, ...) ;
• ajout de <tuple> (collections d'éléments : paires, triples, quadruples, ...) ;
• ajout de <type_traits> (aide à la méta-programmation) ;
• ajout de <random> (génération de nombres aléatoires) ;
• ajout de <array> (tableau de taille fixe) ;
• ajout de <unordered_set> et de <unordered_map> (tables de hachage) ;
• ajout de <regex> (expressions régulières).

Pour plus de détails, vous pouvez aussi lire Library Technical Report.

Mais le TR1 ne se résume pas à la récupération de bibliothèques de Boost. Il y a aussi de nombreuses nouveautés issues
du C99 et un enrichissement important des fonctions mathématiques.

Plusieurs compilateurs et développeurs de bibliothèque standard se sont lancés dans leur propre implémentation du
TR1. C'est le cas de GCC et de CodeWarrior par exemple, ainsi que de Dinkumware qui est le premier a l'avoir
implémenté à 100% (Dinkumware est aussi le fournisseur de la STL de VC++, ce qui laisse penser que ce dernier
supportera lui aussi le TR1).

Les développeurs de Boost se sont bien sûr eux aussi atelés à la tâche, et Boost.TR1 est actuellement en cours de
développement.

Quelles sont les questions à poser pour savoir si un candidat connaît vraiment son sujet ?
Auteurs : Marshall Cline ,
Cette question se destine tout d'abord aux responsables non techniques et au personnel des ressources humaines qui
essaient de faire un travail de bonne qualité quand ils interviewent des candidats développeurs C++. Si vous êtes un
programmeur C++ sur le point d'être interviewé, et que vous consultez cette FAQ en espérant savoir à l'avance quelles
questions vont vous être posées de façon à ne pas devoir apprendre réellement le C++, honte à vous. Prenez le temps de
devenir un développeur compétent et vous n'aurez pas besoin d'essayer de tricher.

Pour revenir aux non techniciens et au personnel des ressources humaines : il est évident que vous êtes parfaitement
qualifiés pour juger si un candidat a un profil correspondant à la culture de votre entreprise. Cependant, il y a assez de
charlatans, de menteurs et de frimeurs pour que vous ayez besoin de faire équipe avec quelqu'un qui est techniquement
compétent pour s'assurer que le candidat ait le niveau technique adéquat. Assez de sociétés ont souffert d'avoir engagé
des gens sympathiques mais incompétents, des gens globalement incompétents malgré le fait qu'ils connaissent les
réponses à quelques questions très pointues. La seule façon de démasquer les menteurs et les frimeurs est d'avoir à
vos cotés une personne capable de poser des questions techniques pointues. Il n'y a aucun espoir que vous y arriviez
par vous-même. Même si je vous donnais une série de questions pièges, elles ne vous permettraient pas de démasquer
ces personnes.

- 263 -
Les sources présentées sur cette pages sont libres de droits, et vous pouvez les utiliser à votre convenance. Par contre la page de présentation de ces sources
constitue une oeuvre intellectuelle protégée par les droits d'auteurs. Copyright © 2008 Developpez LLC. Tout droits réservés Developpez LLC. Aucune
reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents et images sans l'autorisation expresse de Developpez
LLC. Sinon vous encourez selon la loi jusqu'à 3 ans de prison et jusqu'à 300 000 E de dommages et intérêts.
http://cpp.developpez.com/faq/cpp/
Votre partenaire technique n'a pas besoin d'être qualifié (et bien souvent, il ne l'est pas) pour juger la personnalité du
candidat, donc, par pitié, n'oubliez pas que vous êtes le seul juge au final. Mais ne pensez pas que vous pouvez poser
une demi-douzaine de questions sur le C++ et avoir la moindre chance de savoir si le candidat sait de quoi il parle, du
moins d'un point de vue technique.

Par contre, si vous avez un niveau technique suffisant pour lire cette FAQ, vous pouvez y piocher quantité de bonnes
questions pour une interview. La FAQ contient bon nombre de choses permettant de séparer le bon grain de l'ivraie.
La FAQ se concentre sur ce que les programmeurs doivent faire, plutôt que d'essayer de voir ce que le compilateur
leur laisse faire. Il y a des choses en C++ qu'il est possible de faire mais qu'il vaut mieux éviter. La FAQ sert à faire
le tri là-dedans.

Comment accélérer la compilation de mes projets ?


Auteurs : Laurent Gomila ,
Voici quelques astuces pour réduire les temps de compilation, qui peuvent parfois se compter en minutes voire en heures
sur des projets de taille conséquente.

1/ Utiliser les déclarations anticipées

Si dans un en-tête vous ne déclarez qu'un pointeur ou une référence sur une classe, alors vous n'avez pas besoin d'inclure
son en-tête : une déclaration anticipée suffit.

MaClasse.h

class Classe1; // pas d'inclusion de "Classe1.h"


class Classe2; // pas d'inclusion de "Classe2.h"

class MaClasse
{
void Something(const Classe1& c);

Classe2* c2;
};

MaClasse.cpp

#include "Classe1.h"
#include "Classe2.h"

void MaClasse::Something(const Classe1& c)


{
// Ici on peut accéder aux membres de Classe1 et Classe2,
// on a inclus leurs en-têtes
}

Ainsi lorsque Classe1 ou Classe2 sera modifiée, seul MaClasse.cpp sera recompilé. Sans cette astuce, vous auriez dû
recompiler (inutilement) tous les fichiers incluant MaClasse.h. L'élimination des dépendances inutiles est essentielle
pour améliorer les temps de compilation.

2/ Utiliser l'idiome enveloppe-lettre, appelé aussi Pimpl (Private Implementation)

Le principe de cet idiome est de séparer l'interface publique d'une classe de son implémentation.

- 264 -
Les sources présentées sur cette pages sont libres de droits, et vous pouvez les utiliser à votre convenance. Par contre la page de présentation de ces sources
constitue une oeuvre intellectuelle protégée par les droits d'auteurs. Copyright © 2008 Developpez LLC. Tout droits réservés Developpez LLC. Aucune
reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents et images sans l'autorisation expresse de Developpez
LLC. Sinon vous encourez selon la loi jusqu'à 3 ans de prison et jusqu'à 300 000 E de dommages et intérêts.
http://cpp.developpez.com/faq/cpp/
MaClasse.h (interface)

class MaClasseImpl;

class MaClasse
{
public :

void Set(int);
int Get() const;

private :

MaClasseImpl* pImpl;
};

MaClasse.cpp

#include "MaClasseImpl.h"

void MaClasse::Set(int x)
{
pImpl->Set(x);
}

int MaClasse::Get() const


{
return pImpl->Get();
}

MaClasseImpl.h (implémentation)

class MaClasseImpl
{
public :

void Set(int);
int Get() const;

private :

int val;
};

MaClasseImpl.cpp

#include "MaClasseImpl.h"

void MaClasseImpl::Set(int x)
{
val = x;
}

int MaClasseImpl::Get() const


{
return val;
}

- 265 -
Les sources présentées sur cette pages sont libres de droits, et vous pouvez les utiliser à votre convenance. Par contre la page de présentation de ces sources
constitue une oeuvre intellectuelle protégée par les droits d'auteurs. Copyright © 2008 Developpez LLC. Tout droits réservés Developpez LLC. Aucune
reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents et images sans l'autorisation expresse de Developpez
LLC. Sinon vous encourez selon la loi jusqu'à 3 ans de prison et jusqu'à 300 000 E de dommages et intérêts.
http://cpp.developpez.com/faq/cpp/
Ainsi, seule une modification de l'interface de MaClasse entraînera une recompilation des fichiers qui en dépendent. Un
changement dans son implémentation n'entraînera la recompilation que de deux fichiers au maximum : MaClasse.cpp
et MaClasseImpl.cpp.
Comme vous le voyez dans cet exemple, MaClasse est ici limitée à son interface publique, toutes ses données privées et
protégées sont cachées dans MaClasseImpl. C'est un autre avantage de ce procédé : en plus de réduire les dépendances
et d'accélérer la compilation, l'idiome pimpl permet de ne rendre visible pour les clients d'une classe que son interface
publique.

Pour plus de détails sur l'idiome pimpl, nous vous invitons à consulter ces liens tirés de GOTW :
http://www.gotw.ca/gotw/024.htm
http://www.gotw.ca/gotw/028.htm

3/ Utiliser les en-têtes précompilés

En plus des techniques citées ci-dessus, certains environnements de développement comme VC++ et BCB proposent
d'utiliser un en-tête précompilé. Pour en tirer partie il faut :

• Activer son utilisation dans les options du projet.


• Inclure dans l'en-tête précompilé (par défaut, stdafx.h sous VC++) tous les en-têtes que vous ne modifierez pas
ou peu souvent (en-têtes standards, de bibliothèques externes, ...).
• Inclure l'en-tête précompilé dans toutes les unités de traduction (les .cpp) du projet.

Attention, si vous y incluez un en-tête subissant des modifications fréquentes cela aura l'effet inverse : à chaque
modification de celui-ci vous aurez droit à une recompilation complète.

Mes calculs sur nombres flottants sont imprécis, que faire ?


Auteurs : Laurent Gomila , Jean-Marc.Bourguet ,
C'est tout à fait normal. Cela est dû au codage interne des flottants en mémoire (selon la norme IEEE 754). Ces
imprécisions deviennent d'autant plus gênantes qu'elles s'accumulent en même temps que les opérations que vous
effectuez sur vos nombres ; ainsi il est tout à fait possible d'obtenir des nombres très éloignés des résultats théoriques.

Il n'existe pas de solution miracle, cependant vous pouvez tout de même trouver un compromis selon vos besoins :

-> Vous avez besoin d'une précision exacte (gestion de comptes ou autres applications monétaires) : utilisez plutôt ce
que l'on appelle les nombres en virgule fixe, qui, généralement codés sous forme d'entiers, ne souffrent pas de ces
imprécisions. Le C++ ne dispose pas en standard de types en virgule fixe, mais vous pourrez facilement trouver des
classes ou des bibliothèques toutes faites, voire construire la vôtre.

-> Vous voulez tenir compte des imprécisions dans vos calculs : utilisez un epsilon (nombre très petit) lors de vos tests
de comparaison. Typiquement, l'espilon choisi dépend de votre application et des grandeurs manipulées. Un point
de départ est l'espilon défini dans la bibliothèque standard (std::numeric_limits<float>::epsilon() pour les float par
exemple), qui définit le plus petit flottant tel que 1 + espilon > 1. Ainsi vos comparaisons deviennent :

#include <cmath> // pour std::abs


#include <limits> // pour std::numeric_limits

using namespace std;

float f1 = 0.1f;

- 266 -
Les sources présentées sur cette pages sont libres de droits, et vous pouvez les utiliser à votre convenance. Par contre la page de présentation de ces sources
constitue une oeuvre intellectuelle protégée par les droits d'auteurs. Copyright © 2008 Developpez LLC. Tout droits réservés Developpez LLC. Aucune
reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents et images sans l'autorisation expresse de Developpez
LLC. Sinon vous encourez selon la loi jusqu'à 3 ans de prison et jusqu'à 300 000 E de dommages et intérêts.
http://cpp.developpez.com/faq/cpp/
float f2 = 1.1f;
float f3 = f2 - f1;

// Version incorrecte ne tenant pas compte des imprécisions


if (f3 == 1.0f)
{
// Pratiquement jamais vrai !
}

// Version correcte tenant compte des imprécisions


// err est une variable choisie en fonction de l'erreur autorisée
if (abs(f3 - 1.0f) <= err * max(abs(f3), abs(1.0f)) * numeric_limits<float>::epsilon())
{
// Ok
}

Comment connaître les macros prédéfinies pour les différentes plateformes ?


Auteurs : Laurent Gomila ,
Lorsque l'on veut écrire du code portable, il est parfois nécessaire d'utiliser la compilation conditionnelle, c'est-à-dire
compiler tel ou tel code selon la plateforme sur laquelle on se trouve.

Par exemple, voici un code qui sera capable de compiler correctement à la fois sous Windows et sous Unix :

#if defined (linux)


// Code spécifique à Linux
#elif defined (_WIN32)
// Code spécifique à Windows
#else
// Plateforme non gérée, on peut par exemple placer une erreur de compilation
#error Plateforme non gérée
#endif

Cependant, connaître toutes les macros prédéfinies pour chaque OS, compilateur, ... est très difficile.

La solution la plus rapide est d'aller visiter le site Pre-defined C/C++ Compiler Macros, qui maintient une liste à jour
des macros prédéfinies pour les choses suivantes :

• Standard.
• Compilateurs.
• Systèmes d'exploitation.
• Bibliothèques.
• Architectures.

Une bonne habitude est également d'aller regarder les en-têtes de bibliothèques portables (par exemple boost). Ceux-ci
sont truffés de conditions sur les plateformes, et vous donneront de bonnes informations.

Quels sont les identificateurs interdits par la norme ?


Auteurs : Laurent Gomila , JolyLoic , Mat007 ,
En plus de ses mots-clé, le C++ réserve certains identificateurs, notamment pour l'implémentation de la bibliothèque
standard ou pour les ajouts propriétaires que l'on peut trouver sur la plupart des compilateurs. Cela assure qu'il n'y
aura aucun conflit entre votre code et celui livré par le compilateur, qui ne sera pas forcément documenté.

- 267 -
Les sources présentées sur cette pages sont libres de droits, et vous pouvez les utiliser à votre convenance. Par contre la page de présentation de ces sources
constitue une oeuvre intellectuelle protégée par les droits d'auteurs. Copyright © 2008 Developpez LLC. Tout droits réservés Developpez LLC. Aucune
reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents et images sans l'autorisation expresse de Developpez
LLC. Sinon vous encourez selon la loi jusqu'à 3 ans de prison et jusqu'à 300 000 E de dommages et intérêts.
http://cpp.developpez.com/faq/cpp/
En outre, la norme réserve (et donc interdit) les identificateurs suivant :

• Les noms commençant par un _ suivi d'une majuscule.


• Les noms commençant par _ dans le namespace global.
• Les noms contenant __ (deux _ d'affilée).

Une version simplifiée de cette règle est donc de ne pas commencer ses noms par un _ du tout, ni d'utiliser __.

Qu'est-ce qu'un type POD ?


Auteurs : r0d ,
Un type POD (de l'anglais Plain Old Data) est un type C++ qui a un équivalent en C, et qui utilise les mêmes règles que
le C pour l'initialisation, la copie et l'adressage.

La définition précise d'un type POD est récursive et un peu absconse. Voici une définition légèrement simplifiée : les
données membres non statiques d'un type POD doivent être publiques et peuvent être de ce type :

• bool ;
• tous les types numériques, y compris des divers char ;
• enumération ;
• pointeur de données (i.e, tous les types convertibles en void*) ;
• pointeur de fonction (mais pas un pointeur sur une fonction membre) ;
• type POD, y compris un tableau de POD.

Notez que les références ne sont pas permises pour un type POD. De plus, un type POD ne peut avoir ni constructeur,
ni fonction virtuelle, ni classe de base (pas d'héritage), ni surcharge d'opérateur d'assignation.

Dans certaines situations, le C++ permet uniquement l'utilisation de POD. Par exemple, une union ne peut pas contenir
une classe qui a des fonctions virtuelles ou des constructeurs non triviaux. Les PODs peuvent également être utilisés
pour interfacer du code C++ avec du code C.

Que sont les rvalues et lvalues ?


Auteurs : Alp Mestan ,
Une lvalue est, à l'origine, une expression qui peut se trouver du côté gauche lors d'une affectation, c'est-à-dire une
expression à laquelle on peut affecter quelque chose. Désormais, une lvalue désigne toute expression qui fait référence
à un objet, c'est-à-dire toute expression qui fait référence à une région contigüe de mémoire. Cela peut donc être par
exemple un objet constant, ce qui ne rentre pas forcément dans la définition utilisant l'affectation.

Par conséquent, les identifiants (de variables) sont des lvalues. Il en va de même pour les références.

Voici un code qui va peut-être aider à la compréhension :

int n = 5; // n est une lvalue


Client& getClient() { /* ... */ }
getClient() =
UnAutreClient; // getClient() désigne ici une lvalue car la référence renvoyée correspond à la définition d'une

std::string s = "C++"; // s est une lvalue

- 268 -
Les sources présentées sur cette pages sont libres de droits, et vous pouvez les utiliser à votre convenance. Par contre la page de présentation de ces sources
constitue une oeuvre intellectuelle protégée par les droits d'auteurs. Copyright © 2008 Developpez LLC. Tout droits réservés Developpez LLC. Aucune
reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents et images sans l'autorisation expresse de Developpez
LLC. Sinon vous encourez selon la loi jusqu'à 3 ans de prison et jusqu'à 300 000 E de dommages et intérêts.
http://cpp.developpez.com/faq/cpp/
// ...

Une rvalue est, à l'origine, une expression qui peut apparaître du côté droit d'une affectation mais pas du côté gauche.
De façon peut-être plus claire, on définit une rvalue comme une expression qui n'est pas une lvalue.

Les temporaires par exemple sont des rvalues. Les rvalues peuvent être considérées comme des "valeurs" d'expressions
(constantes, résultat d'évaluations d'expressions, ...).

Il est utile de connaître ces notions et savoir les distinguer pour avoir une meilleure maîtrise de son code, savoir ce que
l'on peut modifier, ce que l'on ne peut pas et ainsi donner le droit ou non de modifier des objets lorsque l'on crée des
fonctions notamment.

Qu'est-ce que la RVO ?


Auteurs : Florian Goujeon ,
La RVO (pour Return Value Optimization, optimisation de la valeur de retour) est une optimisation automatique du
code source effectuée par la plupart des compilateurs C++.
Cette optimisation s'applique lors d'un appel de fonction renvoyant un objet dont la copie est couteuse. Elle consiste à
modifier la façon dont la fonction renvoie son objet de retour, de telle façon à ce qu'aucune copie dudit objet ne soit
créée. Il en résulte alors un code potentiellement bien plus rapide.
Prenons comme exemple le code suivant. Celui-ci définit tout d'abord une classe car (voiture), composée de nombreux
objets. Cette classe comprend un constructeur, un constructeur par copie et un destructeur.

class car
{
public:
//constructeur
car
(
const engine& e, //moteur
const wheels& w, //roues
const doors& d, //portières
const steering_wheel& s //volant
//etc...
);

//constructeur par copie


car(const car& c);

//destructeur
~car();

private:
engine engine_;
wheels wheels_;
doors doors_;
steering_wheel steering_wheel_;
//d'autres composants...
};

Les constructeurs et destructeur contiennent une instruction d'écriture sur la sortie standard. Ainsi, nous aurons un
rapport détaillé des différentes opérations effectuées.

car::car
(
const engine& e,
const wheels& w,

- 269 -
Les sources présentées sur cette pages sont libres de droits, et vous pouvez les utiliser à votre convenance. Par contre la page de présentation de ces sources
constitue une oeuvre intellectuelle protégée par les droits d'auteurs. Copyright © 2008 Developpez LLC. Tout droits réservés Developpez LLC. Aucune
reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents et images sans l'autorisation expresse de Developpez
LLC. Sinon vous encourez selon la loi jusqu'à 3 ans de prison et jusqu'à 300 000 E de dommages et intérêts.
http://cpp.developpez.com/faq/cpp/
const doors& d,
const steering_wheel& s
//etc...
):
engine_(e),
wheels_(w),
doors_(d),
steering_wheel_(s)
//etc...
{
std::cout << "Appel au constructeur de car.\n";
}

car::car(const car& s):


engine_(s.engine_),
wheels_(s.wheels_),
doors_(s.doors_),
steering_wheel_(s.steering_wheel_)
//etc...
{
std::cout << "Appel au constructeur par copie de car.\n";
}

car::~car()
{
std::cout << "Appel au destructeur de car.\n";
}

Notre code contient également une fonction build_sport_car(), renvoyant un objet de type car :

const car
build_sport_car()
{
const engine e = build_sport_engine();
const wheels w = build_sport_wheels();
const doors d = build_sport_doors();
const steering_wheel s = build_sport_steering_wheel();
//fabrication des autres composants...

return car(e, w, d, s);


}

Enfin, la fonction main() se contente d'appeler la fonction build_sport_car() :

int
main()
{
car my_car = build_sport_car();

std::cout << "Fin du programme.\n";


return 0;
}

Si l'on désactive la RVO, le programme produit la sortie suivante :

Appel au constructeur de car. (1)


Appel au constructeur par copie de car. (2)
Appel au destructeur de car. (3)
Appel au constructeur par copie de car. (4)
Appel au destructeur de car. (5)
Fin du programme.
Appel au destructeur de car. (6)

- 270 -
Les sources présentées sur cette pages sont libres de droits, et vous pouvez les utiliser à votre convenance. Par contre la page de présentation de ces sources
constitue une oeuvre intellectuelle protégée par les droits d'auteurs. Copyright © 2008 Developpez LLC. Tout droits réservés Developpez LLC. Aucune
reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents et images sans l'autorisation expresse de Developpez
LLC. Sinon vous encourez selon la loi jusqu'à 3 ans de prison et jusqu'à 300 000 E de dommages et intérêts.
http://cpp.developpez.com/faq/cpp/
1 Tout d'abord, la fonction build_sport_car(), appelée depuis main(), crée une instance de car.
2 Le retour de l'objet créé se traduit par sa copie vers un objet temporaire anonyme. Cet objet temporaire sera en
fait la valeur de l'expression « build_sport_car() » située dans la fonction main().
3 Une fois l'objet copié avec succès, la fonction build_sport_car() se termine. Les objets de la pile créés par cette
fonction, comprenant l'instance de car, sont détruits un à un.
4 De retour à la fonction main(), c'est au tour de l'objet temporaire anonyme d'être copié dans l'objet my_car...
5 ... avant d'être détruit à son tour.
6 Enfin, le programme se terminant, l'objet my_car est détruit. Il n'existe alors plus d'instance de car.

Activons maintenant la RVO (il s'agit du réglage par défaut de tous les compilateurs supportant cette optimisation).
Le programme produit une sortie tout à fait différente.

Appel au constructeur de car.


Fin du programme.
Appel au destructeur de car.

Comme dans le cas précédent, la fonction build_sport_car() crée l'instance de car. Mais cette fois-ci, aucun objet
temporaire n'est créé et aucune copie vers l'objet my_car n'est effectuée.
Le compilateur a détecté que l'instance anonyme de voiture créée dans la fonction build_sport_car() devait être copiée
vers l'objet my_car.
Une fois l'optimisation appliquée, ces deux objets ne font plus qu'un. Nous évitons alors deux copies et deux destructions,
et gagnons ainsi en vitesse d'exécution.

Qu'est-ce que la NRVO ?


Auteurs : Florian Goujeon ,
Un type d'optimisation similaire peut également être appliqué dans le cas où l'instance de retour de la fonction est
nommée. On parle alors de NRVO (Named Return Value Optimization, pour optimisation de la valeur de retour
nommée).

const car
build_sport_car()
{
const engine e = build_sport_engine();
const wheels w = build_sport_wheels();
const doors d = build_sport_doors();
const steering_wheel s = build_sport_steering_wheel();
//fabrication des autres composants...

car sport_car(e, w, d, s); //l'instance est nommée


sport_car.price(75000);
sport_car.build_date(today());

return sport_car;
}

Quand s'appliquent la RVO et la NRVO ?


Auteurs : Florian Goujeon ,
Là où il est intéressant d'être conscient de l'existence de ces deux types d'optimisation, c'est que leur application dépend
de la façon dont sont écrites les fonctions concernées.

- 271 -
Les sources présentées sur cette pages sont libres de droits, et vous pouvez les utiliser à votre convenance. Par contre la page de présentation de ces sources
constitue une oeuvre intellectuelle protégée par les droits d'auteurs. Copyright © 2008 Developpez LLC. Tout droits réservés Developpez LLC. Aucune
reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents et images sans l'autorisation expresse de Developpez
LLC. Sinon vous encourez selon la loi jusqu'à 3 ans de prison et jusqu'à 300 000 E de dommages et intérêts.
http://cpp.developpez.com/faq/cpp/
En effet, il pourra être impossible pour le compilateur de déterminer l'instance de retour si la fonction comporte
plusieurs points de sortie (plusieurs instructions return) avec des instances différentes.
Par exemple, la NRVO ne pourra être appliquée au code suivant :

const car
build_car(const bool type_sport = true)
{
if(type_sport)
{
const engine e = build_sport_engine();
const wheels w = build_sport_wheels();
const doors d = build_sport_doors();
const steering_wheel s = build_sport_steering_wheel();
//fabrication des autres composants...

car sport_car(e, w, d, s); //une première instance#

sport_car.price(75000);

return sport_car; //un premier point de sortie#


}
else
{
const engine e = build_town_engine();
const wheels w = build_town_wheels();
const doors d = build_town_doors();
const steering_wheel s = build_town_steering_wheel();
//fabrication des autres composants...

car town_car(e, w, d, s); //une autre instance#

town_car.price(28000);

return town_car; //un autre point de sortie#


}
}

En revanche, un léger réaménagement du code rendra son application possible :

const car
build_car(const bool sport_type = true)
{
std::auto_ptr<engine> e;
std::auto_ptr<wheels> w;
std::auto_ptr<doors> d;
std::auto_ptr<steering_wheel> s;

if(sport_type)
{
e = std::auto_ptr<engine>(new engine(build_sport_engine()));
w = std::auto_ptr<wheels>(new wheels(build_sport_wheels()));
d = std::auto_ptr<doors>(new doors(build_sport_doors()));
s = std::auto_ptr<steering_wheel>(new steering_wheel(build_sport_steering_wheel()));
//fabrication des autres composants...
}
else
{
e = std::auto_ptr<engine>(new engine(build_town_engine()));
w = std::auto_ptr<wheels>(new wheels(build_town_wheels()));
d = std::auto_ptr<doors>(new doors(build_town_doors()));
s = std::auto_ptr<steering_wheel>(new steering_wheel(build_town_steering_wheel()));
//fabrication des autres composants...
}

car new_car(*e, *w, *d, *s); //une seule instance

- 272 -
Les sources présentées sur cette pages sont libres de droits, et vous pouvez les utiliser à votre convenance. Par contre la page de présentation de ces sources
constitue une oeuvre intellectuelle protégée par les droits d'auteurs. Copyright © 2008 Developpez LLC. Tout droits réservés Developpez LLC. Aucune
reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents et images sans l'autorisation expresse de Developpez
LLC. Sinon vous encourez selon la loi jusqu'à 3 ans de prison et jusqu'à 300 000 E de dommages et intérêts.
http://cpp.developpez.com/faq/cpp/
new_car.price(sport_type ? 75000 : 28000);

return new_car; //un seul point de sortie


}

Quels impacts de la (N)RVO sur le passage d'arguments ?


Auteurs : Florian Goujeon ,
Tout bon cours sur le C++ vous dira qu'il est préférable de passer les objets aux fonctions par référence constante plutôt
que par valeur. Il existe cependant un cas où cette assertion est fausse : celui où la (N)RVO s'en mêle.
Soit une fonction effectuant une copie, puis une suite de modifications d'une instance de voiture. Nommons simplement
cette fonction copy_and_modify_car().
La fonction copy_and_modify_car() est appelée depuis la fonction main() de la façon suivante :

int
main()
{
copy_and_modify_car(build_car()); //on passe directement le retour de la fonction build_car()
std::cout << "Fin du programme.\n";
return 0;
}

Commençons par écrire cette fonction de façon académique, en prenant une référence constante :

void
copy_and_modify_car(const car& const_c)
{
car c(const_c);
//modifier c...
}

La (N)RVO agissant, le programme produit la sortie suivante :

Appel au constructeur de car.


Appel au constructeur par copie de car.
Appel au destructeur de car.
Fin du programme.
Appel au destructeur de car.

Écrivons maintenant une nouvelle version de cette fonction, en passant l'instance par valeur :

void
copy_and_modify_car(car c)
{
//modifier c...
}

Contre toute attente, aucune copie implicite n'est créée :

Appel au constructeur de car.


Fin du programme.
Appel au destructeur de car.

Le principe de la (N)RVO (la suppression des objets temporaires intermédiaires) est ici étendu au passage d'objet.

- 273 -
Les sources présentées sur cette pages sont libres de droits, et vous pouvez les utiliser à votre convenance. Par contre la page de présentation de ces sources
constitue une oeuvre intellectuelle protégée par les droits d'auteurs. Copyright © 2008 Developpez LLC. Tout droits réservés Developpez LLC. Aucune
reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents et images sans l'autorisation expresse de Developpez
LLC. Sinon vous encourez selon la loi jusqu'à 3 ans de prison et jusqu'à 300 000 E de dommages et intérêts.
http://cpp.developpez.com/faq/cpp/
L'expression « build_car() » de la fonction main() est associée à une instance temporaire de voiture. Cet objet temporaire
n'ayant de toute façon d'autre destin que d'être détruit une fois la copie implicite effectuée, le compilateur juge bon (à
raison) de le passer directement à la fonction copy_and_modify_car() plutôt que d'en effectuer une copie.
Conséquence de ce raisonnement : l'utilisation d'une instance nommée (donc non-temporaire) empêche l'application
de cette optimisation :

int
main()
{
car c = build_car();
copy_and_modify_car(c); //pas d'optimisation liée à la (N)RVO#

//# étant donné que c peut toujours être utilisé dans la suite du programme

return 0;
}

(N)RVO et classes non copiables ?


Auteurs : Florian Goujeon ,
Il est important de noter que cette optimisation est implicite. Par conséquent, elle ne permet en aucun cas de retourner
des objets d'un type défini comme Qu'est-ce qu'une classe copiable ?.
En effet, l'écriture d'une fonction retournant un tel objet mènerait à une erreur de compilation, même si en pratique
aucune copie n'aurait été effectuée.
Heureusement, le prochain standard du langage C++ (nom de code C++0x) introduit la notion de sémantique de
mouvement, permettant de passer outre cette limitation.

Qu'est-ce que le mot-clef volatile ?


Auteurs : dourouc05 ,
Les variables volatiles sont des variables qui peuvent être modifiées par un processus hors de contrôle du compilateur.
On doit accéder à une copie de la variable si elle a pu être modifiée.
Seules des fonctions membres volatiles peuvent être appelées pour des objets volatils, et des fonctions membres volatiles
ne peuvent appeler que des autres fonctions membres volatiles. La viralité peut être détournée.
Le compilateur s'assure qu'aucune fonction membre n'ait d'optimisation quant aux références mémoire. En l'absence
du mot-clef volatile, il peut optimiser le code de la fonction membre.
Cependant, la présence de ce mot-clef n'empêchera pas le processeur d'optimiser pour la cohérence des caches.
Utilisez ce mot-clef pour des variables que vous ne voulez pas voir optimisées par le compilateur.
Il est faux de penser que ce mot-clef apporte la moindre sécurité face au mutlithread. Il faut pour cela attendre le mot-
clef atomic du C++0x.
Voici un petit exemple d'utilisation.

class Gadget
{
public:
void attendre()
{
while (! _drapeau)
{
dormir(1000); // attend 1000 ms
}
}
void seLever()
{

- 274 -
Les sources présentées sur cette pages sont libres de droits, et vous pouvez les utiliser à votre convenance. Par contre la page de présentation de ces sources
constitue une oeuvre intellectuelle protégée par les droits d'auteurs. Copyright © 2008 Developpez LLC. Tout droits réservés Developpez LLC. Aucune
reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents et images sans l'autorisation expresse de Developpez
LLC. Sinon vous encourez selon la loi jusqu'à 3 ans de prison et jusqu'à 300 000 E de dommages et intérêts.
http://cpp.developpez.com/faq/cpp/
_drapeau = true;
}
...
private:
volatile bool _drapeau;
};

Cette classe est prévue pour attendre qu'un autre thread vienne modifier une de ses variables membres, et vérifier la
situation toutes les secondes.
Si la variable n'avait pas été déclarée volatile, le compilateur aurait cru qu'il n'y a qu'un appel à une fonction externe
qui ne peut modifier le drapeau. Il en aurait conclu qu'il peut mettre le drapeau en cache. Ceci fonctionne très bien
quand il n'y a qu'un thread. Avec le mot-clef, le compilateur comprend que la variable peut être modifiée de l'extérieur,
et ne la mettra pas en cache.

lien : Volatile - Multithreaded Programmer's Best Friend, par Andrei Alexandrescu

Qu'est-ce que l'immuabilité ?


Auteurs : white_tentacle ,
On parle d'immuabilité (ou non-mutabilité) d'un élément (au sens large) lorsque celui-ci ne change pas, tout au long de
sa durée de vie. En C++, il faut distinguer plusieurs cas d'immuabilité :

• immuabilité de membres ;
• immuabilité des paramètres ;
• fonctions membres ne modifiant pas l'objet ;
• types immuables.

L'immuabilité en C++ se déclare au moyen du mot clé const.


Pour l'immuabilité d'un membre :

class C {
int const TAILLE_MAX;
//[...]
};

Une fois affectée (dans le constructeur), TAILLE_MAX ne pourra plus être modifiée. const s'applique normalement à
ce qui le précède (à sa gauche), et à défaut à ce qui le suit (à sa droite). On prendra donc l'habitude d'écrire int const
plutôt que const int, même si les deux sont équivalents, et on comprendra alors plus facilement la différence entre int
const * et int * const.
Pour l'immuabilité d'un paramètre :

int longueur(std::string const & chaine)


{
//[...]
}

Ici, on garantit à l'appelant qu'on ne modifie pas le paramètre qu'il nous donne (bien que celui-ci soit passé par
référence). L'appelant peut se baser sur cet engagement lorsqu'il valide son algorithme.
Fonctions membres ne modifiant pas l'objet :

class chaine {
public:
int longueur() const;
[...]
};

- 275 -
Les sources présentées sur cette pages sont libres de droits, et vous pouvez les utiliser à votre convenance. Par contre la page de présentation de ces sources
constitue une oeuvre intellectuelle protégée par les droits d'auteurs. Copyright © 2008 Developpez LLC. Tout droits réservés Developpez LLC. Aucune
reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents et images sans l'autorisation expresse de Developpez
LLC. Sinon vous encourez selon la loi jusqu'à 3 ans de prison et jusqu'à 300 000 E de dommages et intérêts.
http://cpp.developpez.com/faq/cpp/
Ici, de la même manière, on garantit à l'appelant que le paramètre this (l'objet courant) n'est pas modifié par cet appel.
C'est à dire qu'aucun des membres de l'objet n'est modifié, l'objet reste dans le même état. Un corolaire intéressant
de ceci est que (hors accès concurrents), deux appels successifs à une fonction membre const doivent donner le même
résultat (c'est une garantie sémantique - apparentée à un contrat -, puisqu'en pratique, de nombreuses façons existent
de la détourner).
Si un type ne possède que des fonctions membres ne modifiant pas l'objet, alors, il n'existe aucun moyen de modifier
cet objet et il est dit immuable (ou non-mutable). Cette notion est moins importante en C++ que dans d'autres langages,
du fait que n'importe quel objet mutable devient immuable par l'usage du mot clé const.
Il existe toutefois plusieurs restrictions :

• Dans une fonction membre const, les membres pointeurs sont de type X* const, et non X const * const. Ceci fait
qu'il est tout à fait légal d'écrire le code suivant :

class A {
int * m_val;
public:
int IncrementByOne() const { return ++(*m_val); }
};

Pour justifier ce comportement, prenez l'exemple d'un smart_pointer. Il est logique qu'un smart_ptr<int> const
se comporte de la même manière qu'un int * const, tandis qu'un smart_ptr<const int> se comportera comme un
int const *.
• Les membres déclarés avec le mot clé mutable ignorent les règles d'immuabilité imposées par const. Ceci
permet des implémentations par compteur de références sur des objets const, par exemple.
• Il est possible à tout moment, au moyen de const_cast, de forcer la mutabilité d'une variable immuable.

Il convient donc de se rappeler que const a avant tout valeur d'indication sémantique, et l'utiliser à bon escient. const
bien utilisé facilite la maintenance du code (on sait ce qui change), ainsi que l'utilisation du code par un tiers (limitation
des effets de bord). En revanche, un const volontairement détourné obfusque le code et rend complexe sa compréhension.
En règle générale, on utilisera donc const chaque fois que c'est possible, et on utilisera mutable et const_cast uniquement
lorsque c'est absolument nécessaire et que ça n'induit pas de comportement contre-intuitif (fonction const qui aurait
des effets de bord, par exemple...).

- 276 -
Les sources présentées sur cette pages sont libres de droits, et vous pouvez les utiliser à votre convenance. Par contre la page de présentation de ces sources
constitue une oeuvre intellectuelle protégée par les droits d'auteurs. Copyright © 2008 Developpez LLC. Tout droits réservés Developpez LLC. Aucune
reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents et images sans l'autorisation expresse de Developpez
LLC. Sinon vous encourez selon la loi jusqu'à 3 ans de prison et jusqu'à 300 000 E de dommages et intérêts.
http://cpp.developpez.com/faq/cpp/
Sommaire > Problèmes avec les compilateurs
Mon programme plante ou se comporte bizarrement.
Auteurs : Aurélien Regat-Barrel ,
Parfois, on est confronté à des problèmes très étranges et apparemment sortis de nul part :

• plantage Access Violation, core dump, null pointer


• plantage ou erreur lors d'un appel à new ou à delete : DAMAGE : After Normal Block (#47), Heap block 3228a8
modified, Invalid Address specified to RtlFreeHeap
• variables qui changent de valeur sans qu'on y ait touché
• erreur lors d'une affectation, d'un passage de paramètres, d'une copie
• contenu inattendu dans des tableaux

Ces disfonctionnement ont plusieurs causes possibles, mais ils sont généralement liés à une mauvaise utilisation de
la mémoire. La première chose à faire est de compiler votre programme avec les options de débogage activées et de
l'exécuter dans un débogueur afin que ce dernier vous emmène précisément à l'endroit où l'erreur est détectée (ce qui
ne veut pas dire que c'est là qu'elle a lieu !).
Dans le cas d'un pointeur nul où d'une violation d'accès, il peut s'agir une erreur simple de programmation telle qu'un
pointeur non initialisé. Si vous êtes confronté à une erreur d'apparence plus surnaturelle, vérifiez les points suivants :

• les constructeurs par recopie de vos objets qui allouent des ressources
• qu'il n'y a pas de débordement mémoire quelque part, en particulier à proximité de l'objet victimes de
comportements étranges
• que vos pointeurs sont correctement initialisés, et qu'ils ne pointent pas vers des données qui ont été libérées /
détruites par la suite

soyez sûrs d'une chose : une variable ne change pas toute seule sans raisons de valeur. Cela est très souvent dû à un
débordement qui vient écraser le contenu de votre variable. Vérifiez donc minutieusement l'utilisation que vous faites
des pointeurs.
Un autre cas fréquent d'erreur est déclenché par les opérateurs et fonctions de gestion de la mémoire new, delete,
malloc, free. Il faut savoir que ces derniers, en particulier en mode débogage, effectuent divers tests pour détecter toute
mauvaise utilisation ou corruption de la mémoire. Si la vérification échoue, alors une erreur est signalée. Ce n'est donc
pas ces opérateurs / fonctions qui sont en cause, mais bien votre code qui les utilise de manière erronée. En particulier,
vérifiez bien que :

• vous utilisez delete [] et non pas delete pour libérer des tableaux alloués avec new []
• vous utilisez delete pour libérer une allocation faite avec new, et free pour malloc (ne pas mélanger les deux !)
• que vous n'avez pas désalloué une ressource deux fois
• que le pointeur retourné par new n'a pas été modifié ou altéré avant son passage à delete

Le message d'erreur vous aide généralement à déceler le problème. Si ce dernier parle de bloc corrompu ou modifié,
alors il s'agit d'un débordement mémoire qui a écrasé les données internes ajoutées à proximité de votre allocation par
la bibliothèque standard (afin de garder une trace de ce qui a été alloué, de faire des vérification, etc...).

Mon programme se lance et se termine immédiatement sans que je ne puisse rien voir.
Auteurs : Aurélien Regat-Barrel ,
Sous Windows, quand on lance un programme console depuis certains IDE (tel que devcpp), sa console n'est visible que
durant son exécution. Si ce dernier ne fait qu'afficher un message, elle va donc disparaître immédiatement sans que

- 277 -
Les sources présentées sur cette pages sont libres de droits, et vous pouvez les utiliser à votre convenance. Par contre la page de présentation de ces sources
constitue une oeuvre intellectuelle protégée par les droits d'auteurs. Copyright © 2008 Developpez LLC. Tout droits réservés Developpez LLC. Aucune
reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents et images sans l'autorisation expresse de Developpez
LLC. Sinon vous encourez selon la loi jusqu'à 3 ans de prison et jusqu'à 300 000 E de dommages et intérêts.
http://cpp.developpez.com/faq/cpp/
l'on puisse lire quoique ce soit. Une solution simple consiste donc à attendre que l'utilisateur appuie sur la touche entrée
avant de se terminer, comme dans l'exemple suivant :

#include <iostream>
#include <limits>

using namespace std;

int main()
{
cout << "Hello World !\n";

// attendre avant de quitter


cin.ignore( numeric_limits<streamsize>::max(), '\n' );
}

La méthode utilisée pour effectuer cette attente est détaillée dans Comment faire une pause (attendre que l'utilisateur
tape une touche) ?.

Mon programme C++ compile parfaitement avec gcc 2.x, et marque pleins d'erreurs avec gcc 3.x
Auteurs : Anomaly ,
La raison la plus probable est l'absence de la directive using namespace std;. Cette directive était optionnelle avec gcc
2.x (ceci était un défaut de conformité au standard). Il faut maintenant, avec gcc 3.x qui est conforme au standard, la
mettre dans chaque fichier source, après le bloc des #include. Le programme ainsi modifié compilera aussi bien avec
gcc 2.x que gcc 3.x.

Devcpp se plaint de ne pas connaître le format de mon .lib


Auteurs : Aurélien Regat-Barrel ,
En ajoutant un fichier lib à la ligne de commande avec devcpp vous aurez l'erreur suivante :

file not recognized C:\Dev-Cpp\lib\glut32.lib File format not recognized

Cela est du au fait que le port de gcc fourni avec devcpp (MingW) utilise son propre type de fichiers lib portant
l'extension .a. Il vous faut donc obtenir une version compatible avec devcpp de la bibliothèque que vous voulez utiliser.
Notez qu'un grand nombre d'entre-elles sont fournies dans le répertoire \lib, comme cela est le cas dans cet exemple
avec libglut32.a. Regardez donc dans ce répertoire pour voir si l'équivalent de votre .lib ne s'y trouve pas.

Erreur "symbole externe non résolu _WinMain@16 référencé dans la fonction _WinMainCRTStartup"
Auteurs : Aurélien Regat-Barrel ,
Si en compilant un programme C/C++ sous Windows vous obtenez un message d'erreur du type

error LNK2019: symbole externe non résolu _WinMain@16 référencé dans la fonction _WinMainCRTStartup [Linker
error] undefined reference to `WinMain@16'

C'est que vous avez créé un projet Win32 sans console au lieu d'un projet console, ce qui fait que le compilateur s'attend
à trouver la fonction d'entrée WinMain() à la place de la fonction standard main(). A partir de Visual C++ 7, vous

- 278 -
Les sources présentées sur cette pages sont libres de droits, et vous pouvez les utiliser à votre convenance. Par contre la page de présentation de ces sources
constitue une oeuvre intellectuelle protégée par les droits d'auteurs. Copyright © 2008 Developpez LLC. Tout droits réservés Developpez LLC. Aucune
reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents et images sans l'autorisation expresse de Developpez
LLC. Sinon vous encourez selon la loi jusqu'à 3 ans de prison et jusqu'à 300 000 E de dommages et intérêts.
http://cpp.developpez.com/faq/cpp/
pouvez modifier les propriétés de votre projet via Propriétés de configuation->Editeur de liens->Système->Sous-système :
Console (/SUBSYSTEM:CONSOLE). Pour les versions antérieures, il faut créer un nouveau projet console.

Peut-on utiliser la bibliothèque boost avec Visual C++ ?


Auteurs : Aurélien Regat-Barrel ,
Oui, mais de manière confortable à partir de Visual C++ 7.1 seulement (Visual C++ .Net 2003). Microsoft a consacré un
article à ce sujet qui fait aussi office de bonne introduction à cette bibliothèque : Boost for Visual C++ Developers.

L'article Installer et utiliser Boost/Boost.TR1 avec Visual C++ vous donnera les bases pour réaliser cette installation

La STL livrée avec Visual C++ 6 est bugguée !


Auteurs : Aurélien Regat-Barrel ,
L'implémentation de la STL livrée avec Visual C++ 6 possède divers bugs. Il est vivement conseillé de procédé à sa mise
à jour à partir du site de Dinkumware qui est l'auteur de cette implémentation. Pour cela, lire la page Fixes for
Library Bugs in VC++ V5.0/V6.0.

Erreur C1010 avec Visual C++


Auteurs : Aurélien Regat-Barrel ,
"Fatal error C1010: unexpected end of file while looking for precompiled header directive" "Fatal error C1010: Fin de
fichier inattendue lors de la recherche d'une directive d'en-tête précompilé"

Cette erreur survient quand votre projet est configuré pour utiliser un fichier d'en-tête précompilé (typiquement
stdafx.h). Il vous suffit de désactiver l'utilisation des en-têtes précompilées dans les options C/C++ de votre projet.

J'ai un problème avec Visual C++ qui n'est pas traité ici.
Auteurs : Aurélien Regat-Barrel ,
Vous trouverez des solutions à divers problèmes spécifiques à Visual C++ dans la FAQ Visual C++.

Ma STL provoque des fuites mémoires ! Est-ce normal ?


Auteurs : Laurent Gomila ,
Pas de panique, il se peut que ce soit normal. Si vous utilisez un outil de détection de fuites mémoires (par exemple
valgrind) sur ce programme, avec certains compilateurs (par exemple gcc) vous verrez un rapport indiquant de la
mémoire non libérée :

#include <string>
#include <iostream>

- 279 -
Les sources présentées sur cette pages sont libres de droits, et vous pouvez les utiliser à votre convenance. Par contre la page de présentation de ces sources
constitue une oeuvre intellectuelle protégée par les droits d'auteurs. Copyright © 2008 Developpez LLC. Tout droits réservés Developpez LLC. Aucune
reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents et images sans l'autorisation expresse de Developpez
LLC. Sinon vous encourez selon la loi jusqu'à 3 ans de prison et jusqu'à 300 000 E de dommages et intérêts.
http://cpp.developpez.com/faq/cpp/
using namespace std;

int main()
{
string s("hello world");
cout << s << endl;
return EXIT_SUCCESS;
}

LEAK SUMMARY:
definitely lost : 0 bytes in 0 blocks.
possibly lost : 0 bytes in 0 blocks.
still reachable: 960 bytes in 1 blocks.
suppressed: 0 bytes in 0 blocks.

Il ne s'agit pourtant pas de fuite mémoire mais d'une fonctionnalité de la bibliothèque standard. En effet, celle-ci peut
posséder son propre espace d'allocation afin d'optimiser les performances, celui-ci n'étant pas libéré et rendu à l'OS
une fois votre programme terminé. Mais pas de panique, la quantité de mémoire non libérée est constante et ne grossira
jamais, peu importe le nombre d'objets que votre programme manipulera.

Toutefois, si cette fonctionnalité vous gêne vraiment vous pouvez la désactiver et forcer l'allocation "classique" :
- En définissant la macro __USE_MALLOC (gcc versions 2.91, 2.95, 3.0 et 3.1)
- En définissant la variable d'environnement GLIBCPP_FORCE_NEW (gcc versions 3.2.2 et supérieures)
- Si avez le goût du risque, vous pouvez également réécrire vos propres allocateurs

Cependant n'oubliez pas que ce comportement est un cas isolé, et que 99% des fuites mémoires seront dues à des erreurs
de votre part. Avant d'incriminer la bibliothèque standard, pensez à faire un maximum de tests !

- 280 -
Les sources présentées sur cette pages sont libres de droits, et vous pouvez les utiliser à votre convenance. Par contre la page de présentation de ces sources
constitue une oeuvre intellectuelle protégée par les droits d'auteurs. Copyright © 2008 Developpez LLC. Tout droits réservés Developpez LLC. Aucune
reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents et images sans l'autorisation expresse de Developpez
LLC. Sinon vous encourez selon la loi jusqu'à 3 ans de prison et jusqu'à 300 000 E de dommages et intérêts.
http://cpp.developpez.com/faq/cpp/

S-ar putea să vă placă și