2010-09-19 7 views
9

Ich glaube, mein Problem am besten in Code beschrieben:C++ globale Initialisierungsreihenfolge ignoriert Abhängigkeiten?

#include <stdio.h> 

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

Die obigen Drucke (mit gcc 4.4.3):

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

Das ist, was ich erwarte, und scheint logisch.

Allerdings, wenn ich die folgenden Zeilen tauschen:

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

in diese:

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

gibt das Programm die folgenden:

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

Es scheint Instanzmethoden Foo werden mit einer noch nicht vorhandenen Instanz aufgerufen wurde gebaut! Etwas so Einfaches wie das Verschieben der Deklaration einer Variablen in den globalen Gültigkeitsbereich beeinflusst das Verhalten des Programms, und das lässt mich glauben, dass (1) die Reihenfolge der Initialisierung von Globals nicht definiert ist und (2) die Reihenfolge der Initialisierung von Globals ignoriert alle Abhängigkeiten. Ist das richtig? Kann sichergestellt werden, dass der Konstruktor Foo vor der Initialisierung dummy aufgerufen wird?

Das Problem, das ich zu lösen versuche, füllt ein Repository von Elementen (eine statische Instanz von Foo) statisch. In meinem aktuellen Versuch verwende ich ein Makro, das (unter anderem) eine globale Variable (in einem anonymen Namensraum, um Namenskonflikte zu vermeiden) erstellt, deren Initialisierung die statische Initialisierung auslöst. Vielleicht gehe ich mein Problem aus dem falschen Blickwinkel an? Gibt es eine bessere Alternative (n)? Vielen Dank.

Antwort

9

Lesen Sie in der Reihenfolge der Initialisierung die Antwort here.

Zum Lösen des Initialisierungsproblems können Sie das globale Element als statische lokale Variable in einer Funktion verwenden. Es Standard garantiert, dass die statische lokale Variable wird im ersten Aufruf der Funktion initialisiert werden:

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

Ihre anderen globalen Variablen die Variable als Zugang würde dann:

Foo::singleton().add(); 

Beachten Sie, dass dies nicht generell ist als ein gutes Design angesehen, und auch, wenn dies die Initialisierung Probleme behebt, es nicht die Reihenfolge der Finalisierung zu lösen, so sollten Sie vorsichtig sein, nicht auf das Singleton zugreifen, nachdem es zerstört wurde.

+0

Ich bin auf diese Lösung gestoßen, nachdem ich meine Frage gepostet habe. Ich habe es versucht und es scheint zu funktionieren. Es kann jedoch ein Problem geben: Gibt es mehrere Instanzen aufgrund von 'statischem' innerhalb von 'Singleton'? Das heißt, könnte 'Foo :: singleton()' möglicherweise unterschiedliche Dinge zurückgeben, wenn es in einer Header-Datei deklariert und definiert ist und über verschiedene Übersetzungseinheiten hinweg enthalten ist? – strager

+0

Ein weiteres mögliches Problem mit diesem Muster ist, dass es AFAIK nicht sicher ist, gleichzeitig auf Singleton zuzugreifen. – FuleSnabel

+1

@strager: Wenn die lokale Variable 'static' deklariert wird, dann wird es eine einzelne Instanz davon geben, selbst wenn die Funktion inline ist (das heißt, separat in verschiedenen Übersetzungseinheiten kompiliert), muss der Linker sicherstellen, dass nur eine einzige Definition von Die Variable existiert. –

29

(1) die Reihenfolge der Initialisierung der globalen Variablen nicht

Globale Variablen in einem einzigen Übersetzungseinheit (Quelldatei) definiert in der Reihenfolge initialisiert werden, in der sie definiert sind.

Die Reihenfolge der Initialisierung von globalen Variablen in verschiedenen Übersetzungseinheiten ist nicht spezifiziert.

(2) die Reihenfolge der Initialisierung von Globals ignoriert alle Abhängigkeiten

Rechts.

Kann sichergestellt werden, dass der Konstruktor von Foo vor der Dummy-Initialisierung aufgerufen wird?

Ja, wenn globalFoovordummy definiert ist, und sie sind in der gleichen Übersetzungseinheit.

Eine Option wäre, einen statischen Zeiger auf die globale Instanz zu haben; Solch ein Zeiger wird auf Null initialisiert, bevor irgendeine dynamische Initialisierung stattfindet; addToGlobal kann dann testen, ob der Zeiger Null ist; Wenn dies der Fall ist, ist es das erste Mal, dass global verwendet wird, und addToGlobal kann das globale Foo erstellen.

+0

Von "Ist es möglich, sicherzustellen, dass der Konstruktor von Foo vor der Dummy-Initialisierung aufgerufen wird?" Ich meinte "anders als die Reihenfolge der Definitionen zu ändern". Deine Antwort macht die Dinge ein wenig klarer, aber sie löst mein Problem (noch) nicht wirklich. – strager

+0

Scheint mir, als wäre es vorzuziehen, ein statisches Mitglied von Foo zu haben, welches das Repository ist. Behandeln von Foo als ein Repository (d. H. GlobalFoo) und für andere Zwecke fühlt sich nicht richtig an. Wenn andererseits Foo 'ein globales Repository hat, dann sollte init OK funktionieren und das Design scheint sauberer zu sein. –

+0

Ich hätte lieber das Singleton als Funktion statisch implementiert: 'Foo & global_foo() {statisch Foo foo; Rückkehr foo; } 'da es in der Reihenfolge der Initialisierung eine etwas bessere Garantie gibt: Sie wird beim ersten Aufruf der Methode initialisiert. Das wird immer noch Probleme während der Finalisierung haben ... –

1

Sie haben Recht, die Initialisierung von Globals zwischen Übersetzungseinheiten ist nicht definiert. Es ist möglich, dies mit der singleton pattern umgehen. Seien Sie jedoch gewarnt, dass dieses Entwurfsmuster häufig missbraucht wird. Seien Sie auch gewarnt, dass die Reihenfolge oder Zerstörung von Globals auch nicht definiert ist, falls Sie Abhängigkeiten in den Destruktoren haben.

+1

Die Reihenfolge der Zerstörung ist tatsächlich gut definiert: Objekte mit statischer Speicherdauer werden in der umgekehrten Reihenfolge ihrer Initialisierung oder Konstruktion zerstört (es gibt ein paar Nuancen, aber das ist die allgemeine Regel). –

0

C++ fehlt etwas wie Ada's pragma elaborate, so dass Sie keine Annahmen über die Reihenfolge der Initalisierungen machen können. Sorry. Es ist scheiße, aber das ist das Design.

0

Wie wäre es, wenn die statische globale Variable ein auf nullptr initialisierter Zeiger wäre? Dann, bevor ein anderes globales Objekt versucht, das Objekt zu verwenden, nach seiner Erstellung suchen und bei Bedarf erstellen. Dies funktionierte für mich zum Erstellen einer globalen Registrierung von Klassenerstellern, wo man neue Klassen hinzufügen konnte, ohne die Datei zu ändern, die die Registrierung behandelt hatte. dh

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

Dies kompiliert und arbeitete mit VC++ in Visual Studio 2015

ich vor diesem

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

} 

mit versucht hatte, aber ich Ausnahmen geworfen noch hatte, als thetable nicht vorher erstellt wurde versucht, Einfügen eines Eintrags in die Map, was passieren kann, wenn die Registrierung der Klasse in einer separaten Kompilierungseinheit von der Fabriklogik behandelt wird.