Frage

Dieser Beitrag mag für die kurze Frage am Ende übermäßig lang erscheinen. Aber ich muss auch ein Designmuster beschreiben, das ich mir gerade ausgedacht habe. Vielleicht wird es allgemein verwendet, aber ich habe es nie gesehen (oder vielleicht funktioniert es einfach nicht :).

Erstens ist hier ein Code, der (nach meinem Verständnis) ein undefiniertes Verhalten aufgrund des "statischen Initialisierungsauftragsfiaskos" hat. Das Problem ist, dass die Initialisierung von Spanisch :: s_englishtospaneish von Englisch abhängig ist :: S_NumbertoStr, die sowohl statische initialisierte als auch in verschiedenen Dateien sind, sodass die Reihenfolge dieser Initialisierungen nicht definiert ist:

Datei: englisch.h

#pragma once

#include <vector>
#include <string>

using namespace std;

struct English {
    static vector<string>* s_numberToStr;
    string m_str;

    explicit English(int number)
    {
        m_str = (*s_numberToStr)[number];
    }
};

Datei: englisch.cpp

#include "English.h"

vector<string>* English::s_numberToStr = new vector<string>( /*split*/
[]() -> vector<string>
{
    vector<string> numberToStr;
    numberToStr.push_back("zero");
    numberToStr.push_back("one");
    numberToStr.push_back("two");
    return numberToStr;
}());

Datei: spanisch.h

#pragma once

#include <map>
#include <string>

#include "English.h"

using namespace std;

typedef map<string, string> MapType;

struct Spanish {
    static MapType* s_englishToSpanish;
    string m_str;

    explicit Spanish(const English& english)
    {
        m_str = (*s_englishToSpanish)[english.m_str];
    }
};

Datei: spanisch.cpp

#include "Spanish.h"

MapType* Spanish::s_englishToSpanish = new MapType( /*split*/
[]() -> MapType
{
    MapType englishToSpanish;
    englishToSpanish[ English(0).m_str ] = "cero";
    englishToSpanish[ English(1).m_str ] = "uno";
    englishToSpanish[ English(2).m_str ] = "dos";
    return englishToSpanish;
}());

Datei: staticfiasco.h

#include <stdio.h>
#include <tchar.h>
#include <conio.h>

#include "Spanish.h"

int _tmain(int argc, _TCHAR* argv[])
{
    _cprintf( Spanish(English(1)).m_str.c_str() ); // may print "uno" or crash

    _getch();
    return 0;
}

Um das Problem der statischen Initialisierungsreihenfolge zu lösen, verwenden wir das Konstrukt-auf-zu-First-Use-Idiom und lassen diese statischen Initialisierungen wie folgt funktionieren:

Datei: englisch.h

#pragma once

#include <vector>
#include <string>

using namespace std;

struct English {
    string m_str;

    explicit English(int number)
    {
        static vector<string>* numberToStr = new vector<string>( /*split*/
        []() -> vector<string>
        {
            vector<string> numberToStr_;
            numberToStr_.push_back("zero");
            numberToStr_.push_back("one");
            numberToStr_.push_back("two");
            return numberToStr_;
        }());

        m_str = (*numberToStr)[number];
    }
};

Datei: spanisch.h

#pragma once

#include <map>
#include <string>

#include "English.h"

using namespace std;

struct Spanish {
    string m_str;

    explicit Spanish(const English& english)
    {
        typedef map<string, string> MapT;

        static MapT* englishToSpanish = new MapT( /*split*/
        []() -> MapT
        {
            MapT englishToSpanish_;
            englishToSpanish_[ English(0).m_str ] = "cero";
            englishToSpanish_[ English(1).m_str ] = "uno";
            englishToSpanish_[ English(2).m_str ] = "dos";
            return englishToSpanish_;
        }());

        m_str = (*englishToSpanish)[english.m_str];
    }
};

