45

Quando uso variabili statiche in C++, spesso finisco per voler inizializzare una variabile passandone un'altra al suo costruttore. In altre parole, voglio creare istanze statiche che dipendono l'una dall'altra.Ordine di inizializzazione statico C++

All'interno di un singolo file .cpp o .h questo non è un problema: le istanze verranno create nell'ordine in cui sono dichiarate. Tuttavia, quando si desidera inizializzare un'istanza statica con un'istanza in un'altra unità di compilazione, l'ordine sembra impossibile da specificare. Il risultato è che, a seconda del tempo, può succedere che sia costruita l'istanza che dipende da un'altra, e solo successivamente viene costruita l'altra istanza. Il risultato è che la prima istanza è inizializzata in modo errato.

Qualcuno sa come garantire che gli oggetti statici vengano creati nell'ordine corretto? Ho cercato a lungo una soluzione, provandone tutte (compresa la soluzione Counter Schwarz), ma comincio a dubitare che ce ne sia una che funzioni davvero.

Una possibilità è il trucco con il membro funzione statica:

Type& globalObject() 
{ 
    static Type theOneAndOnlyInstance; 
    return theOneAndOnlyInstance; 
} 

In effetti, questo funziona. Purtroppo, devi scrivere globalObject(). MemberFunction(), invece di globalObject.MemberFunction(), risultante in un codice client un po 'confuso e poco elegante.

Aggiornamento: Grazie per le vostre reazioni. Purtroppo, sembra proprio che abbia risposto alla mia stessa domanda. Credo che dovrò imparare a conviverci ...

+0

_I casi verranno creati nell'ordine in cui sono are_ ** ** definito –

risposta

50

Hai risposto alla tua stessa domanda. L'ordine di inizializzazione statico non è definito, e il modo più elegante attorno ad esso (mentre si sta ancora iniziando l'inizializzazione statica, vale a dire non rifattorizzandolo completamente) è quello di avvolgere l'inizializzazione in una funzione.

Leggi gli articoli ++ C Domande frequenti a partire dal https://isocpp.org/wiki/faq/ctors#static-init-order

+6

Ma, ahimè, che la soluzione non è thread-safe. –

+0

@CharlesSalvia Riportandoti indietro di 2,5 anni :), È davvero un problema che non sia sicuro per i thread? Stiamo parlando dell'inizializzazione statica, quindi, tutto sta accadendo prima di 'main()'. Dovremmo davvero preoccuparci dei problemi di sicurezza dei thread prima di 'main()'? – enobayram

+1

non prima di immettere main, ma quando il metodo viene chiamato per la prima volta. vedi http://blogs.msdn.com/b/oldnewthing/archive/2004/03/08/85901.aspx –

7

Forse si dovrebbe riconsiderare se avete bisogno di così tante variabili statiche globali. Anche se a volte possono essere utili, spesso è molto più semplice rifattorizzarli su un ambito locale più piccolo, specialmente se si scopre che alcune variabili statiche dipendono da altre.

Ma hai ragione, non c'è modo di garantire un particolare ordine di inizializzazione, e quindi se il tuo cuore è impostato su di esso, mantenere l'inizializzazione in una funzione, come hai detto, è probabilmente il modo più semplice.

+3

Hai ragione, non è saggio usare troppe variabili statiche globali, ma per alcuni casi evita di dover passare lo stesso oggetto in modo eccessivo. Pensa all'oggetto logger, al contenitore per le variabili persistenti, alla raccolta di tutte le connessioni IPC, ecc ... –

+2

"Evitare di far passare eccessivamente un oggetto" è solo un modo per dire "le dipendenze tra i componenti del mio programma sono così estese che monitorarli è un lavoro eccessivo, quindi è meglio smettere di seguirli ". Tranne che di solito non è meglio - se le dipendenze non possono essere semplificate, allora è * il più * utile per essere in grado di rintracciarli seguendo dove viene passato l'oggetto. –

5

