FAQ C++Consultez toutes les FAQ
Nombre d'auteurs : 34, nombre de questions : 368, dernière mise à jour : 14 novembre 2021 Ajouter une question
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 ceci.
Sur ce, nous vous souhaitons une bonne lecture.
- Comment exécuter une commande système ou un autre programme ?
- Quelles sont les questions à poser pour savoir si un candidat connaît vraiment son sujet ?
- Comment accélérer la compilation de mes projets ?
- Mes calculs sur nombres flottants sont imprécis, que faire ?
- Comment connaître les macros prédéfinies pour les différentes plateformes ?
- Que sont les rvalues et lvalues ?
- Qu'est-ce que le mot-clef volatile ?
- Qu'est-ce que l'immuabilité ?
- Comment charger explicitement une bibliothèque dynamique ?
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.
Code c++ : | Sélectionner tout |
1 2 3 4 5 6 7 8 9 10 | #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" ); } |
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++).
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.
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.
- 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.hCode c++ : Sélectionner tout 1
2
3
4
5
6
7
8
9class Classe1; // pas d'inclusion de "Classe1.h" class Classe2; // pas d'inclusion de "Classe2.h" class MaClasse { void Something(const Classe1& c); Classe2* c2; };
Code c++ : Sélectionner tout 1
2
3
4
5
6
7
8#include "Classe1.h" #include "Classe2.h" void MaClasse::Something(const Classe1& c) { // Ici on peut accéder aux membres de Classe1 et Classe2, // on a inclus leurs en-têtes }
- 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)Code c++ : Sélectionner tout 1
2
3
4
5
6
7
8
9
10
11
12
13class MaClasseImpl; class MaClasse { public : void Set(int); int Get() const; private : MaClasseImpl* pImpl; };
Code c++ : Sélectionner tout 1
2
3
4
5
6
7
8
9
10
11#include "MaClasseImpl.h" void MaClasse::Set(int x) { pImpl->Set(x); } int MaClasse::Get() const { return pImpl->Get(); }
Code c++ : Sélectionner tout 1
2
3
4
5
6
7
8
9
10
11class MaClasseImpl { public : void Set(int); int Get() const; private : int val; };
Code c++ : Sélectionner tout 1
2
3
4
5
6
7
8
9
10
11#include "MaClasseImpl.h" void MaClasseImpl::Set(int x) { val = x; } int MaClasseImpl::Get() const { return val; }
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 :
- 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.
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.
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 :
Code c++ : | Sélectionner tout |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | #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; // Version incorrecte ne tenant pas compte des imprécisions if (f3 == 1.0f) { // Pratiquement jamais vrai ! } // Version correcte tenant compte des imprécisions // err est une variable choisie en fonction de l'erreur autorisée if (abs(f3 - 1.0f) <= err * max(abs(f3), abs(1.0f)) * numeric_limits<float>::epsilon()) { // Ok } |
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 :
Code c++ : | Sélectionner tout |
1 2 3 4 5 6 7 8 | #if defined (linux) // Code spécifique à Linux #elif defined (_WIN32) // Code spécifique à Windows #else // Plateforme non gérée, on peut par exemple placer une erreur de compilation #error Plateforme non gérée #endif |
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.
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 :
Code c++ : | Sélectionner tout |
1 2 3 4 5 6 | int n = 5; // n est une lvalue Client& getClient() { /* ... */ } getClient() = UnAutreClient; // getClient() désigne ici une lvalue car la référence renvoyée correspond à la définition d'une lvalue std::string s = "C++"; // s est une lvalue // ... |
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.
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.
Code c++ : | Sélectionner tout |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | class Gadget { public: void attendre() { while (! _drapeau) { dormir(1000); // attend 1000 ms } } void seLever() { _drapeau = true; } // ... private: volatile bool _drapeau; }; |
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.
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 :
Code c++ : | Sélectionner tout |
1 2 3 4 | class C { int const TAILLE_MAX; // [...] }; |
Pour l'immuabilité d'un paramètre :
Code c++ : | Sélectionner tout |
1 2 3 4 | int longueur(std::string const & chaine) { // [...] } |
Fonctions membres ne modifiant pas l'objet :
Code c++ : | Sélectionner tout |
1 2 3 4 5 | class chaine { public: int longueur() const; // [...] }; |
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 :
Code c++ : Sélectionner tout 1
2
3
4
5class A { int * m_val; public: int IncrementByOne() const { return ++(*m_val); } };
- 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.).
Il arrive que l'on veuille charger une bibliothèque dynamique (.dll, .so) explicitement depuis le code C++, que ce soit pour implémenter un système de plugins ou tout simplement car on ne dispose pas des fichiers de lien statique (.lib, .a). Cette manipulation est spécifique à chaque système, mais on peut cependant remarquer que les principes et les fonctions mis en jeu sont pratiquement équivalents, au nom près.
En l'occurrence, cela se fait en trois étapes :
- Charger la bibliothèque dynamique
- Récupérer les fonctions qu'elle exporte
- Décharger la bibliothèque dynamique
Voici un code qui met en œuvre 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 :
Code c++ : | Sélectionner tout |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 | // Version pour Windows #if defined(_WIN32) || defined(__WIN32__) #include <windows.h> #define DYNLIB_HANDLE HMODULE #define DYNLIB_LOAD( a ) LoadLibrary( a ) #define DYNLIB_GETSYM( a, b ) GetProcAddress( a, b ) #define DYNLIB_UNLOAD( a ) !FreeLibrary( a ) #define DYNLIB_ERROR( ) "Unknown Error" // Version pour Linux #elif defined(linux) || defined(__linux) #include <dlfcn.h> #define DYNLIB_HANDLE void* #define DYNLIB_LOAD( a ) dlopen( a, RTLD_LAZY ) #define DYNLIB_GETSYM( a, b ) dlsym( a, b ) #define DYNLIB_UNLOAD( a ) dlclose( a ) #define DYNLIB_ERROR( ) dlerror( ) #endif #include <iostream> int main() { // Chargement de la bibliothèque DYNLIB_HANDLE Lib = DYNLIB_LOAD("library"); if (!Lib) { std::cerr << DYNLIB_ERROR() << std::endl; return EXIT_FAILURE; } // Importation de la fonction qui nous intéresse typedef int (*FuncType)(float); FuncType Func = static_cast<FuncType>(DYNLIB_GETSYM(Lib, "Function")); if (!Func) { std::cerr << DYNLIB_ERROR() << std::endl; return EXIT_FAILURE; } // Appel de la fonction importée int x = Func(5.f); // Déchargement de la bibliothèque if (DYNLIB_UNLOAD(Lib)) { std::cerr << DYNLIB_ERROR() << std::endl; return EXIT_FAILURE; } return EXIT_SUCCESS; } |
Proposer une nouvelle réponse sur la FAQ
Ce n'est pas l'endroit pour poser des questions, allez plutôt sur le forum de la rubrique pour çaLes sources présentées sur cette page sont libres de droits et vous pouvez les utiliser à votre convenance. Par contre, la page de présentation constitue une œuvre intellectuelle protégée par les droits d'auteur. Copyright © 2024 Developpez Developpez LLC. Tous droits réservés Developpez LLC. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents et images sans l'autorisation expresse de Developpez LLC. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.