(静态初始化/模板实例化)工厂模式的问题

时间:2010-05-17 19:10:13

标签: c++ static factory

为什么以下代码引发异常(在createObjects调用map::at中) 可选地,可以查看代码(及其输出)here

如果注释行使用microsoft和gcc编译器取消注释(参见here),代码可以按预期工作,这甚至可以将initMap用作普通的静态变量而不是静态getter。

我能想到的唯一原因是静态registerHelper_对象(factory_helper_)和std::map对象(initMap)的初始化顺序是错误的,但是我无法看到这可能发生的原因,因为map对象是在第一次使用时构建的,那就是在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 个答案:

答案 0 :(得分:7)

问题与初始化顺序无关,而是与模板实例化有关。

模板化代码是按需实例化的,也就是说,编译器不会实例化程序中未使用的任何模板化代码。特别是,在你的情况下,静态类成员FactoryBase<>::factory_helper_没有被实例化,因此它不存在于最终二进制文件中,它不会自己注册...(你可以用gnu中的'nm'来检查它工具链,将显示可执行文件中存在的符号列表

尝试将FactoryBase构造函数更改为:

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

这会强制编译器实际实例化二进制文件中的静态成员,你应该设置它。无需创建空方法并调用它。

编辑:作为评论的答案,在当前标准的段落§14.7.1[temp.inst] / 1 的末尾:

  

除非是类模板的成员或   成员模板已明确   实例化或明确地   专业化,专业化   当成员被隐式实例化时   专业化在a中引用   需要会员的上下文   定义存在;特别是,   初始化(和任何相关的   副作用)静态数据成员   除非静态数据不会发生   成员本身就是以某种方式使用的   需要静态的定义   数据成员存在。

答案 1 :(得分: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_>

显式实例化factory_helper_修复了问题。