| ||
auteurs : Musaran, Laurent Gomila | ||
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 :
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 :
|
| ||
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. |
| ||
auteurs : LFE, Aurélien Regat-Barrel | ||
Le membre static doit être initialisé dans le fichier .cpp de la façon suivante.
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).
Note : il existe un raccourci pour les variables statiques entières constantes, voir Que signifie la déclaration suivante : 'static const int MAX = 10' ?.
|
| ||||
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).
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
La place habituelle pour définir une donnée membre static de la classe Fred est dans le fichier Fred.cpp
|
| ||
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 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.
|
| ||||||||||
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
Le fichier y.cpp définit l'objet y:
Pour être complet, le constructeur de Barney pourrait ressembler à quelque chose comme :
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.
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() :
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.
|
| ||||
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 :
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
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
en
nous gérons toujours correctement l'initialisation, mais nous ne gérons plus la libération. Par exemple, s'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 : 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++.
|
| |||||||||||
auteur : Marshall 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
Naturellement, le membre statique est initialisé séparément :
L'objet Fred va être utilisé dans une ou plusieurs fonctions membres de X :
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
Naturellement, ce membre statique est initialisé séparément.
Il suffit ensuite simplement de remplacer toutes les utilisations de x_ par x()
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().
Note : le fiasco dans l'ordre d'initialisation des variables statiques peut aussi se produire, dans certains cas, avec 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
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.
il est possible de simplifier ce code en déplaçant le code d'initialisation pour x et y dans leur fonction respective
Et en supprimant les affichages, nous pouvons simplifier le code en quelque chose de très simple
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.
|
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 © 2008 Developpez LLC. Tous droits réservés Developpez LLC. Aucune reproduction, même partielle, ne peut être faite de ce site ni 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.