2010-05-17 14 views
3

Pourquoi est-ce de code suivant soulèvent une exception (en createObjects appel à map::at) alternativly le code (et sa sortie) peut être vu here(initialisation statique/instanciation de modèle) problèmes avec motif d'usine

intererestingly le code fonctionne comme Si les lignes commentées sont décommentées avec les compilateurs microsoft et gcc (voir here), cela fonctionne même avec initMap en tant que variable statique ordinaire au lieu de getter statique.

La seule raison que je peux penser est que l'ordre d'initialisation de l'objet registerHelper_ statique (factory_helper_) et l'objet std::map (initMap) ont tort, mais je ne peux pas voir comment cela pourrait se produire, parce que l'objet carte est construit à la première utilisation et c'est dans factory_helper_ constructor, donc tout devrait bien se passer, n'est-ce pas? Je suis encore plus surpris que ces lignes de doNothing() corrigent le problème, parce que cet appel à doNothing() se produirait après que la section critique (qui échoue actuellement) soit passée de toute façon.

EDIT: Le débogage a montré que sans l'appel à factory_helper_.doNothing(), le constructeur de factory_helper_ n'est jamais appelé.

#include <iostream> 
#include <string> 
#include <map> 

#define FACTORY_CLASS(classtype) \ 
extern const char classtype##_name_[] = #classtype; \ 
class classtype : FactoryBase<classtype,classtype##_name_> 

namespace detail_ 
{ 
    class registerHelperBase 
    { 
    public: 
     registerHelperBase(){} 
    protected: 
     static std::map<std::string, void * (*)(void)>& getInitMap() { 
      static std::map<std::string, void * (*)(void)>* initMap = 0; 
      if(!initMap) 
       initMap= new std::map<std::string, void * (*)(void)>(); 
      return *initMap; 
     } 
    }; 

    template<class TParent, const char* ClassName> 
    class registerHelper_ : registerHelperBase { 
     static registerHelper_ help_; 
    public: 
     //void doNothing(){} 
     registerHelper_(){ 
      getInitMap()[std::string(ClassName)]=&TParent::factory_init_; 
     } 
    }; 
    template<class TParent, const char* ClassName> 
    registerHelper_<TParent,ClassName> registerHelper_<TParent,ClassName>::help_; 
} 

class Factory : detail_::registerHelperBase 
{ 
private: 
    Factory(); 
public: 
    static void* createObject(const std::string& objclassname) { 
     return getInitMap().at(objclassname)(); 
    } 
}; 


template <class TClass, const char* ClassName> 
class FactoryBase { 
    private: 
     static detail_::registerHelper_<FactoryBase<TClass,ClassName>,ClassName> factory_helper_; 
     static void* factory_init_(){ return new TClass();} 
    public: 
     friend class detail_::registerHelper_<FactoryBase<TClass,ClassName>,ClassName>; 
     FactoryBase(){ 
      //factory_helper_.doNothing(); 
     } 
     virtual ~FactoryBase(){}; 
}; 

template <class TClass, const char* ClassName> 
detail_::registerHelper_<FactoryBase<TClass,ClassName>,ClassName> FactoryBase<TClass,ClassName>::factory_helper_; 


FACTORY_CLASS(Test) { 
public: 
    Test(){} 
}; 

int main(int argc, char** argv) { 
    try { 
     Test* test = (Test*) Factory::createObject("Test"); 
    } 
    catch(const std::exception& ex) { 
     std::cerr << "caught std::exception: "<< ex.what() << std::endl; 
    } 
    #ifdef _MSC_VER 
     system("pause"); 
    #endif 
    return 0; 
} 
+0

Ajoutez une partie de l'impression de débogage à vos constructeurs et à vos fonctions d'accesseur, voir quels objets sont créés et quand. –

Répondre

7

Le problème n'est pas lié à l'ordre d'initialisation, mais plutôt à l'instanciation du modèle.

Le code basé sur un modèle est instancié à la demande, c'est-à-dire que le compilateur n'instancie aucun code basé sur un modèle qui n'est pas utilisé dans votre programme. En particulier, dans votre cas le membre de classe statique FactoryBase<>::factory_helper_ n'est pas instancié et donc n'existe pas dans le binaire final, il ne s'enregistre pas lui-même ... (vous pouvez vérifier cela avec 'nm' de la chaîne d'outils GNU, cela va afficher la liste des symboles présents dans l'exécutable)

Essayez de changer le constructeur FactoryBase à ceci:

template <class TClass, const char* ClassName> 
class FactoryBase { 
    //... 
    FactoryBase(){ 
     factory_helper_; 
    } 
    //... 
}; 

cela forcera le compilateur dans l'instanciation en fait le membre statique dans le binaire et vous devriez être réglé. Il n'est pas nécessaire de créer une méthode vide et de l'appeler.

EDIT: En réponse au commentaire, à la fin du paragraphe §14.7.1 [temp.inst]/1 dans la norme actuelle:

À moins qu'un membre d'une classe modèle ou un modèle de membre a été explicitement instancié ou explicitement spécialisés, la spécialisation du membre est implicitement instanciée lorsque la spécialisation est référencé dans un contexte qui exige que le membre définition d'exister; en particulier, l'initialisation (et tous associés effets secondaires) d'un élément de données statique ne se produit pas à moins que les données statiques élément lui-même est utilisé d'une manière qui nécessite la définition de l'élément de données statique d'exister.

+0

Je pensais que les membres statiques d'une classe modélisée sont automatiquement instanciés lorsque leur classe englobante est instanciée? – smerlin

+0

Pourquoi une fonction 'void doNothing() {factory_helper _;}} dans la FactoryBase ne provoque-t-elle pas la définition de' factory_helper_' (l'a-t-elle essayé)? n'est pas un usage qui nécessite la définition de 'factory_helper_' et devrait provoquer l'instanciation de celui-ci? – smerlin

+0

Si vous appelez 'doNothing' alors c'est un cas qui nécessite le membre statique, mais si vous ne l'appelez nulle part dans le code, alors la fonction membre' doNothing' ne sera pas instanciée et en tant que telle, elle ne se déclenchera pas instanciation de l'attribut de membre statique 'factory_helper_'. –

0
#define FACTORY_CLASS(classtype) \ 
class classtype; \ 
extern const char classtype##_name_[] = #classtype; \ 
template detail_::registerHelper_<FactoryBase<classtype,classtype##_name_>,classtype##_name_> FactoryBase<classtype,classtype##_name_>::factory_helper_; \ 
class classtype : FactoryBase<classtype,classtype##_name_> 

explicitement l'instanciation factory_helper_ fixe la question.