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

sme*_*lin 4 c++ static factory

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

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

我可以想到的唯一原因是静态registerHelper_ object(factory_helper_)和std::mapobject(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;
}
Run Code Online (Sandbox Code Playgroud)

Dav*_*eas 7

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

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

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

template <class TClass, const char* ClassName>
class FactoryBase {
   //...
   FactoryBase(){
      factory_helper_;
   }
   //...
};
Run Code Online (Sandbox Code Playgroud)

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

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

除非已显式实例化或明确专门化类模板或成员模板的成员,否则在需要成员定义存在的上下文中引用特化时,将隐式实例化成员的特化; 特别是,除非静态数据成员本身以需要静态数据成员的定义存在的方式使用,否则不会发生静态数据成员的初始化(以及任何相关的副作用).