2009-08-19 19 views
5

Sto tentando di registrare un gruppo di classi con una factory al momento del caricamento. La mia strategia è quella di sfruttare l'inizializzazione statica per assicurarci che prima che main() inizi, la fabbrica è pronta per partire. Questa strategia sembra funzionare quando collego la mia libreria dinamicamente, ma non quando collego staticamente; quando collego staticamente, solo alcuni dei miei membri di dati statici vengono inizializzati.Perché un membro di dati statici non può essere inizializzato?

Diciamo che la mia fabbrica costruisce macchine. Ho lezioni di CarCreator in grado di istanziare una manciata di macchine, ma non tutte. Voglio che la fabbrica raccolga tutte queste classi di CarCreator in modo che il codice alla ricerca di una nuova auto possa andare in fabbrica senza dover sapere chi farà la costruzione vera e propria.

Così ho

CarTypes.hpp

enum CarTypes 
{ 
    prius = 0, 
    miata, 
    hooptie, 
    n_car_types 
}; 

MyFactory.hpp

class CarCreator 
{ 
public: 
    virtual Car * create_a_car(CarType) = 0; 
    virtual std::list<CarTypes> list_cars_I_create() = 0; 
}; 

class MyFactory // makes cars 
{ 
public: 
    Car * create_car(CarType type); 
    void factory_register(CarCreator *) 

    static MyFactory * get_instance(); // singleton 
private: 
    MyFactory(); 

    std::vector< CarCreator * > car_creator_map; 
}; 

MyFactory.cpp

MyFactory:: MyFactory() : car_creator_map(n_car_types); 

MyFactory * MyFactory::get_instance() { 
    static MyFactory * instance(0); /// Safe singleton 
    if (instance == 0) { 
     instance = new MyFactory; 
    } 
    return instance; 
} 

void MyFactory::factory_register(CarCreator * creator) 
{ 
    std::list<CarTypes> types = creator->list_cars_I_create(); 
    for (std::list<CarTypes>::const_iteator iter = types.begin(); 
     iter != types.end(); ++iter) { 
     car_creator_map[ *iter ] = creator; 
    } 
} 

Car * MyFactory::create_car(CarType type) 
{ 
    if (car_creator_map[ type ] == 0) { // SERIOUS ERROR! 
     exit(); 
    } 
    return car_creator_map[ type ]->create_a_car(type); 
} 

...

Allora dovrò macchine specifiche e creatori di automobili specifiche:

Miata.cpp

class Miata : public Car {...}; 

class MiataCreator : public CarCreator { 
public: 
    virtual Car * create_a_car(CarType); 
    virtual std::list<CarTypes> list_cars_I_create(); 
private: 
    static bool register_with_factory(); 
    static bool registered; 
}; 

bool MiataCreator::register_with_factory() 
{ 
    MyFactory::get_instance()->factory_register(new MiataCreator); 
    return true; 
} 

bool MiataCreator::registered(MiataCreator::register_with_factory()); 

...

Per ribadire: il collegamento in modo dinamico le mie librerie, MiataCreator :: registrato otterrà inizializzato, collegando staticamente le mie librerie, non verrà inizializzato.

Con una configurazione statica, quando qualcuno va in fabbrica a richiedere un Miata, l'elemento miata di car_creator_map punta a NULL e il programma verrà chiuso.

C'è qualcosa di speciale con i membri di dati integrali statici privati ​​che la loro inizializzazione verrà saltata in qualche modo? I membri dei dati statici sono inizializzati solo se la classe viene utilizzata? Le mie classi CarCreator non sono dichiarate in nessun file di intestazione; vivono interamente all'interno del file .cpp. È possibile che il compilatore stia integrando la funzione di inizializzazione e in qualche modo evitando la chiamata a MyFactory :: factory_register?

Esiste una soluzione migliore per questo problema di registrazione?

Non è possibile elencare iall dei CarCreators in una singola funzione, registrarli singolarmente in fabbrica e quindi garantire che la funzione venga richiamata. In particolare, voglio collegare più librerie e definire CarCreators in queste librerie separate, ma comunque utilizzare una fabbrica singolare per costruirle.

...

Ecco alcune risposte che sto anticipando, ma che non affrontare il mio problema:

1) la tua fabbrica Singleton non è thread-safe. a) Non importa, sto lavorando con un solo thread.

2) la fabbrica singleton può essere non inizializzata quando i CarCreators vengono inizializzati (ad es.hai un fiasco di inizializzazione statico) a) Sto usando una versione sicura della classe singleton mettendo l'istanza singleton in una funzione. Se questo fosse un problema, dovrei vedere l'output se aggiungo una dichiarazione di stampa al metodo MiataCreator's::register_with_factory: io no.

