2010-12-07 24 views
5

Wenn ich zwei statische Variablen in verschiedenen Kompilierungseinheiten habe, dann ist ihre Initialisierungsreihenfolge nicht definiert. Diese Lektion ist gut gelernt.C++ Initialisierung von statischen Variablen (noch einmal)

Die Frage, die ich habe: sind alle statischen Variablen bereits zugewiesen, wenn der erste initialisiert wird. Mit anderen Worten:

static A global_a; // in compilation unit 1 
static B global_b; // in compilation unit 2 

struct A { 
    A() { b_ptr = &global_b; } 
    B *b_ptr; 

    void f() { b_ptr->do_something(); } 
} 

int main() { 
    global_a.f(); 
} 

Wird Punkt auf einen gültigen Teil des Speichers B_PTR, wobei B zugeordnet ist, und zum Zeitpunkt der Ausführung der Hauptfunktion initialisiert? Auf allen Plattformen?

Längere Geschichte:

Die Übersetzungseinheit 1 ist Qt-Bibliothek. Der andere ist meine Anwendung. Ich habe paar QObject abgeleitete Klassen, die ich in der Lage sein muss, durch den Klassennamen String zu instantiieren. Dazu kam ich mit einer Templat-Factory-Klasse bis:

class AbstractFactory { 
public: 
    virtual QObject *create() = 0; 
    static QMap<const QMetaObject *, AbstractFactory *> m_Map; 
} 
QMap<const QMetaObject *, AbstractFactory *> AbstractFactory::m_Map; //in .cpp 

template <class T> 
class ConcreteFactory: public AbstractFactory { 
public: 
    ConcreteFactory() { AbstractFactory::m_Map[&T::staticMetaObject] = this; } 
    QObject *create() { return new T(); } 
} 

#define FACTORY(TYPE) static ConcreteFactory <TYPE> m_Factory; 

Dann füge ich dieses Makro auf jeder QObject Unterklasse Definition:

class Subclass : public QObject { 
    Q_OBJECT; 
    FACTORY(Subclass); 
} 

Endlich kann ich eine Klasse durch den Typnamen instanziiert:

QObject *create(const QString &type) { 
    foreach (const QMetaObect *meta, AbstractFactory::m_Map.keys() { 
     if (meta->className() == type) { 
      return AbstractFactory::m_Map[meta]->create(); 
     } 
    } 
    return 0; 
} 

So erhält die Klasse eine statische QMetaObject Instanz: Subclass::staticMetaObject aus der Qt-Bibliothek - es wird automatisch generiert in Q_OBJECT Makro denke ich. Und dann erstellt das FACTORY Makro eine statische ConcreteFactory<Subclass> Instanz. ConcreteFactory versucht in seinem Konstruktor, auf Subclass :: staticMetaObject zu verweisen.

Und ich war ziemlich glücklich mit dieser Implementierung auf Linux (gcc), bis ich es mit Visual Studio 2008 kompilierte. Aus irgendeinem Grund war AbstractFactory :: m_Map in der Laufzeit leer, und der Debugger würde nicht mit dem Factory-Konstruktor brechen .

Hier kommt der Geruch von statischen Variablen, die auf andere statische Variablen verweisen.

Wie kann ich diesen Code optimieren, um all diese Fallen zu vermeiden?

+0

Nun, dieser Code unterscheidet sich ziemlich von Ihrer ursprünglichen Frage ... in Ihrer ursprünglichen Frage haben Sie die Adresse einer globalen Variable gespeichert, aber haben erst auf 'main' zugegriffen, zu welcher Zeit die globale Konfiguration vollständig war. Aber jetzt versuchen Sie, es aus dem Konstruktor eines globalen "ConcreteFactory" -Objekts zu verwenden, das viel früher als "main" ist. –

+0

Im detaillierten Beispiel speichere ich die Adresse der statischen Variable 'staticMetaObject'. Aber es wird zur Laufzeit von 'main' über die' create'-Funktion aufgerufen - genau wie ich es im vereinfachten Beispiel beschrieben habe.Ich bin nicht besorgt über die 'statische QMap <...> m_Map' - in meinem echten Code ist tatsächlich in eine statische Funktion eingewickelt, genau wie @Martin York vorgeschlagen. Das detaillierte Beispiel bezieht sich also auf 'staticMetaObject' vom' ConcreteFactory'-Konstruktor. –

Antwort

5

Ja, der Standard erlaubt dies.

Es gibt eine Reihe von Absätzen in Abschnitt [basic.life] die

eines Objekts Vor der Lebensdauer beginnen hat begonnen, aber nach der Lagerung der das Objekt wird besetzen hat zugeteilt oder nach die Lebensdauer eines Objekts wurde beendet und vor dem Speicher, den das Objekt belegt hat, wiederverwendet oder freigegeben, jeder Zeiger, der verweist auf den Speicherort, wo der obje ct wird oder wurde möglicherweise verwendet werden, aber nur in begrenztem Umfang.

und es gibt eine Fußnote, die darauf hinweist, dass speziell auf Ihre Situation

Zum Beispiel, vor dem Aufbau eines globalen Objekts von nicht-POD-Klasse Typ

+0

Es ist schön, Abschnittsnummern zu haben. Aber gut gefunden. –

+0

Ich bin mir ziemlich sicher, dass Sektionsnamen in allen Versionen des Standards ziemlich konsistent sind, aber ich habe nicht so viel Komfort bei der Nummerierung. Es ist Abschnitt 3.8 der C++ 0x FCD zum Beispiel. –

+0

Hoppla. Verpasste diesen offensichtlichen Namen. :-) Es ist noch früh für mich. –