Effettivamente, questo funziona. Purtroppo, devi scrivere globalObject(). MemberFunction(), invece di globalObject.MemberFunction(), risultante in un codice client un po 'confuso e poco elegante.

Ma la cosa più importante è che funziona, e che è mancata la prova, vale a dire. non è facile aggirare l'uso corretto.

La correttezza del programma dovrebbe essere la vostra prima priorità. Inoltre, IMHO, il() sopra è puramente stilistico - vale a dire. completamente irrilevante.

A seconda della piattaforma, fare attenzione a un'eccessiva inizializzazione dinamica. C'è una quantità relativamente piccola di pulizia che può verificarsi per gli inizializzatori dinamici (vedere here). È possibile risolvere questo problema utilizzando un contenitore di oggetti globale che contiene membri diversi oggetti globali.È quindi necessario:

Globals & getGlobals() 
{ 
    static Globals cache; 
    return cache; 
} 

C'è solo una chiamata a ~ Globals() al fine di ripulire per tutti gli oggetti globali nel programma. Per poter accedere a una globale avete ancora qualcosa di simile:

getGlobals().configuration.memberFunction(); 

Se si voleva davvero si potrebbe avvolgere questo in una macro per risparmiare un po 'di digitazione utilizzando una macro:

#define GLOBAL(X) getGlobals().#X 
GLOBAL(object).memberFunction(); 

Anche se, questo è solo zucchero sintattico sulla tua soluzione iniziale.

2

La maggior parte dei compilatori (linker) supportano effettivamente un modo (non portabile) di specificare l'ordine. Ad esempio, con Visual Studio è possibile utilizzare il pragma init_seg per organizzare l'inizializzazione in diversi gruppi diversi. AFAIK non c'è modo di garantire l'ordine ENTRO ogni gruppo. Dal momento che questo non è portabile, potresti voler considerare se è possibile correggere il tuo progetto per non averlo richiesto, ma l'opzione è disponibile.

2

nonostante l'età di questo thread, vorrei proporre la soluzione che ho trovato. Come molti hanno sottolineato prima di me, C++ non fornisce alcun meccanismo per l'ordinamento di inizializzazione statico. Quello che propongo è di incapsulare ogni membro statico all'interno di un metodo statico della classe che a sua volta inizializza il membro e fornisce un accesso in modo orientato agli oggetti. Lasciate che vi faccia un esempio, supponendo che vogliamo definire la classe denominata "Math", che, tra gli altri membri, contiene "PI":

class Math { 
public: 
    static const float Pi() { 
     static const float s_PI = 3.14f; 
     return s_PI; 
    } 
} 

s_PI verrà inizializzato la prima volta il metodo Pi() è invocato (in GCC). Attenzione: gli oggetti locali con memoria statica hanno un ciclo di vita dipendente dall'implementazione, per ulteriori dettagli controllare 6.7.4 in 2.

Static keyword, C++ Standard

+2

Com'è diverso questo, l'approccio dell'OP, tranne che hai fatto della funzione un membro di una classe? – einpoklum

+0

anche non un esempio illuminante, dal momento che un oggetto come questo può e dovrebbe essere "constexpr' –

0

Avvolgere la statica in un metodo risolverà il problema di ordine, ma non è thread-safe, come altri hanno fatto notare, ma si può fare questo per fare anche filo se questo è un problema .

// File scope static pointer is thread safe and is initialized first. 
static Type * theOneAndOnlyInstance = 0; 

Type& globalObject() 
{ 
    if(theOneAndOnlyInstance == 0) 
    { 
     // Put mutex lock here for thread safety 
     theOneAndOnlyInstance = new Type(); 
    } 

    return *theOneAndOnlyInstance; 
} 
+0

La risposta più alta suggerisce esattamente questo, e fu risposto quasi 5 anni fa. Forse dovremmo spostare questo nella risposta superiore come esempio. – AndyG

+1

Questo non è un problema in C++ 11. Vedi http://stackoverflow.com/questions/8102125/is-local-static-variable-initialization-thread-safe-in-c11 – povman

Problemi correlati