+0

Vedere anche questa domanda simultanea http://stackoverflow.com/questions/1300778/how-to-prevent-the-linker-from-optimizing-away-startup-code –

+0

Vorrei usare objdump e/o nm (o dumpbin su Windows) sul file eseguibile finale. È possibile che il codice non sia incluso perché il linker vede che il tuo programma non fa riferimento a nulla nelle librerie statiche. – nos

+0

Questo ha molto poco a che fare con il fatto che si sta collegando staticamente o dinamicamente e molto più a che fare con il fatto che * l'ordine * di inizializzazione dei membri statici non è definito. La risposta di Tyler McHenry ha ragione. – quark

risposta

7

Penso che tu abbia un fiasco ordine di inizializzazione statico, ma non con la fabbrica.

Non è che la bandiera registrata non viene inizializzata, ma non viene inizializzata abbastanza presto.

Non si può contare su ordine di inizializzazione statica se non nella misura in cui:

variabili
  1. statiche definite nella stessa unità di traduzione (file cpp) verranno inizializzate nell'ordine
  2. variabili statiche definite in un'unità di traduzione verrà inizializzata prima che qualsiasi funzione o metodo in quella unità di traduzione venga invocato per la prima volta.

Quello che non può contare su è che una variabile statica verrà inizializzato prima di una funzione o di un metodo in qualche altra unità traduzione è invocato per la prima volta.

In particolare, non è possibile fare affidamento su MiataCreator :: Register (definito in Miata.cpp) da inizializzare prima che MyFactory :: create_car (definito in MyFactory.cpp) venga richiamato per la prima volta.

Come tutti i comportamenti non definiti, a volte ottieni ciò che desideri, ea volte non lo fai, e le cose più strane apparentemente non correlate (come il collegamento statico o dinamico) possono cambiare se funziona nel modo desiderato o no.

Quello che devi fare è creare un metodo di accesso statico per il flag registrato definito in Miata.cpp e avere il valore MyFactory in fabbrica tramite questo accessorio. Poiché l'accessore si trova nella stessa unità di traduzione della definizione di variabile, la variabile verrà inizializzata al momento dell'esecuzione del programma di accesso. Devi quindi chiamare questa accessoria da qualche parte.

+0

