关于c ++:按类名实例化类

Instantiation of class by classname

我有多个类共享一个公共的基类,如下所示:

1
2
3
4
5
class Base {};

class DerivedA : public Base {};
class DerivedB : public Base {};
class DerivedC : public Base {};

现在,我需要知道在运行时(基于输入)要实例化哪些派生类。例如,如果输入是"DerivedA",我需要创建一个DerivedA对象。输入不一定是一个字符串,也可以是一个整数-要点是有一个某种类型的键,我需要一个值来匹配该键。

但问题是,如何实例化类?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();
}

如果只有几个类,这就足够了,但是如果有几十个或数百个派生类,这就变得很麻烦,可能会很慢。我可以很容易地实现地图创建过程的自动化,以生成一个std::map,但我不知道作为值存储什么。afaik,不允许指向构造函数的指针。同样,如果我使用这个映射来创建一个工厂,我仍然需要为每种类型编写一个工厂方法,这使得它比上面的例子更麻烦。

处理这个问题的有效方法是什么,特别是当有很多派生类时?


您总是可以存储std::function,因为您总是从create函数返回指向Base的指针:

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建议的,您应该返回std::unique_ptr,而不是原始指针。如果create函数的用户想要一个原始指针或一个std::shared_ptr函数,他/她可以"抓取"原始指针并使用它。

更新:

下一种方法提供了一种方便的半自动注册新类型而不更改旧代码的方法。

我不建议使用它,因为它取决于链接器(创建全局变量的时间可能会延迟)、编译代码的方式(可执行、静态库、动态库),它在main()启动之前分配内存,并创建奇怪的命名全局变量。

只有当您真正知道自己在做什么,并且知道自己在使用代码的平台上时,才使用它!

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);


解决这个问题的一种方法是使用设计模式原型。

基本上,您不会通过直接初始化来创建派生类对象,而是通过克隆原型来创建派生类对象。您的create()函数实际上是工厂方法设计模式的实现。您可以在实现中使用原型,如下所示:

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();
}

为简洁起见,在示例中忽略了检查错误。

在实际的项目中,当然应该使用智能指针(如std::unique_ptr)而不是原始指针来管理对象的生命周期。


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脚本)自动创建它们。您可以维护一个类列表,或者从头文件中检索这个列表(例如,通过grepping对class关键字进行ping并检索后续的类名,或者使用一些正确解析头文件的分析工具更好地检索后续的类名)。

有了这些信息,您可以自动创建必要的代码来自动将工厂方法添加到每个类中。使用相同的方法,您还可以生成注册函数,该函数需要调用一次,以便注册对象。