1

Ja. Alle befinden sich in .data Abschnitt, die auf einmal zugeordnet ist (und es ist kein Heap).

Anders ausgedrückt: Wenn Sie in der Lage sind, seine Adresse zu nehmen, dann ist es OK, weil es sich sicher nicht ändern wird.

+2

Der Standard hat kein Konzept von .data-Abschnitten. –

2

Kurze Antwort: Es sollte funktionieren, wie Sie es codiert haben. Siehe Ben Voigt

Lange Antwort:

etwas tun:
Anstatt lassen Sie den Compiler entscheiden, wann Globals erstellt werden, erstellen Sie sie über statische Methoden (mit statischer Funktion Variablen). Dies bedeutet, dass sie bei der ersten Verwendung deterministisch erstellt werden (und in umgekehrter Reihenfolge der Erstellung zerstört werden).

Auch wenn ein globaler verwendet einen anderen während seiner Konstruktion mit dieser Methode garantiert, dass sie in der Reihenfolge erstellt werden, die erforderlich sind und somit für die Verwendung durch den anderen zur Verfügung stehen (achten Sie auf Schleifen).

struct A 
{ 
    // Rather than an explicit global use 
    // a static method thus creation of the value is on first use 
    // and not at all if you don't want it. 
    static A& getGlobalA() 
    { 
     static A instance; // created on first use (destroyed on application exit) 
    // ^^^^^^ Note the use of static here. 
     return instance; // return a reference. 
    } 
    private: 
    A() 
     :b_ref(B::getGlobalB())  // Do the same for B 
    {}        // If B has not been created it will be 
            // created by this call, thus guaranteeing 
            // it is available for use by this object 
    } 
    B& b_ref; 
    public: 
    void f() { b_ref.do_something(); } 
}; 

int main() { 
    a::getGlobalA().f(); 
} 

Obwohl ein Wort der Warnung.

  • Globale sind eine Anzeige von schlechtem Design.
  • Globals, die auf andere Globals angewiesen sind, ist ein anderer Code Geruch (vor allem während der Konstruktion/Zerstörung).
  • +0

    Compiler-Implementierung meinen Sie? –

    +0

    @ak: Ja, tut er. – jwueller

    +0

    fragte das OP nach ihrer Zuweisung und nicht nach der Initialisierung. Vergleichen Sie 'static int i' mit' static int * i'. – ruslik

    1

    Wenn B einen Konstruktor hat, wie A, dann ist die Reihenfolge, in der sie aufgerufen werden, undefiniert. Dein Code funktioniert nur, wenn du Glück hast. Wenn B jedoch keinen Code benötigt, um es zu initialisieren, funktioniert Ihr Code. Es ist nicht implementierungsdefiniert.

    +2

    Das ist nicht die Frage, die gestellt wird. Die Frage, die gestellt wird, ist, dass die Adresse der Variablen global_b für den Konstruktor von global_a verfügbar ist (auch wenn global_b noch nicht von seinem Konstruktor initialisiert wurde). –

    +0

    Ja, du hast recht. Es tut uns leid. Ignoriere meine Antwort, geh und lese Ben Voigt. – TonyK

    1

    Ah gilt, aber die Idee, dass statische Variablen "nicht initialisiert" sind, ist ziemlich falsch. Sie werden immer initialisiert, nur nicht unbedingt mit Ihrem Initialisierer. Insbesondere werden alle statischen Variablen vor jeder anderen Initialisierung mit dem Wert Null erzeugt. Für Klassenobjekte sind die Mitglieder Null. Daher ist global_a.b_ptr oben immer ein gültiger Zeiger, anfänglich NULL und später & global_b. Der Effekt davon ist, dass die Verwendung von nicht-Zeiger nicht spezifiziert ist, nicht undefiniert, insbesondere wird dieser Code genau definiert (in C):

    // unit 1 
    int a = b + 1; 
    
    // unit 2 
    int b = a + 1; 
    
    main ... printf("%d\n", a + b); // 3 for sure 
    

    Die Null Initialisierung Garantie wird mit diesem Muster verwendet:

    int get_x() { 
        static int init; 
        static int x; 
        if(init) return x; 
        else { x = some_calc(); init = 1; return x; } 
    } 
    

    , die entweder eine Nichtrückgabe aufgrund unendlicher Rekursion oder korrekt initialisierten Wert gewährleistet.

    +0

    Ich stoße oft auf ein Problem, bei dem die automatische Initialisierung von Membervariablen in Debud und Release unterschiedlich funktioniert. Wenn ich also vergesse, einem Member-Zeiger 0 (oder NULL) zuzuweisen, wird es möglicherweise nicht im Debug-Modus initialisiert (d. H. Enthält einen ungültigen Wert). Funktioniert es bei statischen Variablen anders? –

    Verwandte Themen