Aber jetzt haben wir ein anderes Problem. Aufgrund der funktionierenden statischen Daten ist keiner dieser Klassen mit Thread-Sicherheit. Um dies zu lösen, fügen wir beiden Klassen eine statische Mitgliedsvariable und eine Initialisierungsfunktion dafür hinzu. In dieser Funktion erzwingen wir dann die Initialisierung aller funktionierenden statischen Daten, indem wir einmal jede Funktion aufrufen, die funktionierende statische Daten aufweist. Daher initialisieren wir effektiv alles zu Beginn des Programms, kontrollieren aber dennoch die Reihenfolge der Initialisierung. Jetzt sollten unsere Klassen fadensicher sein:

Datei: englisch.h

#pragma once

#include <vector>
#include <string>

using namespace std;

struct English {
    static bool s_areStaticsInitialized;
    string m_str;

    explicit English(int number)
    {
        static vector<string>* numberToStr = new vector<string>( /*split*/
        []() -> vector<string>
        {
            vector<string> numberToStr_;
            numberToStr_.push_back("zero");
            numberToStr_.push_back("one");
            numberToStr_.push_back("two");
            return numberToStr_;
        }());

        m_str = (*numberToStr)[number];
    }

    static bool initializeStatics()
    {
        // Call every member function that has local static data in it:
        English english(0); // Could the compiler ignore this line?
        return true;
    }
};
bool English::s_areStaticsInitialized = initializeStatics();

Datei: spanisch.h

#pragma once

#include <map>
#include <string>

#include "English.h"

using namespace std;

struct Spanish {
    static bool s_areStaticsInitialized;
    string m_str;

    explicit Spanish(const English& english)
    {
        typedef map<string, string> MapT;

        static MapT* englishToSpanish = new MapT( /*split*/
        []() -> MapT
        {
            MapT englishToSpanish_;
            englishToSpanish_[ English(0).m_str ] = "cero";
            englishToSpanish_[ English(1).m_str ] = "uno";
            englishToSpanish_[ English(2).m_str ] = "dos";
            return englishToSpanish_;
        }());

        m_str = (*englishToSpanish)[english.m_str];
    }

    static bool initializeStatics()
    {
        // Call every member function that has local static data in it:
        Spanish spanish( English(0) ); // Could the compiler ignore this line?
        return true;
    }
};

bool Spanish::s_areStaticsInitialized = initializeStatics();

Und hier ist die Frage: Ist es möglich, dass ein Compiler diese Aufrufe von Funktionen (Konstruktoren in diesem Fall), die lokale statische Daten haben, optimieren? Die Frage ist also, was genau "Nebenwirkungen haben", was mein Verständnis bedeutet, dass der Compiler sie nicht optimieren darf. Ist es ausreichend funktionierende statische Daten, um den Compiler zu halten, dass der Funktionsaufruf nicht ignoriert werden kann?

War es hilfreich?

Lösung

Abschnitt 1.9 "Programmausführung" [intro.execution] des c ++ 11 Standard

1 Die semantischen Beschreibungen in diesem internationalen Standard definieren eine parametrisierte nicht -deterministische abstrakte Maschine. ... Konforme Implementierungen sind erforderlich, um das nachstehend erläuterte beobachtbare Verhalten der abstrakten Maschine zu emulieren (nur).
...

5 Eine konforme Implementierung, die ein gut geformtes Programm ausführt, erzeugt das gleiche beobachtbare Verhalten wie eine der möglichen Ausführungen der entsprechenden Instanz der abstrakten Maschine mit demselben Programm und demselben Eingang.
...

8 Die geringsten Anforderungen an eine konforme Implementierung sind:
- Der Zugang zu flüchtigen Objekten wird streng gemäß den Regeln der abstrakten Maschine bewertet.
- Bei der Programmabgabe müssen alle in Dateien geschriebenen Daten mit einem der möglichen Ergebnisse identisch sein, die die Ausführung des Programms gemäß der abstrakten Semantik erstellt hätte.
- Die Eingangs- und Ausgangsdynamik interaktiver Geräte muss so erfolgen, dass die Ausgabe von Ausgabe tatsächlich geliefert wird, bevor ein Programm auf die Eingabe wartet. Was ein interaktives Gerät ausmacht, ist implementierungsdefiniert.
Diese werden gemeinsam als die bezeichnet Beobachtbares Verhalten des Programms.
...

