Instantiation of class by classname
我有多个类共享一个公共的基类,如下所示:
1 2 3 4 5 | class Base {}; class DerivedA : public Base {}; class DerivedB : public Base {}; class DerivedC : public Base {}; |
现在,我需要知道在运行时(基于输入)要实例化哪些派生类。例如,如果输入是
但问题是,如何实例化类?C++没有内置的反射,例如C语言或Java语言。我发现一个常见的建议解决方案是使用这样的工厂方法:
1 2 3 4 5 | Base* create(const std::string& name) { if(name =="DerivedA") return new DerivedA(); if(name =="DerivedB") return new DerivedB(); if(name =="DerivedC") return new DerivedC(); } |
如果只有几个类,这就足够了,但是如果有几十个或数百个派生类,这就变得很麻烦,可能会很慢。我可以很容易地实现地图创建过程的自动化,以生成一个
处理这个问题的有效方法是什么,特别是当有很多派生类时?
您总是可以存储
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | class Base {}; class DerivedA : public Base {}; class DerivedB : public Base {}; class DerivedC : public Base {}; Base* create(const std::string& type) { static std::map<std::string, std::function<Base*()>> type_creator_map = { {"DerivedA", [](){return new DerivedA();}}, {"DerivedB", [](){return new DerivedB();}}, {"DerivedC", [](){return new DerivedC();}} }; auto it = type_creator_map.find(type); if(it != type_creator_map.end()) { return it->second(); } return nullptr; } |
正如Angew建议的,您应该返回
更新:
下一种方法提供了一种方便的半自动注册新类型而不更改旧代码的方法。
我不建议使用它,因为它取决于链接器(创建全局变量的时间可能会延迟)、编译代码的方式(可执行、静态库、动态库),它在
只有当您真正知道自己在做什么,并且知道自己在使用代码的平台上时,才使用它!
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 | class Base {}; std::map<std::string, std::function<Base*()>>& get_type_creator_map() { static std::map<std::string, std::function<Base*()>> type_creator_map; return type_creator_map; } template<typename T> struct RegisterTypeHelper { RegisterTypeHelper(const std::string& id) { get_type_creator_map()[id] = [](){return new T();}; } }; Base* create(const std::string& type) { auto& type_creator_map = get_type_creator_map(); auto it = type_creator_map.find(type); if(it != type_creator_map.end()) { return it->second(); } return nullptr; } #define RegisterType(Type) static RegisterTypeHelper<Type> register_type_global_##Type(#Type) class DerivedA : public Base {}; RegisterType(DerivedA); class DerivedB : public Base {}; RegisterType(DerivedB); class DerivedC : public Base {}; RegisterType(DerivedC); |
解决这个问题的一种方法是使用设计模式原型。
基本上,您不会通过直接初始化来创建派生类对象,而是通过克隆原型来创建派生类对象。您的
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | class Base { public: virtual ~Base() {} virtual Base* clone() = 0; }; class DerivedA : public Base { public: virtual DerivedA* clone() override { return new DerivedA; } }; Base* create(const std::string &name) { static std::map<std::string, Base*> prototypes { {"DerivedA", new DerivedA }, {"DerivedB", new DerivedB }, {"DerivedC", new DerivedC } }; return prototypes[name]->clone(); } |
为简洁起见,在示例中忽略了检查错误。
在实际的项目中,当然应该使用智能指针(如
I could quite easily automate the map creation process to produce a std::map, but I have no idea what to store as the value.
您需要将工厂方法存储为值,例如创建类实例的静态方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | class Base {}; class DerivedA : public Base { public: static Base* create(); ... } ... Base* DerivedA::create() { return new DerivedA(); } |
然后,您可以通过类似于
1 2 3 4 5 6 | typedef Base* (*FACTORY_FUNCTION)(); std::map<std::string, FACTORY_FUNCTION> factories; ... factories["ClassA"] = ClassA::create; |
if I do a factory using this map, I'd still need to write a factory method for each type
由于这些工厂方法非常简单,您可以通过一个简单的代码生成工具(例如,使用简单的shell脚本)自动创建它们。您可以维护一个类列表,或者从头文件中检索这个列表(例如,通过
有了这些信息,您可以自动创建必要的代码来自动将工厂方法添加到每个类中。使用相同的方法,您还可以生成注册函数,该函数需要调用一次,以便注册对象。