Accueil
Accueil Le Club Delphi Kylix C C++ Java J2EE DotNET & C# Visual Basic Access Pascal Dev Web PHP ASP XML UML SQLSGBD Windows Linux Autres
logo

precedent    sommaire    suivant   


Que signifie la déclaration suivante : "static const int MAX = 10" ?
auteurs : Musaran, Loulou24
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};
};

Pourquoi déclarer un membre static dans une classe ?
auteur : LFE
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.


Comment initialiser un membre static ?
auteurs : LFE, Aurelien.Regat-Barrel
Le membre static doit être initialisé dans le fichier .cpp de la façon suivante.

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

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" ?.


Pourquoi les classes avec des membres statiques me donnent-elles des erreurs lors de l'édition des liens ?
auteur : Marshall Cline
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).

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"
 
int Fred::j_ = some_expression_evaluating_to_an_int;
 
// A 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


Qu'est-ce que le "fiasco dans l'ordre d'initialisation des variables statiques" ?
auteur : Marshall 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 l'objet x n'a pas encore été 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" ?
auteur : 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;
Le fichier y.cpp définit l'objet y:

y.cpp
#include "Barney.hpp"

Barney y;
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.

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


Pourquoi l'idiome de construction à la première utilisation utilise-t-il un pointeur statique plutôt qu'un objet statique ?
auteur : Marshall 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 :

x.cpp
 #include "Fred.hpp"
 
 Fred& x()
 {
   static Fred ans;  // was static Fred* ans = new Fred();
   return ans;       // was 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

static Fred* ans = new Fred();
en

static Fred ans;
nous gérons toujours correctement l'initialisation, mais nous ne gérons plus la libération. Par exemple, si 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.

La situation est simple ; si il y a un autre objet statique dont le destructeur a besoin de ans après qu'il ait é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++.


Comment puis-je éviter le "fiasco dans l'ordre d'initialisation des variables statiques" pour les données membres statiques ?
auteur : Marshall Cline
Simplement en utilisant la méthode décrite juste avant, mais cette fois, en utilisant une fonction membres statique plutôt qu'une fonction globale.

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

x.hpp
class X {
    public:
        ...
 
    private:
        static Fred x_;
};
Naturellement, le membre statique est initialisé séparément :

x.cpp
#include "X.hpp"
 
Fred X::x_;
L'objet Fred va être utilisé dans une ou plusieurs fonctions membres de 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 {
    public:
        ...
 
    private:
        static Fred& x();
};
Naturellement, ce membre statique est initialisé séparément.

x.cpp
#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 c()

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 fiasco dans l'ordre d'initialisation des variables statiques peut aussi se produire, dans certains cas, avec les types de base.


Dois-je me préoccuper du "fiasco dans l'ordre d'initialisation des variables statiques" pour les types de base ?
auteur : Marshall 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

#include <iostream>
 
int f();  // forward declaration
int g();  // forward declaration
 
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, et d'utiliser l'idiome de la construction à la première utilisation.

#include <iostream>
 
int f();  // forward declaration
int g();  // forward declaration
 
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& y();  // forward declaration
 
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

int& y();  // forward declaration
 
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), elle eut donc redevenir une simple variable.


precedent    sommaire    suivant   

Consultez les autres F.A.Q's

Valid XHTML 1.1!Valid CSS!


Ce document issu de http://www.developpez.com est soumis à la licence GNU FDL traduit en français ici.
Permission vous est donnée de distribuer, modifier des copies de cette page tant que cette note apparaît clairement.
Certaines parties de ce document sont sous copyright Marshall Cline Les codes sources présentés sur cette page sont libres de droits, et vous pouvez les utiliser à votre convenance. Pour le reste, ce document constitue une oeuvre intellectuelle protégée par les droits d'auteurs. Ce document issu de http://www.developpez.com est soumis à trois licences, en fonction des contributeurs :
- Les contributions de Clément Cunin et LFE sont soumises aux termes de la la licence GNU FDL traduite en français ici. Permission vous est donnée de distribuer, modifier des copies des contributions de Clément Cunin et LFE tant que cette note apparaît clairement :
"Ce document issu de http://www.developpez.com est soumis à la licence GNU FDL traduite en français ici. Permission vous est donnée de distribuer, modifier des copies de cette page tant que cette note apparaît clairement".
- Les contributions de Marshall Cline sont sous copyright
- Pour ce qui est des autres contributions : Copyright © 2005 Developpez LLC : Tous droits réservés Developpez LLC. Aucune reproduction, ne peut en être faite sans l'autorisation expresse de Developpez LLC. Sinon vous encourez selon la loi jusqu'à 3 ans de prison et jusqu'à 300 000 E de dommages et intérêts. Cette page est déposée à la SACD.
Vos questions techniques : forum d'entraide Accueil - Publiez vos articles, tutoriels, cours et rejoignez-nous dans l'équipe de rédaction du club d'entraide des développeurs francophones. Nous contacter - Copyright 2000..2005 www.developpez.com