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.
Vedere anche questa domanda simultanea http://stackoverflow.com/questions/1300778/how-to-prevent-the-linker-from-optimizing-away-startup-code –
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
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