Pourquoi un membre de données statique ne pourrait-il pas être initialisé?

J’essaie d’enregistrer plusieurs classes dans une usine au moment du chargement. Ma stratégie consiste à exploiter l’initialisation statique pour m’assurer qu’avant le démarrage de main (), l’usine est prête à fonctionner. Cette stratégie semble fonctionner lorsque je lie ma bibliothèque de manière dynamic, mais pas lorsque je lie de manière statique. Lorsque je lie statiquement, seules certaines de mes données membres sont initialisées.

Disons que mon usine construit des voitures. J’ai des classes CarCreator qui peuvent instancier une poignée de voitures, mais pas toutes. Je souhaite que l’usine rassemble toutes ces classes de CarCreator afin que le code à la recherche d’une nouvelle voiture puisse aller à l’usine sans avoir à savoir qui réalisera la construction.

Donc j’ai

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 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 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 types = creator->list_cars_I_create(); for ( std::list::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 ); } 

Ensuite, j’aurai des voitures spécifiques et des créateurs de voitures spécifiques:

Miata.cpp

 class Miata : public Car {...}; class MiataCreator : public CarCreator { public: virtual Car * create_a_car( CarType ); virtual std::list 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() ); 

Pour répéter: lier dynamicment mes bibliothèques, MiataCreator :: registered sera initialisé, lier statiquement mes bibliothèques, il ne sera pas initialisé.

Avec une construction statique, lorsque quelqu’un se rend à l’usine pour demander une Miata, l’élément miata de car_creator_map pointe sur NULL et le programme se car_creator_map .

Y a-t-il quelque chose de spécial avec les membres de données intégrales statiques privés qui empêche leur initialisation? Les membres de données statiques ne sont-ils initialisés que si la classe est utilisée? Mes classes CarCreator ne sont déclarées dans aucun fichier d’en-tête; ils vivent entièrement dans le fichier .cpp. Est-il possible que le compilateur intègre la fonction d’initialisation et évite en quelque sorte l’appel à MyFactory :: factory_register ?

Existe-t-il une meilleure solution à ce problème d’enregistrement?

Il n’est pas possible de répertorier tous les CarCreators dans une seule fonction, de les enregistrer explicitement auprès de la fabrique, puis de garantir que la fonction est appelée. En particulier, je souhaite lier plusieurs bibliothèques et définir CarCreators dans ces bibliothèques distinctes, tout en utilisant une fabrique singulière pour les construire.

Voici quelques réponses que j’anticipe mais qui ne résolvent pas mon problème:

1) votre singleton Factory n’est pas thread-safe. a) Ça ne devrait pas avoir d’importance, je travaille avec un seul thread.

2) votre singleton Factory peut ne pas être initialisée lorsque vos CarCreators sont en cours d’initialisation (c’est-à-dire que vous avez un fiasco d’initialisation statique) a) J’utilise une version sécurisée de la classe singleton en plaçant l’instance singleton dans une fonction. Si cela MiataCreator's::register_with_factory problème, je devrais voir la sortie si MiataCreator's::register_with_factory une instruction print à la MiataCreator's::register_with_factory : ce n’est pas le cas.

Je pense que vous avez un fiasco d’ordre d’initialisation statique, mais pas avec la fabrique.

Ce n’est pas que le drapeau enregistré ne soit pas initialisé, mais simplement assez tôt.

Vous ne pouvez pas compter sur un ordre d’initialisation statique, sauf dans la mesure où:

  1. Les variables statiques définies dans la même unité de traduction (fichier .cpp) seront initialisées dans l’ordre indiqué
  2. Les variables statiques définies dans une unité de traduction seront initialisées avant que toute fonction ou méthode de cette unité de traduction ne soit invoquée pour la première fois.

Ce sur quoi vous ne pouvez pas compter, c’est qu’une variable statique sera initialisée avant qu’une fonction ou une méthode dans une autre unité de traduction ne soit appelée pour la première fois.

En particulier, vous ne pouvez pas compter sur MiataCreator :: registered (défini dans Miata.cpp) pour être initialisé avant que MyFactory :: create_car (défini dans MyFactory.cpp) ne soit appelé pour la première fois.