12 Zugriff auf ein Objekt, das von einem flüchtigen Glvalue (3.10) bezeichnet wird, ein Objekt geändert, eine Bibliotheks -E/A -Funktion aufgerufen oder eine Funktion aufgerufen wird, die eine dieser Operationen ausführt Nebenwirkungen, die Veränderungen im Zustand der Ausführungsumgebung sind.

Auch in 3.7.2 "Automatische Speicherdauer" [Basic.stc.auto] wird gesagt

3 Wenn eine Variable mit automatischer Speicherdauer Initialisierung oder einen Destruktor mit Nebenwirkungen aufweist, darf sie nicht vor dem Ende seines Blocks zerstört werden, und sie darf auch nicht als Optimierung beseitigt werden, selbst wenn sie nicht genutzt zu werden, außer dass ein Klassenobjekt erscheint oder seine Kopie/Bewegung kann gemäß 12,8 beseitigt werden.

12.8-31 beschreibt die Kopienelision, von der ich glaube, dass sie hier irrelevant ist.

Die Frage ist also, ob die Initialisierung Ihrer lokalen Variablen Nebenwirkungen hat, die verhindern, dass sie optimiert werden. Da es eine Initialisierung einer statischen Variablen mit einer Adresse eines dynamischen Objekts durchführen kann, denke ich, dass sie ausreichende Nebenwirkungen erzeugt (z. B. modifiziert ein Objekt). Sie können dort auch einen Betrieb mit einem flüchtigen Objekt hinzufügen, wodurch ein beobachtbares Verhalten eingeführt wird, das nicht beseitigt werden kann.

Andere Tipps

OK, KINMAL:

  1. Ich kann nicht verstehen, warum die statischen Mitglieder der Klasse öffentlich sein müssen - sie sind Implementierungsdetails.

  2. Machen Sie sie nicht privat, sondern machen Sie sie Mitglieder der Compilation Unit (wo der Code, der Ihre Klassen implementiert).

  3. Verwenden boost::call_once Um die statische Initialisierung durchzuführen.

Die Initialisierung beim ersten Gebrauch ist relativ einfach, um die Bestellung durchzusetzen. Es ist die Zerstörung, die in Ordnung viel schwieriger zu durchführen ist. Beachten Sie jedoch, dass die in call_once verwendete Funktion keine Ausnahme ausgeben darf. Wenn es möglicherweise fehlschlägt, sollten Sie einen fehlgeschlagenen Zustand hinterlassen und nach dem Anruf danach suchen.

(Ich gehe davon aus, dass in Ihrem wirklichen Beispiel Ihre Last nicht etwas ist, in dem Sie Hardcode, aber wahrscheinlich eine dynamische Tabelle laden, sodass Sie nicht einfach ein In-Memory-Array erstellen können.)

Warum verbergen Sie nicht einfach Englisch :: s_numbertostr hinter einer öffentlichen statischen Funktion und überspringen die Konstruktorsyntax vollständig? Verwenden DCLP Gewährleistung der Gewinde.

Ich empfehle dringend, statische Klassenvariablen zu vermeiden, deren Initialisierung nicht triviale Nebenwirkungen beinhaltet. Als allgemeines Entwurfsmuster verursachen sie tendenziell mehr Probleme als sie lösen. Unabhängig davon, über die Sie sich hier befassen, müssen Sie Rechtsberechnung benötigen, weil ich zweifelhaft, dass sie unter realen Umständen messbar sind.

Vielleicht müssen Sie zusätzliche Arbeit erledigen, um die Init -Bestellung zu kontrollieren. wie,

class staticObjects
{
    private:
    vector<string>* English::s_numberToStr;
    MapType* s_englishToSpanish;
};

static staticObjects objects = new staticObjects();

und definieren Sie dann einige Schnittstellen, um es abzurufen.

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top