Für die statische Elementinitialisierung verwende ich eine verschachtelte Hilfsstruktur, die für Klassen ohne Vorlagen gut funktioniert. Wenn die einschließende Klasse jedoch durch eine Vorlage parametrisiert wird, wird die verschachtelte Initialisierungsklasse nicht instanziiert, wenn nicht auf das Hilfsobjekt im Hauptcode zugegriffen wird. Zur Veranschaulichung ein vereinfachtes Beispiel (in meinem Fall muss ich einen Vektor initialisieren).
#include <string>
#include <iostream>
struct A
{
struct InitHelper
{
InitHelper()
{
A::mA = "Hello, I'm A.";
}
};
static std::string mA;
static InitHelper mInit;
static const std::string& getA(){ return mA; }
};
std::string A::mA;
A::InitHelper A::mInit;
template<class T>
struct B
{
struct InitHelper
{
InitHelper()
{
B<T>::mB = "Hello, I'm B."; // [3]
}
};
static std::string mB;
static InitHelper mInit;
static const std::string& getB() { return mB; }
static InitHelper& getHelper(){ return mInit; }
};
template<class T>
std::string B<T>::mB; //[4]
template<class T>
typename B<T>::InitHelper B<T>::mInit;
int main(int argc, char* argv[])
{
std::cout << "A = " << A::getA() << std::endl;
// std::cout << "B = " << B<int>::getB() << std::endl; // [1]
// B<int>::getHelper(); // [2]
}
Mit g++ 4.4.1:
-
[1] und [2] kommentiert:
A = Hello, I'm A.
Funktioniert wie vorgesehen
-
[1] unkommentiert:
A = Hello, I'm A.
B =
Ich würde erwarten, dass der InitHelper mB initialisiert
- [1] und [2] unkommentiert:
A = Hello, I'm A.
B = Hello, I'm B.
Funktioniert wie vorgesehen
- [1] kommentiert, [2] unkommentiert:
Segfault in der statischen Initialisierungsphase bei [3]
Daher meine Frage: Ist das ein Compiler-Bug oder sitzt der Bug zwischen Monitor und Stuhl? Und falls letzteres der Fall ist: Gibt es eine elegante Lösung (dh ohne expliziten Aufruf einer statischen Initialisierungsmethode)?
Update I:
Dies scheint ein gewünschtes Verhalten zu sein (wie im Standard ISO/IEC C++ 2003, 14.7.1 definiert):
Sofern ein Member einer Klassenvorlage oder einer Membervorlage nicht explizit instanziiert oder explizit spezialisiert wurde, wird die Spezialisierung des Members implizit instanziiert, wenn auf die Spezialisierung in einem Kontext verwiesen wird, der das Vorhandensein der Memberdefinition erfordert; insbesondere tritt die Initialisierung (und alle damit verbundenen Nebeneffekte) eines statischen Datenmembers nicht auf, es sei denn, der statische Datenmember selbst wird auf eine Weise verwendet, die die Existenz der Definition des statischen Datenmembers erfordert.
Dies wurde vor einiger Zeit im Usenet diskutiert, während ich versuchte, eine andere Frage zu Stackoverflow zu beantworten: Instanziierungspunkt von statischen Datenelementen. Ich denke, es lohnt sich, den Testfall zu reduzieren und jedes Szenario isoliert zu betrachten, also schauen wir es uns zuerst allgemeiner an:
struct C { C(int n) { printf("%d\n", n); } };
template<int N>
struct A {
static C c;
};
template<int N>
C A<N>::c(N);
A<1> a; // implicit instantiation of A<1> and 2
A<2> b;
Sie haben die Definition einer Vorlage für statische Datenelemente. Dadurch werden noch keine Datenelemente erstellt, weil 14.7.1
:
“… insbesondere tritt die Initialisierung (und alle damit verbundenen Nebenwirkungen) eines statischen Datenmembers nicht auf, es sei denn, der statische Datenmember selbst wird auf eine Weise verwendet, die die Existenz der Definition des statischen Datenmembers erfordert.”
Die Definition von etwas (= Entität) wird benötigt, wenn diese Entität “verwendet” wird, gemäß der einen Definitionsregel, die dieses Wort definiert (at 3.2/2
). Insbesondere wenn alle Verweise von nicht instanziierten Vorlagen stammen, Mitglieder einer Vorlage oder a sizeof
Ausdrücke oder ähnliche Dinge, die die Entität nicht “verwenden” (da sie sie entweder nicht potenziell auswerten oder einfach noch nicht als Funktionen/Memberfunktionen existieren, die selbst verwendet werden), wird ein solches statisches Datenelement nicht instanziiert .
Eine implizite Instanziierung durch 14.7.1/7
instanziiert Deklarationen von statischen Datenmembern – das heißt, es instanziiert jede Vorlage, die zur Verarbeitung dieser Deklaration benötigt wird. Es werden jedoch keine Definitionen instanziiert – das heißt, Initialisierer werden nicht instanziiert und Konstruktoren des Typs dieses statischen Datenelements werden nicht implizit definiert (als verwendet markiert).
Das alles bedeutet, dass der obige Code noch nichts ausgeben wird. Lassen Sie uns jetzt implizite Instanziierungen der statischen Datenelemente veranlassen.
int main() {
A<1>::c; // reference them
A<2>::c;
}
Dadurch werden die beiden statischen Datenelemente vorhanden, aber die Frage ist – wie ist die Reihenfolge der Initialisierung? Beim einfachen Lesen könnte man das denken 3.6.2/1
gilt, was besagt (Hervorhebung von mir):
“Objekte mit statischer Speicherdauer, die im Namensraumbereich im selben definiert sind Übersetzungseinheit und dynamisch initialisiert werden in der Reihenfolge initialisiert, in der ihre Definition in der Übersetzungseinheit erscheint.”
Nun wie im Usenet-Post gesagt und erklärt in diesem Mängelberichtwerden diese statischen Datenelemente nicht in einer Übersetzungseinheit definiert, sondern in einer instanziiert Instanziierungseinheitwie unter erklärt 2.1/1
:
Jede übersetzte Übersetzungseinheit wird untersucht, um eine Liste erforderlicher Instantiierungen zu erzeugen. [Note: this may include instantiations which have been explicitly requested (14.7.2). ] Die Definitionen der erforderlichen Vorlagen befinden sich. Es ist implementierungsabhängig, ob die Quelle der Übersetzungseinheiten, die diese Definitionen enthalten, verfügbar sein muss. [Note: an implementation could encode sufficient information into the translated translation unit so as to ensure the source is not required here. ] Alle erforderlichen Instanziierungen werden durchgeführt, um Instanziierungseinheiten zu erzeugen. [Note: these are similar to translated translation units, but contain no references to uninstantiated templates and no template definitions. ] Das Programm ist falsch formatiert, wenn eine Instanziierung fehlschlägt.
Der Instanziierungspunkt eines solchen Members spielt auch keine Rolle, da ein solcher Instanziierungspunkt die Kontextverbindung zwischen einer Instanziierung und ihren Übersetzungseinheiten ist – er definiert die sichtbaren Deklarationen (wie unter angegeben 14.6.4.1
und jeder dieser Instanziierungspunkte muss Instanziierungen die gleiche Bedeutung geben, wie in der Regel für eine Definition unter angegeben 3.2/5
letzte Kugel).
Wenn wir eine geordnete Initialisierung wollen, müssen wir uns so arrangieren, dass wir nicht mit Instanziierungen herumspielen, sondern mit expliziten Deklarationen – das ist der Bereich der expliziten Spezialisierungen, da diese sich nicht wirklich von normalen Deklarationen unterscheiden. Tatsächlich hat C++0x seinen Wortlaut von geändert 3.6.2
Zu dem Folgendem:
Die dynamische Initialisierung eines nicht lokalen Objekts mit statischer Speicherdauer ist entweder geordnet oder ungeordnet. Definitionen explizit spezialisierter statischer Datenelemente von Klassenvorlagen haben eine geordnete Initialisierung. Andere statische Datenelemente der Klassenvorlage (dh implizit oder explizit instanziierte Spezialisierungen) haben eine ungeordnete Initialisierung.
Dies bedeutet für Ihren Code, dass:
[1]
und [2]
kommentiert: Es gibt keinen Verweis auf die statischen Datenelemente, also ihre Definitionen (und auch nicht ihre Deklarationen, da keine Instanziierung von erforderlich ist B<int>
) werden nicht instanziiert. Es tritt keine Nebenwirkung auf.
[1]
unkommentiert: B<int>::getB()
verwendet wird, die an sich verwendet B<int>::mB
, was erfordert, dass dieser statische Member vorhanden ist. Der String wird vor main initialisiert (auf jeden Fall vor dieser Anweisung als Teil der Initialisierung nicht lokaler Objekte). Nichts nutzt B<int>::mInit
es wird also nicht instanziiert und ist daher kein Objekt von B<int>::InitHelper
jemals erstellt wird, was dazu führt, dass sein Konstruktor nicht verwendet wird, was wiederum niemals etwas zuweisen wird B<int>::mB
: Sie geben nur eine leere Zeichenfolge aus.
[1]
und [2]
unkommentiert: Dass das bei dir geklappt hat, ist Glück (oder das Gegenteil :)). Wie oben erläutert, ist keine bestimmte Reihenfolge der Initialisierungsaufrufe erforderlich. Es könnte auf VC++ funktionieren, auf GCC fehlschlagen und auf Clang funktionieren. Wir wissen es nicht.
[1]
kommentiert, [2]
unkommentiert: Gleiches Problem – wieder, beide statische Datenmitglieder sind benutzt: B<int>::mInit
wird verwendet von B<int>::getHelper
und die Instantiierung von B<int>::mInit
bewirkt, dass sein Konstruktor instanziiert wird, der verwendet wird B<int>::mB
– Aber für Ihren Compiler ist die Reihenfolge in diesem bestimmten Lauf anders (nicht spezifiziertes Verhalten muss nicht zwischen verschiedenen Läufen konsistent sein): Er wird initialisiert B<int>::mInit
zuerst, die auf einem noch nicht konstruierten String-Objekt operieren wird.
Das Problem ist, dass die Definitionen, die Sie für die statischen Mitgliedsvariablen angeben, ebenfalls Vorlagen sind.
template<class T>
std::string B<T>::mB;
template<class T>
typename B<T>::InitHelper B<T>::mInit;
Beim Kompilieren definiert dies eigentlich nichts, da T nicht bekannt ist. Es ist so etwas wie eine Klassendeklaration oder eine Vorlagendefinition, der Compiler generiert keinen Code oder reserviert keinen Speicherplatz, wenn er ihn sieht.
Die Definition erfolgt implizit später, wenn Sie die Template-Klasse verwenden. Da Sie im Segfaulting-Fall B::mInit nicht verwenden, wird es nie erstellt.
Eine Lösung wäre, das benötigte Mitglied explizit zu definieren (ohne es zu initialisieren): Legen Sie irgendwo eine Quelldatei ab a
template<>
typename B<int>::InitHelper B<int>::mInit;
Dies funktioniert im Grunde genauso wie das explizite Definieren einer Template-Klasse.
Visual Studio 2008 hat das gleiche Verhalten (-segfault bei statischer Initialisierung)
– Andreas Brink
30. November 2009 um 11:08 Uhr
Warum schreibst du nicht einfach std::string B::mB = “Hello, I’m B.”?
– Andreas Brink
30. November 2009 um 11:11 Uhr
Ok, ich sehe, dass Sie im eigentlichen Fall einen Vektor initialisieren mussten, sorry.
– Andreas Brink
30. November 2009 um 11:12 Uhr
Einfügung
mB
Definition inInitHelper
.– Alexej Malistow
30. November 2009 um 12:44 Uhr