wake-up-neo.net

Zerstörungsreihenfolge statischer Objekte in C++

Kann ich steuern, in welcher Reihenfolge statische Objekte zerstört werden? Gibt es eine Möglichkeit, meine gewünschte Reihenfolge durchzusetzen? Zum Beispiel, um auf irgendeine Weise anzugeben, dass ich möchte, dass ein bestimmtes Objekt zuletzt oder zumindest nach einem anderen statischen Objekt zerstört wird?

48
Gal Goldman

Die statischen Objekte werden in umgekehrter Reihenfolge der Konstruktion zerstört. Und die Reihenfolge der Konstruktion ist sehr schwer zu kontrollieren. Sie können sich nur sicher sein, dass zwei Objekte, die in derselben Übersetzungseinheit definiert sind, in der Reihenfolge der Definition erstellt werden. Alles andere ist mehr oder weniger zufällig.

50
Arkadiy

Die anderen Antworten darauf bestehen darauf, dass dies nicht möglich ist. Und sie haben laut Spezifikation recht - aber da ist ein Trick, mit dem Sie es tun können.

Erstellen Sie nur eine einzelne statische Variable einer Klasse oder Struktur, die alle anderen Dinge enthält, die Sie normalerweise als statische Variablen definieren würden.

class StaticVariables {
    public:
    StaticVariables(): pvar1(new Var1Type), pvar2(new Var2Type) { };
    ~StaticVariables();

    Var1Type *pvar1;
    Var2Type *pvar2;
};

static StaticVariables svars;

Sie können die Variablen in beliebiger Reihenfolge erstellen und vor allem destroy in beliebiger Reihenfolge im Konstruktor und Destruktor für StaticVariables. Um dies vollständig transparent zu machen, können Sie auch statische Verweise auf die Variablen erstellen.

static Var1Type &var1(*svars.var1);

Voilà - totale Kontrolle. :-) Das heißt, das ist zusätzliche Arbeit und im Allgemeinen unnötig. Aber wenn es ist notwendig ist, ist es sehr nützlich, darüber Bescheid zu wissen.

24
Head Geek

Kurze Antwort: Im Allgemeinen nein.

Etwas längere Antwort: Bei globalen statischen Objekten in einer einzelnen Übersetzungseinheit ist die Initialisierungsreihenfolge von oben nach unten, die Zerstörungsreihenfolge genau umgekehrt. Die Reihenfolge zwischen mehreren Übersetzungseinheiten ist undefiniert.

Wenn Sie wirklich eine bestimmte Bestellung benötigen, müssen Sie dies selbst nachholen.

11
gimpf

Statische Objekte werden in umgekehrter Reihenfolge zerstört, in der sie erstellt wurden (z. B. das zuerst erstellte Objekt wird zuletzt zerstört). Sie können die Reihenfolge, in der statische Objekte erstellt werden, mithilfe der in Punkt 47 beschriebenen Technik steuern. " Stellen Sie sicher, dass globale Objekte vor ihrer Verwendung initialisiert werden " in Meyers 'Buch Effective C++.

Zum Beispiel, um auf irgendeine Weise anzugeben, dass ich möchte, dass ein bestimmtes Objekt zuletzt oder zumindest nach einem anderen statischen Objekt zerstört wird?

Stellen Sie sicher, dass es vor dem anderen statischen Objekt erstellt wurde.

Wie kann ich den Bauauftrag kontrollieren? Nicht alle Statiken befinden sich in derselben DLL.

Ich werde (der Einfachheit halber) die Tatsache ignorieren, dass sie sich nicht in derselben DLL befinden.

Meine Paraphrase von Meyers 'Artikel 47 (die 4 Seiten lang ist) lautet wie folgt. Angenommen, Sie sind global in einer solchen Header-Datei definiert ...

//GlobalA.h
extern GlobalA globalA; //declare a global

... fügen Sie der Include-Datei wie folgt Code hinzu ...

//GlobalA.h
extern GlobalA globalA; //declare a global
class InitA
{
  static int refCount;
public:
  InitA();
  ~InitA();
};
static InitA initA;

Dies bewirkt, dass jede Datei, die GlobalA.h enthält (z. B. Ihre GlobalB.cpp-Quelldatei, die Ihre zweite globale Variable definiert), eine statische Instanz der InitA-Klasse definiert, die vor allen anderen Elementen in dieser Klasse erstellt wird Quelldatei (zB vor Ihrer zweiten globalen Variablen).

Diese InitA-Klasse verfügt über einen statischen Referenzzähler. Wenn die erste InitA-Instanz erstellt wird, die jetzt garantiert ist, bevor Ihre GlobalB-Instanz erstellt wird, kann der InitA-Konstruktor alles tun, um sicherzustellen, dass die globalA-Instanz initialisiert wird.

11
ChrisW

Es gibt keine Möglichkeit, dies in Standard-C++ zu tun, aber wenn Sie gute Kenntnisse der spezifischen Compiler-Interna haben, können Sie dies wahrscheinlich erreichen.

In Visual C++ befinden sich die Zeiger auf die statischen Init-Funktionen im .CRT$XI-Segment (für C-Typ static init) oder .CRT$XC-Segment (für C++ Typ static init). Sie können die Reihenfolge festlegen, in der die statische Initialisierung erfolgt, indem Sie Ihre Objekte im entsprechenden Segment mit deklarieren 

#pragma init_seg

wenn Sie beispielsweise möchten, dass die Objekte der Datei A vor den Dateien der Datei B erstellt werden:

Datei A.cpp:

#pragma init_seg(".CRT$XCB")
class A{}A;

Datei B.cpp:

#pragma init_seg(".CRT$XCC")
class B{}B;

.CRT$XCB wird vor .CRT$XCC zusammengeführt. Wenn das CRT die statischen Init-Funktionszeiger durchläuft, stößt es auf Datei A vor Datei B.

In Watcom ist das Segment XI und Variationen bei #pragma initialize können die Konstruktion steuern:

#pragma initialize before library
#pragma initialize after library
#pragma initialize before user

... siehe Dokumentation für mehr

4
David Mott
3
Martin York

Nein, das kannst du nicht. Sie sollten sich niemals auf den anderen Aufbau oder die Zerstörung statischer Objekte verlassen.

Sie können immer einen Singleton verwenden, um die Reihenfolge der Erstellung/Zerstörung Ihrer globalen Ressourcen zu steuern.

0
Martin Cote

Müssen Sie die Variable wirklich vor main initialisieren?

Wenn Sie dies nicht tun, können Sie eine einfache Sprache verwenden, um die Reihenfolge von Bau und Zerstörung mit Leichtigkeit zu steuern. Siehe hier:

#include <cassert>

class single {
    static single* instance;

public:
    static single& get_instance() {
        assert(instance != 0);
        return *instance;
    }

    single()
    // :  normal constructor here
    {
        assert(instance == 0);
        instance = this;
    }

    ~single() {
        // normal destructor here
        instance = 0;
    }
};
single* single::instance = 0;

int real_main(int argc, char** argv) {
    //real program here...

    //everywhere you need
    single::get_instance();
    return 0;
}

int main(int argc, char** argv) {
    single a;
    // other classes made with the same pattern
    // since they are auto variables the order of construction
    // and destruction is well defined.
    return real_main(argc, argv);
}

Es hält Sie nicht auf, tatsächlich zu versuchen, eine zweite Instanz der Klasse zu erstellen, aber wenn Sie dies tun, schlägt die Assertion fehl. Nach meiner Erfahrung funktioniert es gut.

0
Paolo.Bolzoni

Sie können eine ähnliche Funktionalität effektiv erreichen, indem Sie einen static std::optional<T> anstelle einer T verwenden. Initialisieren Sie es einfach so, wie Sie es mit einer Variablen tun würden, verwenden Sie es mit Indirection und zerstören Sie es, indem Sie std::nullopt (oder für boost boost::none) zuweisen.

Es unterscheidet sich von einem Zeiger darin, dass er vorab zugewiesenen Speicher hat. Ich denke also, was Sie wollen. Wenn Sie es also zerstören und (möglicherweise später) neu erstellen, hat Ihr Objekt dieselbe Adresse (die Sie behalten können), und Sie zahlen zu diesem Zeitpunkt nicht die Kosten für die dynamische Zuweisung/Freigabe.

Verwenden Sie boost::optional<T>, wenn Sie std::/std::experimental:: nicht haben.

0
lorro