Statische C++-Initialisierungsreihenfolge

Lesezeit: 6 Minuten

Statische C Initialisierungsreihenfolge
Dimitri C.

Wenn ich statische Variablen in C++ verwende, möchte ich oft eine Variable initialisieren und eine andere an ihren Konstruktor übergeben. Mit anderen Worten, ich möchte statische Instanzen erstellen, die voneinander abhängen.

Innerhalb einer einzelnen .cpp- oder .h-Datei ist dies kein Problem: Die Instanzen werden in der Reihenfolge erstellt, in der sie deklariert sind. Wenn Sie jedoch eine statische Instanz mit einer Instanz in einer anderen Kompilierungseinheit initialisieren möchten, scheint die Reihenfolge unmöglich anzugeben. Das Ergebnis ist, dass je nach Wetterlage die Instanz, die von einer anderen abhängt, aufgebaut wird und erst danach die andere Instanz aufgebaut wird. Das Ergebnis ist, dass die erste Instanz falsch initialisiert wird.

Kann jemand sicherstellen, dass statische Objekte in der richtigen Reihenfolge erstellt werden? Ich habe lange nach einer Lösung gesucht und alle ausprobiert (einschließlich der Schwarz Counter-Lösung), aber ich zweifle daran, dass es eine gibt, die wirklich funktioniert.

Eine Möglichkeit ist der Trick mit dem statischen Funktionsmember:

Type& globalObject()
{
    static Type theOneAndOnlyInstance;
    return theOneAndOnlyInstance;
}

Tatsächlich funktioniert dies. Leider müssen Sie globalObject().MemberFunction() anstelle von globalObject.MemberFunction() schreiben, was zu etwas verwirrendem und unelegantem Client-Code führt.

Aktualisieren: Vielen Dank für Ihre Reaktionen. Leider scheint es tatsächlich so, als hätte ich meine eigene Frage beantwortet. Ich denke ich muss lernen damit zu leben…

  • Die Instanzen werden in der Reihenfolge erstellt, in der sie sind definiert

    – bartolo-otrit

    13. Dezember 2016 um 14:52 Uhr


Statische C Initialisierungsreihenfolge
laalto

Sie haben Ihre eigene Frage beantwortet. Die statische Initialisierungsreihenfolge ist undefiniert, und der eleganteste Weg, sie zu umgehen (während Sie immer noch eine statische Initialisierung durchführen, dh sie nicht vollständig umgestalten), besteht darin, die Initialisierung in eine Funktion zu packen.

Lesen Sie die häufig gestellten Fragen zu C++ ab https://isocpp.org/wiki/faq/ctors#static-init-order

  • nicht vor der Haupteingabe, sondern beim ersten Aufruf der Methode. sehen blogs.msdn.com/b/oldnewthing/archive/2004/03/08/85901.aspx

    – José

    8. Mai 2012 um 16:13 Uhr

  • @enobayram: Bringt dich 2,1 Jahre in der Zeit zurück :), Ja, es ist ein Problem. Der springende Punkt beim Verschieben der Initialisierung in eine Funktion ist, dass die Initialisierung jetzt erfolgt nicht Vor mainaber wenn die Funktion zum ersten Mal aufgerufen wird, was an jedem Punkt während der Ausführung des Programms sein kann … und in jedem Thread.

    – Leichtigkeitsrennen im Orbit

    31. Mai 2014 um 20:16 Uhr


  • @Lightness, Jose, Charles: Ich denke wirklich, dass Ihre Kommentare in die akzeptierte Antwort aufgenommen werden sollten. Da Multithreading heute alltäglich ist, kann dies zu bösen Überraschungen führen und wird in keinem der Top-Suchergebnisse für das Fiasko der statischen Initialisierung erwähnt. dh Parashift erwähnt es überhaupt nicht.

    – a.peganz

    25. Juni 2014 um 8:21 Uhr

  • Threadsicherheit ist in C++11 kein Problem. Siehe stackoverflow.com/questions/8102125/…

    – Lukas Wert

    18. September 2014 um 2:49 Uhr


  • @laalto, das Lesen der C++-FAQ versetzt uns zurück in die „Hast du die Manpages gelesen“-Tage.

    – SimplyKnownAsG

    29. September 2016 um 5:25 Uhr

Vielleicht sollten Sie noch einmal überdenken, ob Sie so viele globale statische Variablen benötigen. Obwohl sie manchmal nützlich sein können, ist es oft viel einfacher, sie auf einen kleineren lokalen Bereich umzugestalten, insbesondere wenn Sie feststellen, dass einige statische Variablen von anderen abhängen.

Aber Sie haben Recht, es gibt keine Möglichkeit, eine bestimmte Reihenfolge der Initialisierung sicherzustellen, und wenn Sie also darauf fixiert sind, ist es wahrscheinlich der einfachste Weg, die Initialisierung in einer Funktion zu belassen, wie Sie erwähnt haben.

  • Sie haben Recht, es ist unklug, zu viele globale statische Variablen zu verwenden, aber in einigen Fällen vermeidet es, dass dasselbe Objekt übermäßig herumgereicht werden muss. Denken Sie an das Logger-Objekt, den Container für persistente Variablen, die Sammlung aller IPC-Verbindungen usw.

    – Dimitri C.

    17. Juni 2009 um 8:45 Uhr

  • “Vermeidet es, ein Objekt übermäßig zu übergeben” ist nur eine Art zu sagen, “die Abhängigkeiten zwischen den Komponenten meines Programms sind so umfangreich, dass es übermäßige Arbeit ist, sie zu verfolgen. Also ist es besser, sie nicht mehr zu verfolgen”. Abgesehen davon, dass es normalerweise nicht besser ist – wenn die Abhängigkeiten nicht vereinfacht werden können, dann ist es so die meisten nützlich, um sie aufzuspüren, indem man verfolgt, wo das Objekt passiert wird.

    – Steve Jessop

    22. November 2013 um 2:01 Uhr


