在C ++中使用String-To-Class Lookup表来实例化类


Use String-To-Class Lookup table in C++ to instantiate classes

寻找避免大量if/else的方法,并使用查找表将字符串解析为要实例化的特定类,这些类都是从基类派生的。这样的事情可能发生吗?如果可能,怎么可能?

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
typedef struct BaseClass
{
} BaseClass;

typedef struct DerivedClassOne : BaseClass
{
} DerivedClassOne;

typedef struct DerivedClassTwo : BaseClass
{
} DerivedClassTwo;

typedef struct
{
    const char *name;
    BaseClass class;
} LookupList;

LookupList list[] = {
    {"ClassOne", DerivedClassOne},
    {"ClassTwo", DerivedClassTwo}
};

BaseClass *InstantiateFromString(char *name)
{
    int i;
    for (i = 0; i < 2; i++)
    {
        if (!strcmp(name, list[i].name))
            return new list[i].class();
    }
}

int main (int argc, char *argv[])
{
    BaseClass *myObjectFromLookup = InstantiateFromString("ClassOne");
}


如果编译器与C++ 11兼容,那么您可以很容易地用lambdas和eDCOX1 1来实现这一点:

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
#include <iostream>
#include <string>
#include <map>
#include <functional>

using namespace std;

struct BaseClass {virtual void foo()=0;};
struct DerivedClass1 : public BaseClass {void foo() {cout <<"1" << endl;}};
struct DerivedClass2 : public BaseClass {void foo() {cout <<"2" << endl;}};

// Here is the core of the solution: this map of lambdas does all the"magic"
map<string,function<BaseClass*()> > factory {
    {"one", [](){return new DerivedClass1();}}
,   {"two", [](){return new DerivedClass2();}}
};

int main() {
    BaseClass *a = factory["one"](); // Note the function call () at the end
    BaseClass *b = factory["two"]();
    a->foo();
    b->foo();
    delete a;
    delete b;
    return 0;
}

其思想是创建一个映射,该映射为您提供一个生成适当子类的函数。

IDENO演示。


首先,语法入门:

1
2
3
struct Base {
    virtual ~Base() {} // do not forget this if you need polymorphism
};

然后,"工厂"功能:

1
2
template <typename T>
std::unique_ptr<Base> makeBase() { return std::unique_ptr<Base>(new T{}); }

此函数的类型为:

1
using BaseMaker = std::unique_ptr<Base>(*)();

最后,总而言之:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
struct DerivedOne: Base {}; struct DerivedTwo: Base {};

using BaseMakerMap = std::map<std::string, BaseMaker>;

BaseMakerMap const map = { {"DerivedOne", makeBase<DerivedOne> },
                           {"DerivedTwo", makeBase<DerivedTwo> } };

std::unique_ptr<Base> makeFromName(std::string const& n) {
    BaseMakerMap::const_iterator it = map.find(n);

    if (it == map.end()) { return std::unique_ptr<Base>(); } // not found

    BaseMaker maker = it->second;

    return maker();
}


您应该能够执行以下操作:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
template<class C>
BaseClass * makeObject<C> () {
    return new C;
}

struct LookupList {
    const char* name;
    BaseClass * (*factoryFunction) ();
};

LookupList list [] = {
    {"ClassOne", &makeObject<DerivedClassOne>},
    {"ClassTwo", &makeObject<DerivedClassTwo>}
};

...

... instantiateFromString ...
    return list[i].factoryFunction ();

不过,我更喜欢LookupList的映射而不是数组。此外,您可能希望熟悉C++ 11中的函数语法。


不能这样初始化列表。

1
2
3
4
5
6
7
8
9
10
typedef struct
{
    const char *name;
    BaseClass class;
} LookupList;

LookupList list[] = {
    {"ClassOne", DerivedClassOne},
    {"ClassTwo", DerivedClassTwo}
};

list.class是一个baseclass对象,而初始化值是派生的classone,它是一个类。这没有道理。您将得到编译器错误。


基本上,你可能正在寻找C++中的反射。有个讨论,你可能会发现一些有用的东西如何向C++应用程序添加反射?