Comme pour tout comportement non défini, vous obtiendrez parfois ce que vous voulez, et parfois vous ne le ferez pas. Les choses les plus étranges, apparemment les moins apparentées (telles que la liaison statique ou dynamic) peuvent changer, que cela fonctionne comme vous le souhaitez ou non.

Ce que vous devez faire, c’est créer une méthode d’accesseur statique pour l’indicateur enregistré défini dans Miata.cpp et demander à la fabrique MyFactory d’obtenir la valeur par l’intermédiaire de cet accesseur. Puisque l’accesseur est dans la même unité de traduction que la définition de la variable, la variable sera initialisée au moment de l’exécution de l’accesseur. Vous devez ensuite appeler cet accesseur de quelque part.

Si avec des liens statiques, vous entendez append tous les fichiers objects (.o) au binary, cela devrait fonctionner de la même manière que le travail dynamic. Si vous avez créé une bibliothèque statique (.a), l’éditeur de liens ne les liera pas à l’intérieur, seuls les objects utilisés à l’intérieur du fichier. Les bibliothèques statiques sont liées et dans ce cas, aucune n’est utilisée explicitement.

Toutes les technologies d’enregistrement automatique dépendent du code de chargement et de ses méthodes pour éviter le fiasco statique, comme la fonction qui crée l’object et le retourne à la demande.

Mais si vous ne parvenez pas à charger cela ne fonctionnera jamais, lier des fichiers d’object ensemble fonctionnera et chargera également des bibliothèques dynamics, mais les bibliothèques statiques ne seront jamais liées sans dépendances explicites.

Généralement, avec les bibliothèques statiques, l’éditeur de liens n’extrait que les fichiers .o de cette bibliothèque référencée par le programme principal. Puisque vous ne faites pas référence à MiataCreator :: registered ou à quoi que ce soit dans Miata.cpp mais que vous vous reposiez sur une initialisation statique, l’éditeur de liens n’inclura même pas ce code dans votre exe s’il est lié à partir d’une bibliothèque statique.

Vérifiez l’exécutable résultant avec nm ou objdump (ou dumpbin si vous êtes sur Windows) si le code de MiataCreator :: registered est réellement inclus dans le fichier exe lorsque vous créez un lien statique.

Cependant, je ne sais pas comment forcer l’éditeur de liens à inclure tous les éléments d’une bibliothèque statique.

Avec gcc, vous pouvez append -Wl,--whole-archive myLib.a --Wl,--no-whole-archive . Cela forcera l’éditeur de liens à inclure les objects même s’ils ne sont pas référencés. Ce n’est cependant pas portable.

Quand vérifie-t-on si l’élément miata est à l’intérieur de la carte? est-ce avant ou après principal?
La seule raison pour laquelle je pouvais penser est d’accéder aux éléments de la carte avant main () (comme dans l’initialisation globale), ce qui peut avoir lieu avant la création de MiataCreator :: registered (si elle est dans une unité de traduction différente)

Personnellement, je pense que vous êtes en train de jongler avec l’éditeur de liens.

Les variables booléennes ne sont pas utilisées ‘bool MiataCreator :: registered’ si l’éditeur de liens ne les extrait pas de la bibliothèque dans l’exécutable (rappelez-vous que s’il n’y a pas de référence à une fonction / globale dans l’exécutable, l’éditeur de liens ne les extraira pas d’object la lib [Elle recherche uniquement les objects actuellement indéfinis dans l’exécutable])

Vous pouvez append des instructions print dans ‘bool MiataCreator :: register_with_factory ()’ pour voir si elle est appelée. Ou vérifiez les symboles dans votre exécutable pour vérifier qu’il est là.

Certaines choses que je ferais:

 // 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; } 

Plutôt que d’avoir une initialisation en deux étapes de l’object. Ce que je soupçonne est en train d’échouer à cause de l’éditeur de liens. Créez une instance de votre usine et demandez au constructeur de l’enregistrer.

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

Je ferais ceci:

 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; 

L’éditeur de liens connaît les constructeurs et les objects C ++ et devrait extraire toutes les variables globales car les constructeurs peuvent potentiellement avoir des effets secondaires (je sais que votre nom le fait aussi, mais je peux voir que certains éditeurs de liens optimisent cela).

Quoi qu’il en soit, ma valeur 2c.