| auteur : 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 ()
{
std:: system ( " dir " );
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++)
|
| auteurs : Aurélien Regat-Barrel, Laurent Gomila |
C++0x est le nom de code de la prochaine version de l'actuel standard ISO C++ 98,
qui porte le nom de C++98.
Cette future norme devrait être ratifiée d'ici 2009, et le 'x' dans son nom de code
sera alors remplacé par l'année en cours (par exemple, C++09 si cela a lieu en 2009).
Faire évoluer un langage tel que le C++ est une tâche complexe qui nécessite du temps.
Conscient que C++0x ne serait pas ratifié avant la fin de la décénie,
le commité de normalisation ISO a entrepris et achevé en 2005 la rédaction d'un document intermédiaire
limité au développement d'extensions à la bibliothèque standard du C++.
Vous pouvez lire à ce sujet Qu'est-ce que le Library Technical Report (tr1 / tr2) ?.
Voici quelques liens vers des documents sur le sujet :
|
| auteur : Alp Mestan | Il est déjà possible de tester quelques nouvelles fonctionnalités de C++0x grâce au travail énorme fourni par les équipes de développement de 2 compilateurs majeurs : Microsoft Visual C++ et g++.
En effet, depuis quelques années, nous avons pu voir le compilateur g++ implémenter des fonctionnalités de C++0x. En ce qui concerne les concepts de C++0x, ils sont dans une grande mesure utilisables par le biais de ConceptGCC. Parallèlement, g++ intègre peu à peu des fonctionnalités du monde C++0x au fil de ses versions 4.x et l'on peut suivre ce développement sur le site officiel de gcc/g++.
- lambda Expressions ;
- rvalue References ;
- static_assert ;
- mot clé auto, mais dans sa nouvelle version (il fait partie des fonctionnalités relatives à l'inférence de type en C++).
|
lien : Qu'est-ce que C++0x ?
|
| auteur : Aurélien Regat-Barrel |
Le Technical Report on C++ Library Extensions
est le premier Technical Report (TR1)
consacré à la bibliothèque standard du C++.
Il a été réalisé en 2005, et constitue une sorte de brouillon officiel
décrivant un ensemble d'extensions à la bibliothèque standard actuelle suceptibles d'être adoptées dans
C++0x.
Un deuxième document du même type est actuellement à l'étude (TR2).
Un Technical Report n'est pas normatif, c'est-à-dire qu'il ne fait pas officiellement partie de la norme,
et ne garantit pas que ce sera le cas dans le futur.
Les distributeurs de compilateurs ne sont pas obligés non plus de développer et fournir ces extensions.
Un autre élément important est que les extensions proposées s'appuient sur les capacités actuelles du langage C++,
et non sur les futures possibilités de C++0x.
Cependant, un TR (Technical Report) préfigure fortement ce que sera la prochaine norme C++, et l'objectif d'un tel document est double :
- fournir dès maintenant aux programmeurs C++ de nouvelles bibliothèques préstandards ;
- obtenir des retours d'expériences sur ces ajouts afin d'y apporter des améliorations avant leur normalisation définitive.
# include <memory>
int main ()
{
std:: tr1:: shared_ptr< int > p ( new int );
}
|
Les principales extensions issues de boost et incluses dans le TR1 sont :
- enrichissement de <memory> avec des pointeurs intelligents (shared_ptr, weak_ptr, ...) ;
- enrichissement de <functional> (bind, ref, reference_wrapper, mem_fn, result_of, ...) ;
- enrichissement de <utility> (tuple_size, tuple_element, ...) ;
- ajout de <tuple> (collections d'éléments : paires, triples, quadruples, ...) ;
- ajout de <type_traits> (aide à la méta-programmation) ;
- ajout de <random> (génération de nombres aléatoires) ;
- ajout de <array> (tableau de taille fixe) ;
- ajout de <unordered_set> et de <unordered_map> (tables de hachage) ;
- ajout de <regex> (expressions régulières).
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.
|
| auteur : 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.
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.
|
| auteur : Laurent Gomila |
Voici quelques astuces pour réduire les temps de compilation, qui peuvent parfois se compter en minutes voire en heures sur des projets
de taille conséquente.
1/ Utiliser les déclarations anticipées
Si dans un en-tête vous ne déclarez qu'un pointeur ou une référence sur une classe, alors vous n'avez pas besoin d'inclure son en-tête :
une déclaration anticipée suffit.
MaClasse.h
class Classe1;
class Classe2;
class MaClasse
{
void Something (const Classe1& c);
Classe2* c2;
} ;
|
MaClasse.cpp
# include "Classe1.h"
# include "Classe2.h"
void MaClasse:: Something (const Classe1& c)
{
}
|
Ainsi lorsque Classe1 ou Classe2 sera modifiée, seul MaClasse.cpp sera recompilé. Sans cette astuce, vous auriez dû recompiler
(inutilement) tous les fichiers incluant MaClasse.h. L'élimination des dépendances inutiles est essentielle pour améliorer les temps de
compilation.
2/ Utiliser l'idiome enveloppe-lettre, appelé aussi Pimpl (Private Implementation)
Le principe de cet idiome est de séparer l'interface publique d'une classe de son implémentation.
MaClasse.h (interface)
class MaClasseImpl;
class MaClasse
{
public :
void Set (int );
int Get () const ;
private :
MaClasseImpl* pImpl;
} ;
|
MaClasse.cpp
# include "MaClasseImpl.h"
void MaClasse:: Set (int x)
{
pImpl- > Set (x);
}
int MaClasse:: Get () const
{
return pImpl- > Get ();
}
|
MaClasseImpl.h (implémentation)
class MaClasseImpl
{
public :
void Set (int );
int Get () const ;
private :
int val;
} ;
|
MaClasseImpl.cpp
# include "MaClasseImpl.h"
void MaClasseImpl:: Set (int x)
{
val = x;
}
int MaClasseImpl:: Get () const
{
return val;
}
|
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.
3/ Utiliser les en-têtes précompilés
En plus des techniques citées ci-dessus, certains environnements de développement comme VC++ et BCB proposent d'utiliser un en-tête
précompilé. Pour en tirer partie il faut :
- Activer son utilisation dans les options du projet.
- Inclure dans l'en-tête précompilé (par défaut, stdafx.h sous VC++) tous les en-têtes que vous ne modifierez pas ou peu souvent
(en-têtes standards, de bibliothèques externes, ...).
- Inclure l'en-tête précompilé dans toutes les unités de traduction (les .cpp) du projet.
Attention, si vous y incluez un en-tête subissant des modifications fréquentes cela aura l'effet inverse : à chaque modification de
celui-ci vous aurez droit à une recompilation complète.
|
| auteurs : Laurent Gomila, Jean-Marc.Bourguet |
C'est tout à fait normal. Cela est dû au codage interne des flottants en mémoire (selon
la norme IEEE 754). Ces imprécisions deviennent d'autant plus
gênantes qu'elles s'accumulent en même temps que les opérations que vous effectuez sur vos nombres ; ainsi il est tout à fait possible
d'obtenir des nombres très éloignés des résultats théoriques.
Il n'existe pas de solution miracle, cependant vous pouvez tout de même trouver un compromis selon vos besoins :
-> Vous avez besoin d'une précision exacte (gestion de comptes ou autres applications monétaires) : utilisez plutôt ce que l'on appelle
les nombres en virgule fixe, qui, généralement codés sous forme d'entiers, ne souffrent pas de ces imprécisions. Le C++ ne dispose
pas en standard de types en virgule fixe, mais vous pourrez facilement trouver des classes ou des bibliothèques toutes faites, voire
construire la vôtre.
-> Vous voulez tenir compte des imprécisions dans vos calculs : utilisez un epsilon (nombre très petit) lors de vos tests de comparaison.
Typiquement, l'espilon choisi dépend de votre application et des grandeurs manipulées. Un point de départ est l'espilon défini dans la
bibliothèque standard (std::numeric_limits<float>::epsilon() pour les float par exemple), qui définit le plus petit flottant tel
que 1 + espilon > 1. Ainsi vos comparaisons deviennent :
# include <cmath> // pour std::abs
# include <limits> // pour std::numeric_limits
using namespace std;
float f1 = 0 .1f ;
float f2 = 1 .1f ;
float f3 = f2 - f1;
if (f3 = = 1 .0f )
{
}
if (abs (f3 - 1 .0f ) <= err * max (abs (f3), abs (1 .0f )) * numeric_limits< float > :: epsilon ())
{
}
|
|
| auteur : Laurent Gomila |
Lorsque l'on veut écrire du code portable, il est parfois nécessaire d'utiliser la compilation conditionnelle,
c'est-à-dire compiler tel ou tel code selon la plateforme sur laquelle on se trouve.
Par exemple, voici un code qui sera capable de compiler correctement à la fois sous Windows et sous Unix :
# if defined ( linux )
# elif defined ( _WIN32 )
# else
# error Plateforme non gérée
# endif
|
Cependant, connaître toutes les macros prédéfinies pour chaque OS, compilateur, ... est très difficile.
La solution la plus rapide est d'aller visiter le site Pre-defined C/C++
Compiler Macros, qui maintient une liste à jour des macros prédéfinies pour les choses suivantes :
- Standard.
- Compilateurs.
- Systèmes d'exploitation.
- Bibliothèques.
- Architectures.
Une bonne habitude est également d'aller regarder les en-têtes de bibliothèques portables
(par exemple boost). Ceux-ci sont truffés de conditions sur les plateformes, et vous donneront
de bonnes informations.
|
| auteurs : Laurent Gomila, JolyLoic, Mat007 |
En plus de ses mots-clé, le C++ réserve certains identificateurs, notamment pour l'implémentation de la bibliothèque standard ou pour les ajouts
propriétaires que l'on peut trouver sur la plupart des compilateurs. Cela assure qu'il n'y aura aucun conflit entre votre code et celui livré par le compilateur,
qui ne sera pas forcément documenté.
En outre, la norme réserve (et donc interdit) les identificateurs suivant :
- Les noms commençant par un _ suivi d'une majuscule.
- Les noms commençant par _ dans le namespace global.
- Les noms contenant __ (deux _ d'affilée).
Une version simplifiée de cette règle est donc de ne pas commencer ses noms par un _ du tout, ni d'utiliser __.
|
| auteur : r0d |
Un type POD (de l'anglais Plain Old Data) est un type C++ qui a un équivalent en C, et qui utilise les mêmes règles que le C
pour l'initialisation, la copie et l'adressage.
La définition précise d'un type POD est récursive et un peu absconse. Voici une définition légèrement simplifiée :
les données membres non statiques d'un type POD doivent être publiques et peuvent être de ce type :
- bool ;
- tous les types numériques, y compris des divers char ;
- enumération ;
- pointeur de données (i.e, tous les types convertibles en void*) ;
- pointeur de fonction (mais pas un pointeur sur une fonction membre) ;
- type POD, y compris un tableau de POD.
Notez que les références ne sont pas permises pour un type POD. De plus,
un type POD ne peut avoir ni constructeur, ni fonction
virtuelle, ni classe de base (pas d'héritage), ni surcharge
d'opérateur d'assignation.
Dans certaines situations, le C++ permet uniquement l'utilisation de POD. Par exemple, une union ne peut pas contenir une
classe qui a des fonctions virtuelles ou des constructeurs non triviaux.
Les PODs peuvent également être utilisés pour interfacer du
code C++ avec du code C.
|
| auteur : Alp Mestan | Une lvalue est, à l'origine, une expression qui peut
se trouver du côté gauche lors d'une affectation, c'est-à-dire une
expression à laquelle on peut affecter quelque chose. Désormais, une
lvalue désigne toute expression qui fait référence à un objet,
c'est-à-dire toute expression qui fait référence à une région contigüe de
mémoire. Cela peut donc être par exemple un objet constant, ce qui ne
rentre pas forcément dans la définition utilisant l'affectation.
Par conséquent, les identifiants (de variables) sont des
lvalues. Il en va de même pour les références.
Voici un code qui va peut-être aider à la compréhension :
int n = 5 ;
Client& getClient () { }
getClient () = UnAutreClient;
std:: string s = " C++ " ;
|
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.
|
| auteur : Florian Goujeon |
La RVO (pour Return Value Optimization, optimisation de la
valeur de retour) est une optimisation automatique du code source
effectuée par la plupart des compilateurs C++.
Cette optimisation s'applique lors d'un appel de fonction renvoyant
un objet dont la copie est couteuse. Elle consiste à modifier la façon
dont la fonction renvoie son objet de retour, de telle façon à ce
qu'aucune copie dudit objet ne soit créée. Il en résulte alors un code
potentiellement bien plus rapide.
Prenons comme exemple le code suivant.
Celui-ci définit tout d'abord une classe car (voiture), composée de
nombreux objets. Cette classe comprend un constructeur, un constructeur
par copie et un destructeur.
class car
{
public :
car
(
const engine& e,
const wheels& w,
const doors& d,
const steering_wheel& s
);
car (const car& c);
~ car ();
private :
engine engine_;
wheels wheels_;
doors doors_;
steering_wheel steering_wheel_;
} ;
|
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,
const doors& d,
const steering_wheel& s
):
engine_ (e),
wheels_ (w),
doors_ (d),
steering_wheel_ (s)
{
std:: cout < < " Appel au constructeur de car.\n " ;
}
car:: car (const car& s):
engine_ (s.engine_),
wheels_ (s.wheels_),
doors_ (s.doors_),
steering_wheel_ (s.steering_wheel_)
{
std:: cout < < " Appel au constructeur par copie de car.\n " ;
}
car:: ~ car ()
{
std:: cout < < " Appel au destructeur de car.\n " ;
}
|
Notre code contient également une fonction build_sport_car(),
renvoyant un objet de type car :
const car
build_sport_car ()
{
const engine e = build_sport_engine ();
const wheels w = build_sport_wheels ();
const doors d = build_sport_doors ();
const steering_wheel s = build_sport_steering_wheel ();
return car (e, w, d, s);
}
|
Enfin, la fonction main() se contente d'appeler la fonction
build_sport_car() :
int
main ()
{
car my_car = build_sport_car ();
std:: cout < < " Fin du programme.\n " ;
return 0 ;
}
|
Si l'on désactive la RVO, le programme produit la sortie suivante :
Appel au constructeur de car. (1)
Appel au constructeur par copie de car. (2)
Appel au destructeur de car. (3)
Appel au constructeur par copie de car. (4)
Appel au destructeur de car. (5)
Fin du programme.
Appel au destructeur de car. (6)
|
- Tout d'abord, la fonction build_sport_car(), appelée depuis main(), crée une instance de car.
- 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().
- 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.
- De retour à la fonction main(), c'est au tour de l'objet temporaire anonyme d'être copié dans l'objet my_car...
- ... avant d'être détruit à son tour.
- Enfin, le programme se terminant, l'objet my_car est détruit. Il n'existe alors plus d'instance de car.
Activons maintenant la RVO (il s'agit du réglage par défaut de
tous les compilateurs supportant cette optimisation).
Le programme produit une sortie tout à fait différente.
Appel au constructeur de car.
Fin du programme.
Appel au destructeur de car.
|
Comme dans le cas précédent, la fonction build_sport_car() crée
l'instance de car. Mais cette fois-ci, aucun objet temporaire
n'est créé et aucune copie vers l'objet my_car n'est effectuée.
Le compilateur a détecté que l'instance anonyme de voiture créée
dans la fonction build_sport_car() devait être copiée vers
l'objet my_car.
Une fois l'optimisation appliquée, ces deux objets ne font plus
qu'un. Nous évitons alors deux copies et deux destructions, et
gagnons ainsi en vitesse d'exécution.
|
| auteur : Florian Goujeon |
Un type d'optimisation similaire peut également être appliqué dans le
cas où l'instance de retour de la fonction est nommée. On parle alors
de NRVO (Named Return Value Optimization, pour optimisation de
la valeur de retour nommée).
const car
build_sport_car ()
{
const engine e = build_sport_engine ();
const wheels w = build_sport_wheels ();
const doors d = build_sport_doors ();
const steering_wheel s = build_sport_steering_wheel ();
car sport_car (e, w, d, s);
sport_car.price (75000 );
sport_car.build_date (today ());
return sport_car;
}
|
|
| auteur : Florian Goujeon |
Là où il est intéressant d'être conscient de l'existence de ces deux
types d'optimisation, c'est que leur application dépend de la façon
dont sont écrites les fonctions concernées.
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 ();
car sport_car (e, w, d, s);
sport_car.price (75000 );
return sport_car;
}
else
{
const engine e = build_town_engine ();
const wheels w = build_town_wheels ();
const doors d = build_town_doors ();
const steering_wheel s = build_town_steering_wheel ();
car town_car (e, w, d, s);
town_car.price (28000 );
return town_car;
}
}
|
En revanche, un léger réaménagement du code rendra son
application possible :
const car
build_car (const bool sport_type = true )
{
std:: auto_ptr< engine> e;
std:: auto_ptr< wheels> w;
std:: auto_ptr< doors> d;
std:: auto_ptr< steering_wheel> s;
if (sport_type)
{
e = std:: auto_ptr< engine> (new engine (build_sport_engine ()));
w = std:: auto_ptr< wheels> (new wheels (build_sport_wheels ()));
d = std:: auto_ptr< doors> (new doors (build_sport_doors ()));
s = std:: auto_ptr< steering_wheel> (new steering_wheel (build_sport_steering_wheel ()));
}
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 ()));
}
car new_car (* e, * w, * d, * s);
new_car.price (sport_type ? 75000 : 28000 );
return new_car;
}
|
|
| auteur : Florian Goujeon |
Tout bon cours sur le C++ vous dira qu'il est préférable de passer
les objets aux fonctions par référence constante plutôt que par valeur.
Il existe cependant un cas où cette assertion est fausse : celui où
la (N)RVO s'en mêle.
Soit une fonction effectuant une copie, puis une suite de modifications
d'une instance de voiture. Nommons simplement cette fonction
copy_and_modify_car().
La fonction copy_and_modify_car() est appelée depuis la fonction
main() de la façon suivante :
int
main ()
{
copy_and_modify_car (build_car ());
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);
}
|
La (N)RVO agissant, le programme produit la sortie suivante :
Appel au constructeur de car.
Appel au constructeur par copie de car.
Appel au destructeur de car.
Fin du programme.
Appel au destructeur de car.
|
Écrivons maintenant une nouvelle version de cette fonction, en
passant l'instance par valeur :
void
copy_and_modify_car (car c)
{
}
|
Contre toute attente, aucune copie implicite n'est créée :
Appel au constructeur de car.
Fin du programme.
Appel au destructeur de car.
|
Le principe de la (N)RVO (la suppression des objets temporaires
intermédiaires) est ici étendu au passage d'objet.
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);
return 0 ;
}
|
|
| auteur : Florian Goujeon |
Il est important de noter que cette optimisation est
implicite. Par conséquent, elle ne permet en aucun cas de retourner
des objets d'un type défini comme non-copiable.
En effet, l'écriture d'une fonction retournant un tel objet mènerait
à une erreur de compilation, même si en pratique aucune copie n'aurait
été effectuée.
Heureusement, le prochain standard du langage C++ (nom de code C++0x)
introduit la notion de sémantique de mouvement, permettant de passer
outre cette limitation.
|
| auteur : dourouc05 |
Les variables volatiles sont des variables qui peuvent être modifiées
par un processus hors de contrôle du compilateur. On doit accéder à
une copie de la variable si elle a pu être modifiée.
Seules des fonctions membres volatiles peuvent être appelées pour
des objets volatils, et des fonctions membres volatiles ne peuvent
appeler que des autres fonctions membres volatiles. La viralité peut
être détournée.
Le compilateur s'assure qu'aucune fonction membre n'ait d'optimisation
quant aux références mémoire. En l'absence du mot-clef
volatile, il peut optimiser le code de la
fonction membre.
Cependant, la présence de ce mot-clef n'empêchera pas le processeur
d'optimiser pour la cohérence des caches.
Utilisez ce mot-clef pour des variables que vous ne voulez pas voir
optimisées par le compilateur.
Il est faux de penser que ce mot-clef apporte la moindre sécurité
face au mutlithread. Il faut pour cela attendre
le mot-clef atomic du C++0x.
Voici un petit exemple d'utilisation.
class Gadget
{
public :
void attendre ()
{
while (! _drapeau)
{
dormir (1000 );
}
}
void seLever ()
{
_drapeau = true ;
}
...
private :
volatile bool _drapeau;
} ;
|
Cette classe est prévue pour attendre qu'un autre thread vienne
modifier une de ses variables membres, et vérifier la situation
toutes les secondes.
Si la variable n'avait pas été déclarée volatile, le compilateur
aurait cru qu'il n'y a qu'un appel à une fonction externe qui ne
peut modifier le drapeau. Il en aurait conclu qu'il peut mettre le
drapeau en cache. Ceci fonctionne très bien quand il n'y a qu'un
thread. Avec le mot-clef, le compilateur comprend que la variable
peut être modifiée de l'extérieur, et ne la mettra pas en cache.
|
lien :
Volatile - Multithreaded Programmer's Best Friend,
par Andrei Alexandrescu
|
| auteur : white_tentacle |
On parle d'immuabilité (ou non-mutabilité) d'un élément
(au sens large) lorsque celui-ci ne change pas, tout au long de sa durée
de vie. En C++, il faut distinguer plusieurs cas d'immuabilité :
- immuabilité de membres ;
- immuabilité des paramètres ;
- fonctions membres ne modifiant pas l'objet ;
- types immuables.
L'immuabilité en C++ se déclare au moyen du mot clé const.
Pour l'immuabilité d'un membre :
class C {
int const TAILLE_MAX;
} ;
|
Une fois affectée (dans le constructeur), TAILLE_MAX ne pourra plus
être modifiée. const s'applique normalement à ce qui
le précède (à sa gauche), et à défaut à ce qui
le suit (à sa droite).
On prendra donc l'habitude d'écrire int const plutôt que
const int, même si les deux sont équivalents, et on comprendra
alors plus facilement la différence entre int const * et
int * const.
Pour l'immuabilité d'un paramètre :
int longueur (std:: string const & chaine)
{
}
|
Ici, on garantit à l'appelant qu'on ne modifie pas le paramètre
qu'il nous donne (bien que celui-ci soit passé par référence).
L'appelant peut se baser sur cet engagement lorsqu'il valide
son algorithme.
Fonctions membres ne modifiant pas l'objet :
class chaine {
public :
int longueur () const ;
[...]
} ;
|
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...).
|
Consultez les autres F.A.Q.
|
|