Tatsächlich funktioniert dies. Leider müssen Sie globalObject().MemberFunction() anstelle von globalObject.MemberFunction() schreiben, was zu etwas verwirrendem und unelegantem Client-Code führt.

Aber das Wichtigste ist, dass es funktioniert und dass es ausfallsicher ist, dh. Es ist nicht einfach, die korrekte Verwendung zu umgehen.

Die Korrektheit des Programms sollte Ihre erste Priorität sein. Außerdem ist IMHO das obige () rein stilistisch – dh. völlig unwichtig.

Achten Sie je nach Plattform auf zu viele dynamische Initialisierungen. Es gibt eine relativ kleine Menge an Aufräumarbeiten, die für dynamische Initialisierer stattfinden können (siehe Hier). Sie können dieses Problem lösen, indem Sie einen globalen Objektcontainer verwenden, der Mitglieder verschiedener globaler Objekte enthält. Sie haben also:

Globals & getGlobals ()
{
  static Globals cache;
  return cache;
}

Es gibt nur einen Aufruf von ~Globals(), um alle globalen Objekte in Ihrem Programm zu bereinigen. Um auf ein globales zuzugreifen, haben Sie immer noch etwas wie:

getGlobals().configuration.memberFunction ();

Wenn Sie wirklich wollten, könnten Sie dies in ein Makro packen, um ein wenig Tipparbeit mit einem Makro zu sparen:

#define GLOBAL(X) getGlobals().#X
GLOBAL(object).memberFunction ();

Dies ist jedoch nur syntaktischer Zucker für Ihre ursprüngliche Lösung.

Die meisten Compiler (Linker) unterstützen tatsächlich eine (nicht-portable) Möglichkeit, die Reihenfolge anzugeben. Mit Visual Studio können Sie beispielsweise die verwenden init_seg Pragma, um die Initialisierung in mehrere verschiedene Gruppen zu ordnen. AFAIK gibt es keine Möglichkeit, die Ordnung INNERHALB jeder Gruppe zu garantieren. Da dies nicht portabel ist, sollten Sie überlegen, ob Sie Ihr Design so reparieren können, dass es nicht erforderlich ist, aber die Option ist da draußen.

Trotz des Alters dieses Threads möchte ich die Lösung vorschlagen, die ich gefunden habe. Wie viele von mir bereits betont haben, bietet C++ keinen Mechanismus für die statische Initialisierungsreihenfolge. Ich schlage vor, jedes statische Mitglied in einer statischen Methode der Klasse zu kapseln, die wiederum das Mitglied initialisiert und einen objektorientierten Zugriff bereitstellt. Lassen Sie mich Ihnen ein Beispiel geben, angenommen, wir möchten die Klasse namens “Math” definieren, die neben den anderen Mitgliedern “PI” enthält:

class Math {
public:
   static const float Pi() {
       static const float s_PI = 3.14f;
       return s_PI;
   }
}

s_PI wird initialisiert, wenn die Pi()-Methode zum ersten Mal aufgerufen wird (in GCC). Beachten Sie: Die lokalen Objekte mit statischem Speicher haben einen implementierungsabhängigen Lebenszyklus, für weitere Details siehe 6.7.4 in 2.

Statisches Schlüsselwort, C++-Standard

  • Wie unterscheidet sich das vom Ansatz von OP, außer dass Sie die Funktion zu einem Mitglied einer Klasse gemacht haben?

    – einpoklum

    9. Dezember 2015 um 18:20 Uhr

  • auch kein erhellendes Beispiel, da so ein Objekt sein kann und soll constexpr

    – Unterstrich_d

    5. April 2016 um 1:22 Uhr

1646938209 44 Statische C Initialisierungsreihenfolge
Strahl

Das Einschließen der Statik in eine Methode behebt das Reihenfolgeproblem, ist jedoch nicht threadsicher, wie andere darauf hingewiesen haben, aber Sie können dies tun, um es auch zu einem Thread zu machen, wenn dies ein Problem darstellt.

// File scope static pointer is thread safe and is initialized first.
static Type * theOneAndOnlyInstance = 0;

Type& globalObject()
{
    if(theOneAndOnlyInstance == 0)
    {
         // Put mutex lock here for thread safety
         theOneAndOnlyInstance = new Type();
    }

    return *theOneAndOnlyInstance;
}

  • Wie unterscheidet sich das vom Ansatz von OP, außer dass Sie die Funktion zu einem Mitglied einer Klasse gemacht haben?

    – einpoklum

    9. Dezember 2015 um 18:20 Uhr

  • auch kein erhellendes Beispiel, da so ein Objekt sein kann und soll constexpr

    – Unterstrich_d

    5. April 2016 um 1:22 Uhr

988720cookie-checkStatische C++-Initialisierungsreihenfolge

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy