[10.14] Was ist das "Fiasko der statischen Initialisierungsreihenfolge"?
Ein subtiler Weg, Ihr Programm zum Absturz zu bringen.
Das Fiasko der statischen Initialisierungsreihenfolge ist ein sehr subtiler und gemeinhin missverstandener missverstandener Aspekt von C++. Leider ist es sehr schwer zu erkennen - die Fehler treten oft auf, bevor main()
beginnt.
Kurz gesagt, nehmen wir an, Sie haben zwei statische Objekte x
y y
die bestehen in separaten Quelldateien, zum Beispiel x.cpp
y y.cpp
. Nehmen wir weiter an, dass die Initialisierung für die y
Objekt (typischerweise die y
Konstruktor des Objekts) ruft eine Methode des Objekts x
Objekt.
Das war's. So einfach ist das.
Die Tragödie ist, dass Sie eine 50%-50%ige Chance hat, zu sterben. Wenn die Kompilierungs Einheit für x.cpp
bekommt zufällig zuerst initialisiert wird, ist alles in Ordnung. Aber wenn die Kompiliereinheit für y.cpp
bekommen. zuerst initialisiert, dann y
's Initialisierung wird ausgeführt, bevor x
's Initialisierung, und Sie sind erledigt. Z.B., y
Konstruktor könnte eine Methode auf dem x
Objekt, doch die x object hasn't yet been constructed.
Ich habe gehört, dass sie unten in der McDonalds. Genießen Sie Ihren neuen Job Burger braten.
Wenn Sie denken, dass es "aufregend" ist, Russisches Roulette zu spielen Russisches Roulette mit scharfen Kugeln in der Hälfte der Kammern zu spielen, können Sie hier aufhören zu lesen. Wenn Sie hingegen Ihre Überlebenschancen verbessern wollen Überlebenschancen verbessern wollen, indem Sie Katastrophen mit systematisch vorbeugen, wollen Sie wahrscheinlich die nächste FAQ lesen.
Hinweis: Die statische Initialisierungsreihenfolge Fiasko kann in einigen Fällen auch für für eingebaute/eigenständige Typen gelten.
[10.15] Wie verhindere ich das "Fiasko der statischen Initialisierungsreihenfolge"?
Verwenden Sie die Redewendung "construct on first use". Idiom, was einfach bedeutet, dass Sie Ihre statisches Objekt in eine Funktion zu verpacken.
Nehmen wir zum Beispiel an, Sie haben zwei Klassen, Fred und Barney. Es gibt eine globales Fred-Objekt namens x, und ein globales Barney-Objekt namens y. Der Konstruktor von Barney ruft die Funktion goBowling()-Methode für das Objekt x auf. Die Datei x.cpp definiert das x-Objekt:
// File x.cpp
#include "Fred.h"
Fred x;
Die Datei y.cpp definiert das y-Objekt:
// File y.cpp
#include "Barney.h"
Barney y;
Der Vollständigkeit halber: Barney Konstruktor etwa so aussehen dies:
// File Barney.cpp
#include "Barney.h"
Barney::Barney()
{
...
x.goBowling();
...
}
Wie oben beschrieben, ist die Katastrophe tritt ein, wenn y vor x gebaut wird, was in 50% der Fälle passiert, da sie in verschiedenen Quelldateien sind.
Hierfür gibt es viele Lösungen Problem, aber eine sehr einfache und komplett portable Lösung ist es, das das globale Fred-Objekt, x, durch eine globale Funktion, x(), zu ersetzen, die die das Fred-Objekt als Referenz zurückgibt.
// File x.cpp
#include "Fred.h"
Fred& x()
{
static Fred* ans = new Fred();
return *ans;
}
Da statische lokale Objekte das erste Mal konstruiert werden, wenn die Kontrolle (nur) über ihre Deklaration fließt, wird die obige Anweisung new Fred() nur einmal: das erste Mal, wenn x() aufgerufen wird. Jeder nachfolgende Aufruf wird das gleiche Fred-Objekt zurück (das auf das ans zeigt). Alles, was Sie dann tun, ist Ihre Verwendungen von x in x() zu ändern:
// File Barney.cpp
#include "Barney.h"
Barney::Barney()
{
...
x().goBowling();
...
}
Dies wird als "Construct On First" bezeichnet. Use Idiom genannt, weil es genau das tut: Das globale Fred-Objekt wird bei seiner ersten Verwendung.
Der Nachteil dieses Ansatzes ist, dass dass das Fred-Objekt nie zerstört wird. Es gibt eine andere Technik, die dieses Problem löst, aber sie muss mit Vorsicht verwendet werden, da sie die Möglichkeit eines weiteren (ebenso unangenehmen) Problem.
Hinweis: Die statische Initialisierungsreihenfolge Fiasko kann in einigen Fällen auch für für eingebaute/eigenständige Typen gelten.