IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)
logo

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.

SommaireLes classes en C++Les données et fonctions membres statiques (10)
précédent sommaire suivant
 

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(); 
}

Mis à jour le 19 octobre 2004 Aurelien.Regat-Barrel LFE

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 
};
Pour correctement définir les variables statiques qui ne sont pas entières ou constantes, voir Comment initialiser un membre static ?.

À 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}; 
};

Mis à jour le 26 janvier 2014 Laurent Gomila Musaran

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.

Mis à jour le 9 octobre 2003 LFE

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;
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).

Code c++ : Sélectionner tout
1
2
3
4
5
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 » ?.

Mis à jour le 17 octobre 2005 Aurelien.Regat-Barrel LFE

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_ 
    ... 
};
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

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_;
La place habituelle pour définir une donnée membre static de la classe Fred est dans le fichier Fred.cpp

Mis à jour le 5 mars 2004 Cline

Un moyen subtil de planter votre programme.

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

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

C'est aussi simple que cela.

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

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

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

Mis à jour le 5 mars 2004 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

Code c++ : Sélectionner tout
1
2
3
#include "Fred.hpp" 
  
Fred x;
Le fichier y.cpp définit l'objet y:

Code c++ : Sélectionner tout
1
2
3
#include "Barney.hpp" 
  
Barney y;
Pour être complet, le constructeur de Barney pourrait ressembler à quelque chose comme :

Code c++ : Sélectionner tout
1
2
3
4
5
6
7
8
#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.

Code c++ : Sélectionner tout
1
2
3
4
5
6
7
#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() :

Code c++ : Sélectionner tout
1
2
3
4
5
6
7
8
#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.

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

Mis à jour le 5 mars 2004 Cline

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

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

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

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; 
}
Cependant, il apparaît (ou du moins, il peut apparaître) un problème relativement subtil avec ce changement. Pour comprendre ce problème potentiel, il faut se souvenir pourquoi nous faisons cela : Nous avons besoin d'être sûr à 100 % que notre objet statique
  • est construit avant sa toute première utilisation
  • n'a pas besoin d'être détruit après sa dernière utilisation

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

En changeant la déclaration

Code c++ : Sélectionner tout
static Fred* ans = new Fred();
en

Code c++ : Sélectionner tout
static Fred ans;
nous gérons toujours correctement l'initialisation, mais nous ne gérons plus la libération. Par exemple, s'il y a trois 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 anssoit détruit après la destruction du dernier des trois objets.

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++.

Mis à jour le 5 mars 2004 Cline

Simplement en utilisant la méthode décrite juste avant, mais cette fois, en utilisant une fonction membre statique plutôt qu'une fonction globale.

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

Code c++ : Sélectionner tout
1
2
3
4
5
6
7
8
class X 
{ 
public: 
    // ... 
  
private: 
    static Fred x_; 
};
Naturellement, le membre statique est initialisé séparément :

Code c++ : Sélectionner tout
1
2
3
#include "X.hpp" 
  
Fred X::x_;
L'objet Fred va être utilisé dans une ou plusieurs fonctions membres de X :

Code c++ : Sélectionner tout
1
2
3
4
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

Code c++ : Sélectionner tout
1
2
3
4
5
6
7
8
class X 
{ 
public: 
    // ... 
  
private: 
    static Fred& x(); 
};
Naturellement, ce membre statique est initialisé séparément.

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; 
}
Il suffit ensuite simplement de remplacer toutes les utilisations de x_ par x()
Code c++ : Sélectionner tout
1
2
3
4
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().

Code c++ : Sélectionner tout
1
2
3
4
5
void X::someMethod() 
{ 
    static Fred& x = X::x(); 
    x.goBowling(); 
}
Note : le fiasco dans l'ordre d'initialisation des variables statiques peut aussi se produire, dans certains cas, avec les types de base.

Mis à jour le 17 mars 2008 Cline

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; 
}
La sortie de ce petit programme montre qu'il utilise y avant qu'il ait été initialisé. La solution, comme précédemment, est d'utiliser l'idiome de la construction à la première utilisation.

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; 
}
il est possible de simplifier ce code en déplaçant le code d'initialisation pour x et y dans leur fonction respective

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; 
}
Et en supprimant les affichages, nous pouvons simplifier le code en quelque chose de très simple

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; 
}
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.

Mis à jour le 5 mars 2004 Cline

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 ça


Réponse à la question

Liens sous la question
précédent sommaire suivant
 

Les 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.