L’ordre d’initialisation globale C ++ ignore les dépendances?

Je pense que mon problème est mieux décrit dans le code:

#include  struct Foo; extern Foo globalFoo; struct Foo { Foo() { printf("Foo::Foo()\n"); } void add() { printf("Foo::add()\n"); } static int addToGlobal() { printf("Foo::addToGlobal() START\n"); globalFoo.add(); printf("Foo::addToGlobal() END\n"); return 0; } }; Foo globalFoo; int dummy = Foo::addToGlobal(); int main() { printf("main()\n"); return 0; } 

Les impressions ci-dessus (avec gcc 4.4.3):

 Foo::Foo() Foo::addToGlobal() START Foo::add() Foo::addToGlobal() END main() 

C’est ce que j’attends et semble logique.

Cependant, lorsque j’échange les lignes suivantes:

 Foo globalFoo; int dummy = Foo::addToGlobal(); 

dans ceci:

 int dummy = Foo::addToGlobal(); Foo globalFoo; 

le programme génère les éléments suivants:

 Foo::addToGlobal() START Foo::add() Foo::addToGlobal() END Foo::Foo() main() 

Il semble que les méthodes d’instance de Foo soient appelées à l’aide d’une instance qui n’a pas encore été construite ! Quelque chose d’aussi simple que de déplacer la déclaration d’une variable dans la scope globale affecte le comportement du programme, et cela me porte à croire (1) l’ordre d’initialisation des globals n’est pas défini et (2) l’ordre d’initialisation des globals ignore toutes les dépendances. Est-ce correct? Est-il possible de s’assurer que le constructeur de Foo est appelé avant d’initialiser dummy ?

Le problème que j’essaie de résoudre est le remplissage statique d’un référentiel d’éléments (une instance statique de Foo ). Dans ma tentative actuelle, j’utilise une macro qui crée (entre autres choses) une variable globale (dans un espace de noms anonyme pour éviter les conflits de noms) dont l’initialisation déclenche l’initialisation statique. Peut-être que j’aborde mon problème sous le mauvais angle? Existe-t-il une meilleure alternative? Merci.

Dans l’ordre d’initialisation, lisez la réponse ici .

Pour résoudre le problème d’initialisation, vous pouvez pousser le global à devenir une variable locale statique dans une fonction. La norme garantit que la variable locale statique sera initialisée lors du premier appel à la fonction:

 class Foo { public: static Foo& singleton() { static Foo instance; return instance; } }; 

Ensuite, vos autres variables globales accéderaient à la variable de la manière suivante:

 Foo::singleton().add(); 

Notez que ceci n’est généralement pas considéré comme une bonne conception et que même si cela résout les problèmes d’initialisation, cela ne résout pas l’ordre de finalisation. Vous devez donc faire attention à ne pas accéder au singleton une fois qu’il a été détruit.

(1) l’ordre d’initialisation des globaux n’est pas défini

Les variables globales d’une seule unité de traduction (fichier source) sont initialisées dans l’ordre dans lequel elles ont été définies.

L’ordre d’initialisation des variables globales dans différentes unités de traduction n’est pas spécifié.

(2) l’ordre d’initialisation des globales ignore toutes les dépendances

Droite.

Est-il possible de s’assurer que le constructeur de Foo est appelé avant d’initialiser dummy?

Oui, si globalFoo est défini avant le dummy et qu’ils se trouvent dans la même unité de traduction.

Une option serait d’avoir un pointeur statique sur l’instance globale; un tel pointeur sera initialisé à null avant toute initialisation dynamic; addToGlobal peut alors tester si le pointeur est null; Si c’est le cas, c’est la première fois que le global est utilisé et addToGlobal peut créer le Foo global.

Vous avez raison, l’initialisation des globales entre les unités de traduction n’est pas définie. Il est possible de contourner cela en utilisant le motif singleton . Toutefois, sachez que ce modèle est souvent utilisé à mauvais escient. Sachez également que l’ordre ou la destruction des globaux est également indéfini, au cas où vous auriez des dépendances dans les destructeurs.

C ++ n’a rien de pragma elaborate au pragma elaborate Ada , vous ne pouvez donc faire aucune supposition quant à l’ordre dans lequel les initiations seront effectuées. Désolé. C’est nul, mais c’est la conception.

Pourquoi ne pas faire en sorte que la variable globale statique soit un pointeur initialisé à nullptr. Ensuite, avant qu’un autre object global essaie d’utiliser l’object, vérifiez sa création et créez-le si nécessaire. Cela a fonctionné pour moi pour créer un registre global de créateurs de classes où l’on pourrait append de nouvelles classes sans changer le fichier qui gérait le registre. c’est à dire

 class Factory { static map* theTable; static void register1(const ssortingng& ssortingng, Creator* creator); ... }; ... map* Factory::theTable= nullptr; void Factory::register1(const ssortingng& theName, Creator* creator) { if (!theTable) theTable=new map; (*theTable)[theName]=creator; } 

Ceci compilé et travaillé avec VC ++ dans Visual Studio 2015.

J’avais essayé d’utiliser avant cette

 class Factory { public: static map theTable; static map& getTable(); static void register1(const ssortingng& ssortingng, Creator* creator); } map Factory::theTable; map& Factory::getTable() { return theTable; } void Factory::register1(const ssortingng& theSsortingng, Creator* creator) { getTable()[theSsortingng]=creator; // fails if executed before theTable is created } 

mais des exceptions étaient toujours générées lorsque laTable n’était pas créée avant d’essayer d’insérer une entrée dans la carte, ce qui peut arriver si l’enregistrement de la classe est géré dans une unité de compilation distincte de la logique d’usine.

Les variables globales d’une seule unité de traduction (fichier source) sont initialisées dans l’ordre dans lequel elles ont été définies.

Il est important d’append la note à cette règle selon laquelle la simple déclaration ne définit pas l’ordre:

 extern Foo globalFoo; // or just a ref that is defined at a single place extern Foo & globalFooRef; 

ou en tant que membre statique

 struct Global { static Foo globalFoo; // or just a ref that is defined at a single place static Foo & globalFooRef; };