Pertanto, i membri dei dati statici non vengono sempre inizializzati al momento del caricamento. C'è qualcosa che è? Vorrei evitare che MyFactory sappia a quale classe deve parlare. Prima di iniziare a implementare i CarCreators, avevo appena ricevuto un'enorme istruzione switch in MyFactory: switch (car_type) { case miata: return new Miata; Uno dei grossi problemi con questa configurazione è che tutte le classi di automobili dovevano essere definite nella stessa libreria con la fabbrica. Man mano che la libreria si ingrandiva, i tempi di sviluppo rallentarono. – Andrew

+0

"2. Le variabili statiche definite in un'unità di traduzione verranno inizializzate prima che qualsiasi funzione o metodo in quella unità di traduzione venga richiamata per la prima volta." C'è un problema di pollo e uova: la conoscenza che l'unità di traduzione deve essere invocata vive nell'unità di traduzione. Non c'è modo, quindi, di comunicare "hey, usa questo codice" senza codificare in modo rigido quell'istruzione da qualche parte centralmente (ad esempio in Factory.cpp)? È terribilmente deprimente. – Andrew

+0

Questa è la teoria, ma anche i ragazzi si affidano all'inizializzazione statica prima del principale http://www.boost.org/doc/libs/1_39_0/libs/flyweight/doc/tutorial/technical.html –

3

Se con il collegamento statico si intende l'aggiunta di tutti i file oggetto (.o) al file binario, che dovrebbe funzionare come le cose dinamiche, se si crea una libreria (.a) statica il linker non li collegherà all'interno come solo il gli oggetti usati all'interno della libreria statica sono collegati e in questo caso nessuno è usato esplicitamente.

Tutte le tecniche di registrazione automatica dipendono dal codice di caricamento e dai modi per evitare il fiasco statico, come la funzione che crea l'oggetto e lo restituisce su richiesta.

Ma se non si riesce a caricarlo, non funzionerà, il collegamento di file oggetto funziona insieme e il caricamento di librerie dinamiche, ma le librerie statiche non collegheranno mai senza dipendenze esplicite.

0

Quando si controlla se l'elemento miata si trova all'interno della mappa? è prima o dopo principale?
L'unico motivo per cui potrei pensare è l'accesso agli elementi della mappa prima di main() (come nell'inizializzazione globale), che può aver luogo prima della creazione di MiataCreator :: registrato (se si trova in un'altra unità di traduzione)

+0

Ho letto solo dalla mappa dopo che main() è iniziato. – Andrew

1

Generalmente con le librerie statiche, il linker estrarrà solo i file .o da quella libreria a cui fa riferimento il programma principale. Dal momento che non stai facendo riferimento a MiataCreator :: registrati o veramente nulla in Miata.cpp ma fai affidamento sull'inizializzazione statica il linker non includerà nemmeno quel codice nel tuo exe se è collegato da una libreria statica-

Verifica il file eseguibile risultante con nm o objdump (o dumpbin se si è su Windows) se il codice per MiataCreator :: registrato è effettivamente incluso nel file exe quando si collega staticamente.

Non so come forzare il linker di includere ogni bit e pezzi di una libreria statica però ..

+0

Sì, quello che stavo pensando. –

+0

Questa è solo la metà della spiegazione. Il linker si comporta come un garbage collector: esplora tutti i puntatori (chiamate di funzione) a partire da qualcosa che è attivo (principale).Se un oggetto non viene mai raggiunto (un'unità di compilazione non viene mai raggiunta) durante l'esplorazione della memoria attiva (funzioni potenzialmente raggiungibili da main()), il garbage collector lo elimina (il linker non lo include). (Continuerò questo nel mio prossimo commento) – Andrew

+0

L'unico modo per segnalare al linker che Miata.cpp deve essere incluso è di avere una funzione centrale (ad esempio quella che vive in MyFactroy.cpp) includendo un riferimento ad un pezzo di dati in Miata.cpp La mia tesi è che questo passaggio extra è tanto restrittivo quanto il problema originale: MyFactory (o qualcuno di centrale) deve conoscere tutti i CarCreators. Non vi è alcuna soluzione in cui MyFactory possa attendere fino al momento del caricamento per CarCreators sconosciuti per venire ad esso con le richieste di registrazione. Non riesco a mettere CarCreators in altre librerie. – Andrew

0

Personalmente penso che vi sia un fallo del linker.

Le variabili booleane non vengono utilizzate 'bool MiataCreator :: registered' os il linker non le preleva dalla lib nell'eseguibile (ricordare se non vi è alcun riferimento a una funzione/globale nell'eseguibile il linker non lo farà estraeteli dalla lib [Cerca solo oggetti attualmente non definiti nell'eseguibile])

Puoi aggiungere alcune dichiarazioni di stampa in 'bool MiataCreator :: register_with_factory()' per vedere se viene mai chiamato. O controlla i simboli nel tuo eseguibile per verificare che sia lì.

Alcune cose che vorrei fare:

// Return the factory by reference rather than pointer. 
// If you return by pointer the user has to assume it could be NULL 
// Also the way you were creating the factory the destructor was never 
// being called (though not probably a big deal here) so there was no 
// cleanup, which may be usefull in the future. And its just neater. 
MyFactory& MyFactory::get_instance() 
{ 
    static MyFactory instance; /// Safe singleton 
    return instance; 
} 

Piuttosto che avere un'inizializzazione due fasi dell'oggetto. Che sospetto stia fallendo a causa del linker. Crea un'istanza della tua fabbrica e ottieni il costruttore per registrarla.

bool MiataCreator::register_with_factory() 
{ 
    MyFactory::get_instance()->factory_register(new MiataCreator); 
    return true; 
} 
// 
// I would hope that the linker does not optimize this out (but you should check). 
// But the linker only pulls from (or searches in) static libraries 
// for references that are explicitly not defined. 
bool MiataCreator::registered(MiataCreator::register_with_factory()); 

farei questo:

MiataCreator::MiataCreator() 
{ 
    // I would change factory_register to take a reference. 
    // Though I would store it internall as a pointer in a vector. 
    MyFactory::getInstance().factory_register(*this); 
} 

// In Cpp file. 
static MiataCreator factory; 

Il linker sa di C++ oggetti e costruttori e dovrebbe tirare tutte le variabili globali come i costruttori possono potenzialmente avere effetti collaterali (So che il vostro bool fa pure, ma posso vedere alcuni linker che lo ottimizzano).

Comunque questo è il mio valore di 2c.

0

Con gcc, è possibile aggiungere -Wl,--whole-archive myLib.a --Wl,--no-whole-archive. Ciò costringerà il linker a includere gli oggetti anche se non si fa riferimento a. Questo, tuttavia, non è portatile.

Problemi correlati