在 C++ 中使用字符串到类查找表来实例化类
Posted
技术标签:
【中文标题】在 C++ 中使用字符串到类查找表来实例化类【英文标题】:Use String-To-Class Lookup table in C++ to instantiate classes 【发布时间】:2013-07-19 16:36:31 【问题描述】:寻找一种方法来避免大量 IF/ELSE 并使用查找表将字符串解析为特定类以进行实例化,这些类都派生自基类。 这样的事情可能吗?如果可以,怎么做?
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++ 中使用typedef class
,只是这样你就知道了。
为什么不改用std::map<std::string, factory>
(当然,factory
是适当工厂函数的原型)。重点是,不要使用 C 风格的字符串和线性搜索,使用适当的类。
查看工厂模式。 ***.com/questions/5120768/…
【参考方案1】:
首先,语法入门:
struct Base
virtual ~Base() // do not forget this if you need polymorphism
;
然后,一个“工厂”函数:
template <typename T>
std::unique_ptr<Base> makeBase() return std::unique_ptr<Base>(new T);
这个函数的类型是:
using BaseMaker = std::unique_ptr<Base>(*)();
最后,总结一下:
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();
【讨论】:
这看起来是一个很有前途的模式,但是除了修复makeFromName()
中的map
引用的问题之外,它还有一些编译错误:error C2207: 'std::pair<_Ty1,_Ty2>::second' : a member of a class template cannot acquire a function type
error C3867: 'std::pair<const _Kty,_Ty>::second': function call missing argument list; use '&std::pair<const _Kty,_Ty>::second' to create a pointer to member
error C2064: term does not evaluate to a function taking 0 arguments
@JohnKaster:我修复了函数指针声明和maker
的类型,它现在可以在ideone 上为我编译。
感谢您的更新!我需要一些稍微不同的东西(每次遇到我在下面发布的“名称”时都有一个新实例,但这种模式很有价值。
@JohnKaster:我给出的方法还会在每次遇到模式时创建一个新实例:映射存储函数指针,而不是实例,并且每次调用函数都会创建一个新实例。
感谢您的支持。我会更新我的模式以使用你的模式,因为它更优雅,并在我得到它工作后跟进。【参考方案2】:
如果您的编译器与 C++11 兼容,您可以使用 lambda 和 std::map
轻松做到这一点:
#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;
我们的想法是制作一个地图,为您提供一个创建适当子类的函数。
Demo on ideone.
【讨论】:
谢谢,这是正确的答案,虽然我忘了提到我试图避免使用 STL。听起来这一切只是在后台为每个实例化特定对象类型的数组条目创建小的静态函数。也许我可以在数组中有一个函数指针,每个指针指向一个单独的静态函数。 @user1054922 正确 - 标准库的使用是偶然的,我把它放在那里是为了缩短查找时间。您绝对可以用一对数组替换它,就像您在帖子中所做的那样。【参考方案3】:您应该能够做到以下几点:
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
,我更喜欢 Map 而不是数组。此外,您可能还想熟悉 C++11 中的函数式语法。
【讨论】:
【参考方案4】:你不能这样初始化你的列表。
typedef struct
const char *name;
BaseClass class;
LookupList;
LookupList list[] =
"ClassOne", DerivedClassOne,
"ClassTwo", DerivedClassTwo
;
list.class是一个BaseClass对象,而初始值是DerivedClassOne,是一个类。那没有意义。你会得到编译器错误。
【讨论】:
以上是关于在 C++ 中使用字符串到类查找表来实例化类的主要内容,如果未能解决你的问题,请参考以下文章
在不使用 STL 的情况下,在 C++ 中存储和实例化类列表的最佳方法是啥?