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.
- Quelle est la particularité d'une fonction membre static ?
- Que signifie la déclaration suivante : « static const int MAX = 10 » ?
- Pourquoi déclarer un membre static dans une classe ?
- Comment initialiser un membre static ?
- Pourquoi les classes avec des membres statiques me donnent-elles des erreurs lors de l'édition des liens ?
- Qu'est-ce que le « fiasco dans l'ordre d'initialisation des variables statiques » ?
- Comment puis-je éviter le « fiasco dans l'ordre d'initialisation des variables statiques » ?
- Pourquoi l'idiome de construction à la première utilisation utilise-t-il un pointeur statique plutôt qu'un objet statique ?
- Comment puis-je éviter le « fiasco dans l'ordre d'initialisation des variables statiques » pour les données membres statiques ?
- Dois-je me préoccuper du « fiasco dans l'ordre d'initialisation des variables statiques » pour les types de base ?
Une fonction membre déclarée static a la particularité de pouvoir être appelée sans devoir instancier la classe.
Elle ne peut utiliser que des variables et des fonctions membres static elles aussi, c'est-à-dire qui ont une existence en dehors de toute instance.
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 | class A { public: // variable et fonction non statiques int var1; void f1() {}; // variable et fonction statiques static int var2; static void f2() {}; }; // IMPORTANT : il faut définir la variable static int A::var2 = 0; int main() { A a; // instance de A // var1 et f1 nécessitent une instance de A a.var1 = 1; a.f1(); // var2 et f2 sont static et n'ont pas besoin d'instance A::var2 = 1; A::f2(); } |
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 :
Code c++ : | Sélectionner tout |
1 2 3 4 5 6 | 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 }; |
À 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 :
Code c++ : | Sélectionner tout |
1 2 3 4 | class MaClasse { enum {MAX = 10}; }; |
Déclarer un membre static dans une classe permet de n'avoir qu'une instance de ce membre en mémoire. Toute modification effectuée sur ce membre dans une instance de cette classe sera visible par les autres instances.
On peut voir cela comme une façon de mettre en place un mécanisme de « variables globales » internes à la classe, par exemple.
Le membre static doit être initialisé dans le fichier .cpp de la façon suivante.
Code c++ : | Sélectionner tout |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | // dans le fichier Exemple.h #ifndef EXEMPLE_H #define EXEMPLE_H class Exemple { public: static int compteur; }; #endif // dans le fichier Exemple.cpp #include "Exemple.h" int Exemple::compteur = 0; |
Code c++ : | Sélectionner tout |
1 2 3 4 5 | Exemple::Exemple() : compteur(5) // ERREUR { } |
Parce que les données membres statiques doivent être explicitement définies dans exactement une unité de compilation.
Si vous n'avez pas fait cela, vous avez certainement eu une erreur du type « undefined external » (référence externe indéfinie) par l'éditeur de liens (linker).
Code c++ : | Sélectionner tout |
1 2 3 4 5 6 7 | class Fred { public: ... private: static int j_; // Déclare la donnée membre static Fred::j_ ... }; |
Code c++ : | Sélectionner tout |
1 2 3 4 5 6 | #include "Fred.h" int Fred::j_ = some_expression_evaluating_to_an_int; // À côté de ça, si vous souhaitez utiliser la valeur implicite des entiers static : // int Fred::j_; |
Un moyen subtil de planter votre programme.
Le fiasco dans l'ordre d'initialisation des variables statiques est un des aspects les plus subtils et habituellement mal compris du C++. Malheureusement, il est très difficile à détecter étant donné que l'erreur se produit avant même le début de l'exécution du main().
Supposons que l'on ait deux objets statiques x et y qui se trouvent dans des fichiers sources séparés (x.cpp et y.cpp) Supposons ensuite que l'initialisation de l'objet y (typiquement le constructeur de l'objet y) appelle une fonction membre de l'objet x.
C'est aussi simple que cela.
La tragédie est que vous avez 50% de chances de vous planter. S'il arrive que l'unité de compilation correspondant à x.cpp soit initialisée avant celle correspondant à y.cpp, tout va bien. Mais si l'unité de compilation correspondant à y.cpp est initialisée d'abord, alors le constructeur de y sera en route avant le constructeur de x, et vous êtes cuit. C'est-à-dire que le constructeur de y appellera une fonction de l'objet x, alors que ce dernier n'est pas encore construit.
Si vous pensez que c'est « excitant » de jouer à la roulette russe avec la moitié du barillet chargé, vous pouvez vous arrêter de lire ici. Si au contraire vous aimez augmenter vos chances de survie en prévenant les désastres de manière systématique, vous serez probablement intéressé par la question suivante.
Note : le fiasco dans l'ordre d'initialisation des variables statiques peut aussi se produire, dans certains cas, avec les types de base.
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
Code c++ : | Sélectionner tout |
1 2 3 | #include "Fred.hpp" Fred x; |
Code c++ : | Sélectionner tout |
1 2 3 | #include "Barney.hpp" Barney y; |
Code c++ : | Sélectionner tout |
1 2 3 4 5 6 7 8 | #include "Barney.hpp" Barney::Barney() { // ... x.goBowling(); // ... } |
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.
Code c++ : | Sélectionner tout |
1 2 3 4 5 6 7 | #include "Fred.hpp" Fred& x() { static Fred* ans = new Fred(); return *ans; } |
Code c++ : | Sélectionner tout |
1 2 3 4 5 6 7 8 | #include "Barney.hpp" Barney::Barney() { // ... x().goBowling(); // ... } |
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.
Note : 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 courte : il est possible d'utiliser un objet statique plutôt qu'un pointeur statique, mais faire cela ouvre la porte à un autre problème aussi subtil que pervers.
La réponse longue : parfois, les gens s'inquiètent des problèmes de fuite mémoire de la solution précédente. Dans la plupart des cas, ce n'est pas un problème, mais par contre cela peut en être un dans d'autres circonstances. Note : Même si l'objet pointé par ans dans la question précédente n'est jamais libéré, la mémoire n'est pas perdue quand le programme se termine, étant donné que l'OS récupère automatiquement l'entièreté de la mémoire allouée au programme quand celui-ci se termine. En d'autres mots, le seul moment ou vous devez vous en inquiéter est celui où le destructeur de l'objet Fred effectue certaines actions importantes (par exemple, écrire quelque chose dans un fichier) qui doit être effectué lorsque le programme se termine.
Dans ce cas, où l'objet construit à la première utilisation (Fred dans ce cas) a éventuellement besoin d'être détruit, vous pouvez changer la fonction x() comme suit :
Code c++ : | Sélectionner tout |
1 2 3 4 5 6 7 | #include "Fred.hpp" Fred& x() { static Fred ans; // au lieu de static Fred* ans = new Fred(); return ans; // au lieu de return *ans; } |
- est construit avant sa toute première utilisation
- n'a pas besoin d'être détruit après sa dernière utilisation
Il est évident qu'il serait désastreux qu'un objet statique soit utilisé avant sa construction ou après sa destruction. L'idée ici est que vous devez vous inquiéter de deux situations et non pas simplement d'une des deux.
En changeant la déclaration
Code c++ : | Sélectionner tout |
static Fred* ans = new Fred();
Code c++ : | Sélectionner tout |
static Fred ans;
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++.
Simplement en utilisant la méthode décrite juste avant, mais cette fois, en utilisant une fonction membre statique plutôt qu'une fonction globale.
Supposons que l'on ait une classe X qui a un objet Fred statique
Code c++ : | Sélectionner tout |
1 2 3 4 5 6 7 8 | class X { public: // ... private: static Fred x_; }; |
Code c++ : | Sélectionner tout |
1 2 3 | #include "X.hpp" Fred X::x_; |
Code c++ : | Sélectionner tout |
1 2 3 4 | void X::someMethod() { x_.goBowling(); } |
Quoi qu'il en soit, il est portable et prudent de transformer la donnée membre statique X::x_ en une fonction membre statique
Code c++ : | Sélectionner tout |
1 2 3 4 5 6 7 8 | class X { public: // ... private: static Fred& x(); }; |
Code c++ : | Sélectionner tout |
1 2 3 4 5 6 7 | #include "X.hpp" Fred& X::x() { static Fred* ans = new Fred(); return *ans; } |
Code c++ : | Sélectionner tout |
1 2 3 4 | void X::someMethod() { x().goBowling(); } |
Code c++ : | Sélectionner tout |
1 2 3 4 5 | void X::someMethod() { static Fred& x = X::x(); x.goBowling(); } |
Oui.
Si vous initialisez les types de base en utilisant une fonction, le fiasco dans l'ordre d'initialisation des variables statiques peut vous causer des problèmes aussi bien qu'avec des classes définies par vous.
Le code suivant expose le problème
Code c++ : | Sélectionner tout |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | #include <iostream> int f(); // déclaration anticipée 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; } |
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 | #include <iostream> int f(); // déclaration anticipée int g(); // déclaration anticipée int& x() { static int ans = f(); return ans; } int& y() { static int ans = g(); return ans; } int f() { std::cout << "using 'y' (which is " << y() << ")\n"; return 3*y() + 7; } int g() { std::cout << "initializing 'y'\n"; return 5; } |
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 | #include <iostream> int& y(); // déclaration anticipée int& x() { static int ans; static bool firstTime = true; if (firstTime) { firstTime = false; std::cout << "using 'y' (which is " << y() << ")\n"; ans = 3*y() + 7; } return ans; } int& y() { static int ans; static bool firstTime = true; if (firstTime) { firstTime = false; std::cout << "initializing 'y'\n"; ans = 5; } return ans; } |
Code c++ : | Sélectionner tout |
1 2 3 4 5 6 7 8 9 10 11 12 13 | int& y(); // déclaration anticipée int& x() { static int ans = 3*y() + 7; return ans; } int& y() { static int ans = 5; return ans; } |
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.