Académique Documents
Professionnel Documents
Culture Documents
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 ?.
-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.
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.
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.
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 ?.
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.
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).
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.
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.
-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 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.
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....])
-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++
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++).
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.
- 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.
La solution des exercices du livre Le langage C++ est disponible ici : http://www.vandevoorde.com/C++Solutions/.
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.
• 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 :
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.
•
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++ ?.
• 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.
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
// ...
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"
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.
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.
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++ 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.
sizeof('x')
vaut
sizeof(char)
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;
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é.
#ifdef __cplusplus
extern "C" {
#endif
#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.
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").
- 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
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 ne constitue pas un mécanisme de sécurité. Il s'agit d'une protection contre les erreurs, pas contre
l'espionnage.
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).
"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.
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;
}
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 ?.
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).
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 LinkedListIterator::LinkedListIterator(Node* p)
: p_(p)
{
}
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>.
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 ?
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).
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.
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).
int main()
{
CConcrete c;
Fonction(c);
return 0;
}
- 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 ?
class Widget
{
public:
virtual ~Widget() { /* ... */ }
void show()
{
// ...
do_show();
// ...
}
// ...
private :
virtual void do_show()=0; // fonction virtuelle pure
};
void show_widget(Widget& w)
{
w.show();
}
// ...
Button b;
Textfield t;
- 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.
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
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.
#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.
- 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.
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;
};
void
do_click_forward_button()
{
//Lire chanson suivante...
}
};
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;
return 0;
}
- 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
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.
- 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é :
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.
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
}
};
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).
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)
class mere
{
// ...
}
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() {}
- 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"
// 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
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.
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
}
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;
};
2. Le mode d'héritage par défaut est public pour les structures, private pour les classes.
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/
{
};
// 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
A * Exemple();
Fichier.cpp
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.
- 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.
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
- 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.
// Utilisation
Base* Obj1 = new Derivee1;
Base* Obj2 = new 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 :
- 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 ?.
• constructeur ;
• constructeur par copie : privé, non-défini ;
• operator= : privé, non-défini ;
• swap() + spécialisation de std::swap ;
• destructeur.
- 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.
• 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.
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.
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.
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.
- 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 ?
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 ?
- 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();
};
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
};
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.
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<>".
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/
}
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)
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 );
}
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
};
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.
Par exemple, ce constructeur initialise l'objet membre x_ en utilisant une liste d'initialisation :
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.
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.
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.
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.
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 ?
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é :
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_;
};
- 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)
{
}
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.
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.
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;
};
int main()
{
Creation<A>();
Creation<B>();
Creation<C>();
Creation<D>();
Creation<E>();
Creation<F>();
Creation<G>();
Creation<H>();
Creation<I>();
return 0;
}
- 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/
{
}
};
int main()
{
Creation<B>();
Creation<C>();
Creation<D>();
return 0;
}
Ce code produit :
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.
• 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();
};
#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é.
- 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];
// ...
}
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.
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é).
À 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 :
- 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()
}
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/
};
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.
#include <iostream>
// B hérite de A
class B : public A
{
public:
B() { std::cout << "Constructeur de B.\n"; }
~B() { std::cout << "Destructeur de 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 :
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"; }
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).
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.
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();
// ...
};
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&).
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};
};
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
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" ?.
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"
// 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
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.
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;
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.
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
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
en
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++.
Supposons que l'on ait une classe X qui a un objet Fred statique
x.hpp
class X
{
public:
// ...
private:
static Fred x_;
};
x.cpp
#include "X.hpp"
Fred X::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();
};
x.cpp
#include "X.hpp"
Fred& X::x()
{
static Fred* ans = new Fred();
return *ans;
}
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.
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.
#include <iostream>
- 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& 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& x()
{
static int ans;
- 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;
return ans;
}
Et en supprimant les affichages, nous pouvons simplifier le code en quelque chose de très simple
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()
{
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 ?
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 :
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 :
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>
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>
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:
};
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;
Cette approche possède quelques avantages par rapport à l'astuce utilisée en C++03 :
Notez également l'utilisation de la syntaxe "=defaut" pour récupérer un constructeur par défaut généré
automatiquement par le compilateur.
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.
L'héritage public est le plus problématique car il doit respecter le LSP et notamment ses implications en termes de
contrat.
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;}
};
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.
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.
- 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 A{};
class B{};
class C: public A,public B{};
- 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 :
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;
};
- 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;
}
- 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")
{
}
};
int main()
{
Creation<B>();
Creation<C>();
Creation<D>();
return 0;
}
Ce code produit :
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.
• 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.
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
};
• 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
};
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
};
void show_widget(Widget& w)
{
w.show();
}
// ...
Button b;
Textfield t;
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 :
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 ?
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
- 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()"
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.
class A
{
public:
// variable et fonction non statiques
int var1;
void f1() {};
// variable et fonction statiques
static int var2;
static void f2() {};
};
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();
#include <iostream>
#include <string>
struct Base
{
void F(int);
};
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 :
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) :
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 :
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.
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 :
Cette méthode est intensivement utilisée par exemple pour manipuler les flux standards :
#include <iostream>
#include <vector>
class Polygon
{
pulic :
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...
#include <string>
#include <vector>
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)
{
// ...
}
std::string s1 = "salut";
std::string s2 = "hello";
FonctionIncorrecte(s1); // Ok
FonctionCorrecte(s1); // Ok
void FonctionIncorrecte(char* s)
{
// ...
}
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/
}
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.
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.
- 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()"
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.
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).
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
}
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;
}
};
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;
};
int main()
{
A *a1 = new A;
// faire une copie
A *a2 = static_cast<A*>( a1->Clone() ); // cast obligatoire !
}
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/
};
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 ?.
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.
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.)
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()).
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.
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.
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.
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é.
int f();
void userCode(int x)
{
int ans;
- 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.
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 :
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 :
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.
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.
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 :
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.
- 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.
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).
int* const p
- 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.
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*>
- 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.
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.
* 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 :
class A {};
// conversion de const A * en A *
const A * cptr;
A * ptr = const_cast<A*>( cptr );
- 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.
#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).
• 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 ?
// 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();
};
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 );
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
#else
#endif
- 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.
class Array {
public:
int& 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.
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);
// ...
}
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];
}
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é.
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()
{
delete[] data_;
}
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);
//...
}
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()
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;
}
class Number {
public:
Number& operator++ ();
void operator++ (int);
};
Number& Number::operator++ ()
{
//...
return *this;
}
Attention, il ne faut pas que la version postfixée renvoie l'objet 'this' par référence, vous aurez été prévenus.
- 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
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
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.
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.
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 :
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.
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.
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 :
// 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.
// 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.
return *this;
} // A la fin de la fonction, Temp sera détruit automatiquement
// et son destructeur désallouera correctement la ressource
- 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;
}
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 :
- 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->.
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;
};
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 :
Remarque : Ces règles s'appliquent aussi à un prédicat de type égalité que l'on passerait à un algorithme.
- 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;
};
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).
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 à :
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.
De même, delete appelle le destructeur de la classe, alors que free() ne le fait pas.
- 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.
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.
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 ?.
- 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 ?.
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 ?).
delete []Tab;
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 ?.
#include <iostream>
int Tab[50];
std::cout << sizeof(Tab) / sizeof(int); // affiche "50"
- 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 :
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>
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
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() :
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::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 :
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
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 :
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)
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;
- 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))"
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.
Maintenant, le compilateur intégrera automatiquement les appels au constructeur dans un bloc try/catch.
principe
Foo* p;
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 :
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 :
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 :
- 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
}
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.
void someCode()
{
char memory[sizeof(Fred)]; // Ligne 1
void* place = memory; // Ligne 2
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 ?.
- 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 :
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 ?).
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 !
}
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 ?).
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 :
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 :
- 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_;
};
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.
• 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).
- 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();
namespace mylib
{
void fonction1();
void fonction2();
}
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.
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()
{
}
#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
int main()
{
// ici il est obligatoire de préfixer par std::
affiche_coucou();
std::cout << "coucou" << std::endl;
}
#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;
}
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;
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 :
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 :
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":
}
}
- 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();
}
}
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;
}
int * ptr;
try
{
// tenter d'allouer 100 entiers
ptr = new int [ 100 ];
}
catch ( const std::bad_alloc & )
{
// échec de l'allocation
}
- 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.
#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 :
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.
L'utilisation de
try {
// ...
} catch(...) {
// ...
}
permet de capturer toutes les exceptions pouvant survenir, mais il est, dans ce cas, impossible de faire la distinction.
#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.
#include <iostream>
#include <sstream>
#include <exception>
private:
std::string msg;
};
int main()
{
try
{
throw my_exception( "exception test", __LINE__ );
}
catch ( const std::exception & e )
{
std::cerr << e.what() << "\n";
}
}
- 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
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.
- 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.
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>.
int main()
{
std::string message = "Hello"; // création de la chaîne "Hello"
message += " World !"; // concaténation de " Word !"
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* ?.
- 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 ];
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.
- 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() ?
#include <string>
#include <iostream>
int main()
{
using namespace std;
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() ?
#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" );
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;
#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 ?.
#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 ?
#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/
}
#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>
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.
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 )
{
}
#include <string>
int main()
{
using std::string;
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é !
- 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.
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";
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").
#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 ?.
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 :
return s;
}
};
- 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;
ci_string s1 = "salut";
ci_string s2 = "SAluT";
Pour des informations plus complètes, vous pouvez jeter un oeil à l'item n°29 des GOTW (Guru Of The Week).
• 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).
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");
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++.
- 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>
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.
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.
// fonctions de traitement
void parametre_input();
void parametre_output();
void parametre_inconnu();
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();
- 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)();
}
}
#include <string>
#include <locale>
#include <vector>
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.
- 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>
int main()
{
using namespace std;
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é).
#include <string>
#include <iostream>
int main()
{
using namespace std;
- 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" );
int main()
{
using namespace std;
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"
}
- 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 :
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.
#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.
#include <string>
#include <sstream>
int x = 127;
std::ostringstream oss;
oss << std::hex << x;
- 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).
#include <string>
#include <sstream>
On peut de la même manière convertir une représentation décimale (std::dec), ou 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
- 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/
}
- 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>
// 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).
#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;
};
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;
};
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);
#include <sstream>
#include <string>
std::ostringstream oss;
std::string Fichier[10];
- 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.
#include <algorithm>
#include <iomanip>
#include <sstream>
#include <string>
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>
- 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';
}
Afin d'accéder au streambuf d'un flux, il faut utiliser la fonction membre rdbuf (ses 2 surcharges pour être précis) :
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;
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) :
std::vector<int> Tab;
Tab.push_back(1);
Tab.push_back(3);
Tab.push_back(6);
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.
// 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
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.
- 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>
#include <iostream>
using namespace std;
ou
#include <iostream>
ou encore
#include <iostream>
using std::cout;
using std::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>
#include <iostream>
ou alors
#include <iostream>
using namespace std;
#include <iostream>
#include <limits>
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.
int main()
{
using namespace std;
- 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 :
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>
int main ()
{
int choix;
if ( read_choice( choix ) )
{
cout << "Vous avez choisi : " << choix << '\n';
}
return 0;
}
- 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>
int main()
{
cout << "Appuyez sur entrée pour continuer...";
cin.ignore( numeric_limits<streamsize>::max(), '\n' );
}
#include <iostream>
- 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>
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 ) ?.
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;
}
#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
}
- 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é:
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é :
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 :
string line;
while ( getline( file, line ) )
{
++count;
}
- 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 ;
}
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 * :
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 :
L'expression
est elle un peu plus simple à comprendre. Il n'y a pas de conversion implicite, mais un simple appel à l'opérateur
- 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).
#include <string>
#include <fstream>
#include <iostream>
int main()
{
// le constructeur de ifstream permet d'ouvrir un fichier en lecture
std::ifstream fichier( "fichier.txt" );
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é.
#include <fstream>
#include <iostream>
#include <sstream>
int main()
{
// le constructeur de ifstream permet d'ouvrir un fichier en lecture
std::ifstream fichier( "fichier.txt" );
- 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 ?.
// 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;
}
}
}
int main()
{
std::ifstream file( "fichier.txt" );
if ( file )
{
int lines = std::count(
std::istreambuf_iterator<char>( file ),
std::istreambuf_iterator<char>(),
'\n' );
}
}
- 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>
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.
#include <fstream>
#include <iostream>
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));
}
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 :
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;
}
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.
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 ?).
#include <iostream>
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)
{
int main()
{
Exemple<int> A(3);
Exemple<float> B(A);
return 0;
}
// Version générique
template <typename T>
void QuiSuisJe( const T & x )
{
std::cout << "Je ne sais pas" << 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/
}
// Test
MaClasse Test1;
int Test2;
float Test3;
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;
Attention, un template ne peut être spécialisé qu'à l'intérieur d'un namespace, et pas dans une classe.
- 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).
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.
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.
// exemple.h
#ifndef EXEMPLE_H
#define EXEMPLE_H
- 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();
};
// exemple.tpp
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.
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)
- 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;
};
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 :
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 :
#include <vector>
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;
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 :
"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".
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 :
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.
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/
};
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 :
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.
- 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 ?.
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>.
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é.
Il s'agit désormais de définir des widget bien précis et concrets, comme button ou textfield.
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.
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.
- 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;
};
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é.
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.
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.
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.
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
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.
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;
// redimensionner le tableau
// resize() initialise tous les nouveaux entiers à 0
v.resize( 10 );
// 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 );
- 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';
// 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";
}
}
- 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.
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 !
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.
- 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::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");
- 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.
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.
- 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);
- 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* ?.
struct A
{
int Number;
std::string 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 ?).
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.
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 ?.
#include <iostream>
#include <vector>
#include <algorithm>
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 );
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 :
#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 );
- 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 ) );
int Number;
std::string 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"));
- 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;
// 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;
}
#include <list>
#include <algorithm>
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 ?.
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();
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 :
// Tri du vecteur
std::sort(v.begin(), v.end(), MonFoncteur());
- 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 :
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.
- 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.
Citons néanmoins les tutoriels de Miles, en français : Un aperçu des possibilités des bibliothèques de Boost.
• 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.
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" ) );
Ce programme produit le résultat suivant s'il est compilé pour ne pas ignorer les assertions :
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 ?
#include <boost/shared_ptr.hpp>
class Base
{
public:
virtual ~Base() {};
};
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 );
}
}
• 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 ?).
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.
• 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.
#include <iostream>
#include <boost/tokenizer.hpp>
// séparateur personnalisé
boost::char_separator<char> sep( Separators.c_str() );
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";
-- 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.
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;
}
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
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;
};
• 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& 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.
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 :
• 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 :
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 :
#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;
}
// 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.
#include <fstream>
#include <iostream>
#include <limits>
return Count;
}
int main()
{
std::ifstream File("fichier.txt");
File.clear();
File.seekg(0, std::ios::beg);
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++)
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) ?.
•
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.
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+
+).
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 :
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 );
}
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.
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 MaClasse
{
void Something(const Classe1& c);
Classe2* c2;
};
MaClasse.cpp
#include "Classe1.h"
#include "Classe2.h"
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.
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);
}
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;
}
- 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
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 :
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.
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 :
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;
Par exemple, voici un code qui sera capable de compiler correctement à la fois sous Windows et sous Unix :
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.
- 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 :
Une version simplifiée de cette règle est donc de ne pas commencer ses noms par un _ du tout, ni d'utiliser __.
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.
Par conséquent, les identifiants (de variables) sont des lvalues. Il en va de même pour les références.
- 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.
class car
{
public:
//constructeur
car
(
const engine& e, //moteur
const wheels& w, //roues
const doors& d, //portières
const steering_wheel& s //volant
//etc...
);
//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()
{
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...
int
main()
{
car my_car = build_sport_car();
- 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.
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.
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 sport_car;
}
- 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...
sport_car.price(75000);
town_car.price(28000);
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...
}
- 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);
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...
}
Écrivons maintenant une nouvelle version de cette fonction, en passant l'instance par valeur :
void
copy_and_modify_car(car c)
{
//modifier c...
}
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;
}
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.
• immuabilité de membres ;
• immuabilité des paramètres ;
• fonctions membres ne modifiant pas l'objet ;
• types immuables.
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 :
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 :
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>
int main()
{
cout << "Hello World !\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.
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.
L'article Installer et utiliser Boost/Boost.TR1 avec Visual C++ vous donnera les bases pour réaliser cette installation
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++.
#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/