Statische Member-Initialisierung in C++ (Template-Spaß im Inneren)

Lesezeit: 9 Minuten

Statische Member Initialisierung in C Template Spas im Inneren
Herr Herr

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.

  • 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 in InitHelper.

    – Alexej Malistow

    30. November 2009 um 12:44 Uhr

Statische Member Initialisierung in C Template Spas im Inneren
Johannes Schaub – litb

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.1und jeder dieser Instanziierungspunkte muss Instanziierungen die gleiche Bedeutung geben, wie in der Regel für eine Definition unter angegeben 3.2/5letzte 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>::mInites 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>::getHelperund 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.

1646254210 434 Statische Member Initialisierung in C Template Spas im Inneren
Günther Piez

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.

  • Ich denke, in diesem Beispiel würde es keinen Unterschied machen, die Initialisierung in eine andere Header-Datei zu packen, da sich alles in derselben Übersetzungseinheit befindet.

    – Herr Herr

    30. November 2009 um 11:58 Uhr

  • Natürlich. Deshalb habe ich das Konditional (“würde”) verwendet. Aber ich bin kein englischer Muttersprachler, also ist das wahrscheinlich irreführend. Ich habe es gelöscht.

    – Günther Piez

    30. November 2009 um 15:08 Uhr

  • Verstehen Sie jetzt, warum Sie ein segfault/leeres Mitglied erhalten, oder ist es immer noch unklar?

    – Günther Piez

    30. November 2009 um 15:12 Uhr

  • Ja Dankeschön. Ich habe es im C++-Standard (14.7.1) nachgelesen: > Sofern ein Mitglied einer Klassenvorlage oder eine Mitgliedsvorlage nicht explizit instanziiert oder explizit spezialisiert wurde, wird die Spezialisierung des Mitglieds implizit instanziiert, wenn auf die Spezialisierung in einem Kontext verwiesen wird das erfordert, dass die Member-Definition existiert; 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.

    – Herr Herr

    30. November 2009 um 15:52 Uhr

  • Nur zur Verdeutlichung: Im Fall 3 / dem Segfaulting-Fall: Der Segfault tritt nicht auf, weil mInit nicht existiert, sondern weil mB nicht existiert. Ich denke, das liegt daran, dass die Reihenfolge bei der statischen Objektinitialisierung nicht garantiert ist.

    – Herr Herr

    1. Dezember 2009 um 7:36 Uhr

  • [1] unkommentierter Fall: Es ist ok. static InitHelper B<int>::mInit ist nicht vorhanden. Wenn ein Mitglied der Vorlagenklasse (Struktur) nicht verwendet wird, wird es nicht kompiliert.

  • [1] und [2] unkommentierter Fall: Es ist ok. B<int>::getHelper() verwenden static InitHelper B<int>::mInit und mInit existiert.

  • [1] kommentiert, [2] unkommentiert: es funktioniert bei mir in VS2008.

916020cookie-checkStatische Member-Initialisierung in C++ (Template-Spaß im Inneren)

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

Privacy policy