Question

Ce message peut sembler trop long pour que la question courte à la fin de celui-ci. Mais je dois aussi décrire un modèle de conception, je viens avec. Peut-être qu'il est couramment utilisé, mais je ne l'ai jamais vu (ou peut-être qu'il ne fonctionne pas:).

Tout d'abord, voici un code qui (à ma connaissance) a un comportement non défini en raison de « fiasco dans l'ordre d'initialisation statique ». Le problème est que l'initialisation de l'espagnol :: s_englishToSpanish dépend de l'anglais :: s_numberToStr, qui sont à la fois statique et initialisé dans des fichiers différents, de sorte que l'ordre de ces initialisations n'est pas défini:

Fichier: English.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];
    }
};

Fichier: English.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;
}());

Fichier: Spanish.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];
    }
};

Fichier: Spanish.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;
}());

Fichier: 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;
}

Pour résoudre le problème de l'ordre d'initialisation statique, nous utilisons le langage de construction sur-première utilisation, et de faire les initialisations statiques fonctionnent local comme ceci:

Fichier: English.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];
    }
};

Fichier: Spanish.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];
    }
};

Mais maintenant, nous avons un autre problème. En raison des données statiques fonction locale, aucune de ces classes est thread-safe. Pour résoudre ce problème, on ajoute aux deux classes une variable membre statique et une fonction d'initialisation pour elle. Ensuite, à l'intérieur de cette fonction, nous forçons l'initialisation de toutes les données statiques fonction locale, en appelant une fois chaque fonction qui a des données statiques fonction locale. Ainsi, effectivement nous tout initialiser au début du programme, mais en contrôlant l'ordre d'initialisation. Alors maintenant, nos classes devraient être thread-safe:

Fichier: English.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();

Fichier: Spanish.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();

Et voici la question: Est-il possible que certains compilateur peut optimiser loin ces appels à des fonctions (constructeurs dans ce cas) qui ont des données statiques locales? La question est ce qui équivaut exactement à « ayant des effets secondaires », qui à mes moyens comprendre le compilateur n'est pas permis d'optimiser loin. Est-ce avoir assez de données statique fonction locale pour faire le compilateur pense que l'appel de fonction ne peut pas être ignorée?

Était-ce utile?

La solution

Section 1.9 "d'exécution du programme" [intro.execution] du C ++ 11 indique que la norme

1 Les descriptions sémantiques dans la présente Norme internationale définit une machine abstraite nondéterministe paramétrés. ... implémentations conformes doivent Emuler (uniquement) le comportement observable de la machine abstraite comme expliqué ci-dessous.
...

5 Une mise en œuvre conforme exécution d'un programme bien formé doit produire le même comportement observable comme l'un des exécutions possibles de l'instance correspondante de la machine abstraite avec le même programme et la même entrée.
...

8 Les exigences moins sur une mise en œuvre conforme sont:
- L'accès aux objets volatils sont évalués strictement selon les règles de la machine abstraite
. - À la fin du programme, toutes les données écrites sur les fichiers doivent être identiques à l'un des résultats possibles que l'exécution du programme en fonction de la sémantique abstraite aurait produit
. - L'entrée et la sortie dynamique des dispositifs interactifs auront lieu de telle façon que la sortie demandant est effectivement livrée avant un temps d'attente du programme pour l'entrée. Ce qui constitue un dispositif interactif est défini par l'implémentation.
Ceux-ci sont collectivement appelés comportement observable du programme.
...

12 l'accès à un objet désigné par un glvalue volatile (3.10), la modification d'un objet, en appelant une fonction I / O bibliothèque, ou l'appel d'une fonction qui fait l'une de ces opérations sont tous effets secondaires , qui sont des changements dans l'état de l'environnement d'exécution.

En outre, en 3.7.2 "durée de stockage automatique" [de basic.stc.auto] il est dit que

3 Si une variable avec une durée de stockage automatique a l'initialisation ou un destructor avec des effets secondaires, il ne doit pas être détruit avant la fin de son bloc, ni être éliminé comme une optimisation, même si elle semble être utilisé, sauf que un objet de classe ou de sa copie / déplacement peut être éliminé comme indiqué dans 12,8.

décrit copie élision 12,8 à 31 que je crois est hors de propos ici.

La question est de savoir si l'initialisation de vos variables locales a des effets secondaires qui l'empêchent d'être optimisé loin. Comme il peut effectuer l'initialisation d'une variable statique avec une adresse d'un objet dynamique, je pense qu'il produit des effets secondaires suffisantes (par exemple, modifie un objet). Aussi, vous pouvez y ajouter une opération avec un objet volatile, introduisant ainsi un comportement observable qui ne peut pas être éliminé.

Autres conseils

Ok, en un mot:

  1. Je ne vois pas pourquoi les membres statiques de la nécessité de la classe pour être publique -. Ils sont les détails de mise en œuvre

  2. Ne pas les rendre privé, mais plutôt faire les membres de l'unité de compilation (où le code qui implémente vos classes seront).

  3. Utilisation boost::call_once pour effectuer l'initialisation statique.

Initialisation de la première utilisation est relativement facile à faire respecter l'ordre de, il est la destruction qui est beaucoup plus difficile à réaliser dans l'ordre. Notez cependant que la fonction utilisée dans call_once ne doit pas jeter une exception. Par conséquent, si vous ne parvenez pas, il pourrait devriez laisser une sorte d'état a échoué et vérifier que, après l'appel.

(je suppose que dans votre exemple réel, votre charge n'est pas quelque chose que vous dur code mais plus probablement vous chargez une sorte de tableau dynamique, de sorte que vous ne pouvez pas créer un tableau en mémoire) .

Pourquoi ne pas simplement cacher l'anglais :: s_numberToStr derrière une fonction statique publique et sauter la syntaxe du constructeur entièrement? Utilisez DCLP pour assurer la sécurité-fil.

Je recommande fortement d'éviter les variables statiques de classe dont l'initialisation implique non triviales effets secondaires. En tant que modèle de conception générale, ils ont tendance à causer plus de problèmes que de solutions. Quels que soient les problèmes des performances que vous êtes préoccupé par ici besoin de justification, car je doute qu'ils sont mesurables dans des circonstances réelles.

vous devez peut-être faire un travail supplémentaire pour contrôler l'ordre d'initialisation. comme,

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

static staticObjects objects = new staticObjects();

et définir des interfaces pour le récupérer.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top