Domanda

Questo post può sembrare eccessivamente lungo solo per il breve domanda alla fine di esso. Ma ho anche bisogno di descrivere un modello di progettazione Ho appena è venuta. Forse è comunemente usato, ma non ho mai visto (o forse semplicemente non funziona:).

In primo luogo, qui è un codice che (la mia comprensione) ha un comportamento indefinito a causa di "inizializzazione statico ordine fiasco". Il problema è che l'inizializzazione dello spagnolo :: s_englishToSpanish dipende inglese :: s_numberToStr, che sono entrambi statica inizializzato e in file diversi, per cui l'ordine di queste inizializzazioni non è definito:

File: 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];
    }
};

File: 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;
}());

File: 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];
    }
};

File: 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;
}());

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

Per risolvere il problema ordine di inizializzazione statico, usiamo il linguaggio costrutto-on-first-use, e rendere tali inizializzazioni statiche funzionano-locale in questo modo:

File: 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];
    }
};

File: 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];
    }
};

Ma ora abbiamo un altro problema. Dovuto i dati statici funzione locale, nessuna di queste classi è thread-safe. Per risolvere questo problema, si aggiunge a entrambe le classi una variabile membro statica e una funzione di inizializzazione per esso. Poi dentro questa funzione si forza l'inizializzazione di tutti i dati statici funzione locale, chiamando una volta ogni funzione che ha dati statici funzione locale. Così, in modo efficace stiamo inizializzazione tutto all'inizio del programma, ma comunque controllare l'ordine di inizializzazione. Così ora le nostre classi dovrebbero essere thread-safe:

File: 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();

File: 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();

Ed ecco la domanda: è possibile che alcuni compilatore potrebbe ottimizzare via quelle chiamate a funzioni (costruttori in questo caso) che hanno dati statici locali? Quindi la domanda è ciò che equivale esattamente a "aventi effetti collaterali", che ai miei significa capire il compilatore non è consentito per ottimizzare via. Sta avendo abbastanza dati statici funzione-locale per rendere il compilatore pensa la chiamata di funzione non può essere ignorato?

È stato utile?

Soluzione

Sezione 1.9 "L'esecuzione del programma" [intro.execution] del C ++ standard di 11 dice che

1 Le descrizioni semantiche nella presente norma internazionale definisce un parametrizzato non deterministico macchina astratta. ... conforme implementazioni sono tenuti a emulare (solo) il comportamento osservabile della macchina astratta come spiegato di seguito.
...

5 A conforme attuazione esecuzione di un programma ben formato deve produrre lo stesso comportamento osservabile come una delle possibili esecuzioni del corrispondente istanza della macchina astratta con lo stesso programma e lo stesso input.
...

8 I minimi requisiti su conforme attuazione sono:
- L'accesso agli oggetti volatili vengono valutati rigorosamente secondo le regole della macchina astratta
. - A conclusione del programma, tutti i dati scritti in file devono essere identico a uno dei possibili risultati che l'esecuzione del programma in base alle semantica astratta avrebbe prodotto
. - La dinamica di ingresso e di uscita dei dispositivi interattivi si svolgono in modo tale che spingendo uscita viene effettivamente consegnato prima di un programma attende ingresso. Ciò che costituisce un dispositivo interattivo è definito dall'implementazione.
Questi insieme sono indicati come comportamento osservabile del programma.
...

12 Accesso a un oggetto designato da un glvalue volatile (3.10), modifica di un oggetto, chiamando una funzione di libreria I / O, o chiamando una funzione che fa una di queste operazioni sono tutti effetti collaterali , che sono cambiamenti nello stato dell'ambiente di esecuzione.

Inoltre, in 3.7.2 "durata Memorizzazione automatica" [basic.stc.auto] si dice che

3 Se una variabile con durata di memorizzazione automatica ha inizializzazione o un distruttore con effetti collaterali, esso non deve essere distrutto prima della fine del suo blocco, né può essere eliminato come ottimizzazione anche se sembra essere inutilizzati, salvo che un oggetto classe o sua copia / spostamento possono essere eliminati come specificato in 12.8.

12,8-31 descrive copia elisione che credo sia irrilevante in questa sede.

Quindi la domanda è se l'inizializzazione delle variabili locali ha effetti collaterali che le impediscono di essere ottimizzato via. Poiché può eseguire l'inizializzazione di una variabile statica con un indirizzo di un oggetto dinamico, Credo produce effetti collaterali sufficienti (ad esempio modifica un oggetto). Inoltre è possibile aggiungere c'è un'operazione con un oggetto volatile, introducendo così un comportamento osservabile che non può essere eliminata.

Altri suggerimenti

Ok, in poche parole:

  1. Non riesco a vedere il motivo per cui i membri statici della classe di necessità di essere pubblico -. Sono dettaglio di implementazione

  2. Ma non farli privato ma invece li rende membri della unità di compilazione (in cui il codice che implementa le vostre classi saranno).

  3. Usa boost::call_once per eseguire l'inizializzazione statica.

Inizializzazione al primo utilizzo è relativamente facile da applicare l'ordinamento di, è la distruzione che è molto più difficile da eseguire in ordine. Si noti tuttavia che la funzione utilizzata in call_once non deve generare un'eccezione. Quindi se potrebbe non riuscire si dovrebbe lasciare una sorta di stato fallito e verificare che dopo la chiamata.

(I assumerà che nel tuo esempio reale, il carico non è qualcosa che hard-code in ma più probabilmente si carica un qualche tipo di tabella dinamica, quindi non si può semplicemente creare un array in memoria) .

Perché non solo nascondere Inglese :: s_numberToStr dietro una funzione statica pubblica e saltare la sintassi del costruttore del tutto? Utilizzare DCLP garantire thread safety.

consiglio vivamente evitare le variabili di classe statiche cui inizializzazione comporta non banali effetti collaterali. Come un modello generale di progettazione, tendono a causare più problemi di quanti ne risolvano. Qualunque siano i problemi delle prestazioni siete preoccupati qui ha bisogno di una giustificazione, perché io sono dubbioso che sono misurabili in circostanze reali.

Forse avete bisogno di fare lavoro extra per controllare l'ordine init. come,

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

static staticObjects objects = new staticObjects();

e quindi definire alcune interfacce per recuperarla.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top