我有多个共享公共基类的类,如下所示:
class Base {};
class DerivedA : public Base {};
class DerivedB : public Base {};
class DerivedC : public Base {};
现在,我需要知道在运行时(基于输入)实例化这些派生类中的哪一个。例如,如果输入是"DerivedA"
,我需要创建一个DerivedA
对象。输入不一定是字符串,它也可以是整数 - 关键是有某种键,我需要一个值来匹配键。
问题是,如何实例化该类? C ++没有像C#或Java那样的内置反射。我发现一个常用的解决方案是使用这样的工厂方法:
Base* create(const std::string& name) {
if(name == "DerivedA") return new DerivedA();
if(name == "DerivedB") return new DerivedB();
if(name == "DerivedC") return new DerivedC();
}
如果只有几个类,这就足够了,但是如果有几十个或几百个派生类,那就变得很麻烦并且可能会很慢。我可以很容易地自动化地图创建过程以生成std::map<std::string, ***>
,但我不知道要存储什么作为值。 AFAIK,不允许指向构造函数的指针。同样,如果我使用这个地图做工厂,我仍然需要为每种类型编写一个工厂方法,使它比上面的例子更麻烦。
处理这个问题的有效方法是什么,特别是当有很多派生类时?
答案 0 :(得分:7)
您始终可以存储std::function<Base*()>
,因为您总是从Base
函数返回指向create
的指针:
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建议的那样,你应该返回std::unique_ptr
而不是原始指针。如果create
函数的用户想要原始指针或std::shared_ptr
他/她可以“抓住”原始指针并使用它。
更新:
Next方法提供了一种方便的半自动方式来注册新类型而无需更改旧代码。
我不建议使用它,因为它取决于链接器(创建全局变量的时刻可能会被延迟),它们编译代码的方式(可执行文件,静态库,动态库),它在{{{{{{{{ 1}}启动并创建奇怪的命名全局变量。
只有在您真正了解自己在做什么并了解使用该代码的平台时才使用它!
main()
答案 1 :(得分:3)
解决此问题的一种方法是使用设计模式Prototype。
基本上,您不会通过直接初始化来创建派生类对象,而是通过克隆原型来创建。您的create()
函数实际上是Factory method设计模式的实现。您可以在实现中使用Prototype,如下所示:
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();
}
为简洁起见,在示例中省略了错误。
在实际项目中,您当然应该使用智能指针(例如std::unique_ptr
)而不是原始指针来管理对象&#39;寿命。
答案 2 :(得分:1)
我可以很容易地自动化地图创建过程以生成std :: map,但我不知道要存储什么值。
您需要将工厂方法存储为值,例如一个静态方法,用于创建类的实例:
class Base {};
class DerivedA : public Base {
public:
static Base* create();
...
}
...
Base* DerivedA::create() {
return new DerivedA();
}
然后,您可以通过像
这样的地图实现名称/查找typedef Base* (*FACTORY_FUNCTION)();
std::map<std::string, FACTORY_FUNCTION> factories;
...
factories["ClassA"] = ClassA::create;
如果我使用这张地图做工厂,我仍然需要为每种类型编写工厂方法
由于这些工厂方法非常简单,您可以通过简单的代码生成工具 自动化创建(例如,使用简单的shell脚本)。您可以维护一个类列表,也可以从头文件中检索此列表(例如通过grep
ping class
关键字并检索后续的类名,或者甚至更好地使用一些分析工具正确解析头文件。)
使用该信息,您可以自动创建必要的代码,以自动将工厂方法添加到每个类。使用相同的方法,您还可以生成需要调用一次的注册函数,以便您的对象被注册。