(статическая инициализация / создание шаблона) проблемы с фабричным шаблоном - PullRequest
4 голосов
/ 17 мая 2010

Почему следующий код вызывает исключение (в createObjects вызывается map::at) Альтернативно код (и его вывод) можно посмотреть здесь

Интересно, что код работает должным образом, если закомментированные строки не закомментированы как с помощью Microsoft, так и компилятора gcc (см. здесь ), это даже работает с initMap в качестве обычной статической переменной вместо статического геттера.

Единственная причина, по которой я могу думать, состоит в том, что порядок инициализации статического объекта registerHelper_ (factory_helper_) и объекта std::map (initMap) неверен, однако я не вижу, как это могло произойти потому что объект карты создается при первом использовании, и это в конструкторе factory_helper_, поэтому все должно быть в порядке, не так ли? Я даже более удивлен, что эти строки doNothing () решают проблему, потому что этот вызов doNothing () произойдет после того, как критическая секция (которая в настоящее время не работает) все равно будет пройдена.

РЕДАКТИРОВАНИЕ: отладка показала, что без вызова factory_helper_.doNothing () конструктор factory_helper_ никогда не вызывается.

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

Ответы [ 2 ]

7 голосов
/ 17 мая 2010

Проблема не связана с порядком инициализации, а скорее с созданием шаблона.

Шаблонный код создается по требованию, то есть компилятор не создает экземпляр шаблонного кода, который не используется в вашей программе. В частности, в вашем случае статический член класса FactoryBase<>::factory_helper_ не создается, и поэтому он не существует в конечном двоичном файле, он не регистрируется сам ... (вы можете проверить это с помощью 'nm' из цепочки инструментов gnu, который покажет список символов, присутствующих в вашем исполняемом файле)

Попробуйте изменить конструктор FactoryBase на это:

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

Это заставит компилятор создать экземпляр статического члена в двоичном файле, и вы должны быть установлены. Нет необходимости создавать пустой метод и вызывать его.

РЕДАКТИРОВАТЬ : в качестве ответа на комментарий к концу пункта §14.7.1 [temp.inst] / 1 в текущем стандарте:

Если только член шаблона класса или шаблон члена был явно конкретизированный или явно специализированная, специализация член неявно создается, когда специализация упоминается в контекст, который требует члена определение существовать; особенно, инициализация (и любые связанные побочные эффекты) статического члена данных не происходит, если статические данные член сам по себе используется таким образом, что требует определения статического элемент данных существует.

0 голосов
/ 18 мая 2010
#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_>

явное создание экземпляра factory_helper_ исправило проблему.

...