我一直遵循this tutorial / example在C ++ 17中创建自注册类型。但是我遇到了一个问题:尝试注册类型时,我的程序终止,错误为read access violation. _Wherenode was nullptr.
。此错误发生在标准库的std::_Tree<std::Tmap_traits<...>::_Lbound
方法中。
这是我的源代码,与页面上的代码几乎相同(通过一些调整来修正原始代码所具有的错误:)
class Entity { ... }; // Contents irrelevant
class EntityTypeFactory {
public:
using TCreateMethod = std::function<Entity* ()>;
EntityTypeFactory() = delete;
static Entity* Create(const std::string& type);
static bool Register(const std::string& name, TCreateMethod func);
private:
static std::map<std::string, TCreateMethod> s_methods;
};
#define REG_ENT(TYPE) class TYPE##Creator { private: static bool s_created; }; \
bool TYPE##Creator::s_created = EntityTypeFactory::Register(#TYPE, []() -> Entity* { return new TYPE(); });
std::map<std::string, EntityTypeFactory::TCreateMethod>* EntityTypeFactory::s_methods;
Entity* EntityTypeFactory::Create(const std::string& type)
{
if (s_methods == nullptr) return nullptr;
return (*s_methods)[type]();
}
bool EntityTypeFactory::Register(const std::string& name, TCreateMethod func)
{
if (s_methods == nullptr)
s_methods = new std::map<std::string, TCreateMethod>();
(*s_methods)[name] = func;
return true;
}
class Foo : public Entity {}; // Contents irrelevant
REG_ENT(Foo)
编辑:这似乎与链接顺序有关,就好像我将测试实现Foo
放在源文件中时,该源文件早于Entity
按字母顺序排序(这似乎是VC ++选择链接顺序的方式),但是它失败了,但是如果我将此实现放在一个在之后链接的文件中,它将按预期工作。我仍然不确定如何使它通用。
答案 0 :(得分:0)
正如您在Edit中正确指出的那样,问题出在初始化的顺序上,即在调用main方法之前发生的事情。
在同一文件中(确切地说是编译单元),您可以依靠构造函数执行静态对象的顺序来匹配它们在源代码中出现的顺序。
当程序由多个编译单元组成时,编译单元的构建顺序取决于实现。
有一种方法可以实现您的目标,因为编译器首先初始化所有静态存储,然后调用静态对象构造函数-您需要确保每次访问static std::map<std::string, TCreateMethod> s_methods;
时都已正确初始化。您可以通过在方法内将其声明为静态来实现。例如:
static std::map<std::string, TCreateMethod>& methods()
{
static std::map<std::string, TCreateMethod> singleton;
return singleton;
}
这可确保首次调用此函数时,将构造映射对象(名为单例)并将其返回。
这项技术有时称为 Meyer's Singleton ,称为惰性评估,这意味着所需的操作会延迟到需要时才进行到最后一分钟。
(我在https://pabloariasal.github.io/2020/01/02/static-variable-initialization/上找到了有关C ++静态初始化的详细说明)