我有一个超类的几个子类,并希望根据给定的字符串创建特定类的实例
Superclass instantiateSubclass(string s);
我没有使用巨大的if-cascade,而是想使用配置文件来实现这一目标。
这允许我在不重新编译的情况下更改字符串s的可能值,我希望它能带来更简洁的代码。
配置文件应包含“subclass1”,“subclass2”等字符串,但是如何根据字符串创建类呢?
基本上我需要从字符串到类的映射,这在C ++中是否可行?我认为其他语言提供了解决这个问题的可能性。
答案 0 :(得分:9)
注册课程:
struct Base;
struct Derived1 : Base
{
static Base * create() { return new Derived1; }
};
std::map<std::string, Base * (*)()> registry = { {"derived1", &Derived1::create},
/* ... */
};
制作:
Base * create_from_string(std::string const & s)
{
auto it = registry.find(s);
return it == registry.end() ? nullptr : (it->second)();
}
答案 1 :(得分:3)
这仍然要求您拥有那些小的存根工厂函数,通常它们是创建该类型的单行。宏可用于生成工厂方法。
typedef std::unordered_map<std::string, Superclass *(*)()> TypeDirectory;
TypeDirectory types;
#define NAMEFACTORY(name_) static Superclass *Create() { return new name_; }
class Hello : public Superclass
{
...
NAMEFACTORY(Hello)
};
static void RegisterFactoryNames()
{
types.emplace_back("Hello", &Hello::Create);
...
}
static Superclass *MakeInstance(std::string &name)
{
auto i = types.find(name);
return i != types.end() ? types->second() : 0;
}
只有你知道工厂数据的名称所在的位置,我没有把它放在我的例子中的任何内容中。
注意:如果您使用的是MSVC 10或更低版本(2010或更低版本),请使用types.push_back(TypeDirectory::value_type("Hello", &Hello::Create));
,因为emplace_back在这些版本中的实现完全不正确。
答案 2 :(得分:1)
使用以下代码,您可以注册从基类INTERFACE
派生的任意数量的类,并使用从register_class
返回的字符串对它们进行实例化。
缺点是平台之间的密钥可能会有所不同,你必须知道每个类返回什么typeid(CLASS).name()以确保在配置文件中放入正确的类密钥(它可能与班级名称不一样)
阅读更多关于typeid的信息,了解这里发生的事情
template <class INTERFACE> class factory
{
public:
template <class CLASS> const std::string register_class()
{
static const std::string key = typeid(CLASS).name();
class class_factory : public ifactory
{
private:
virtual std::shared_ptr<INTERFACE> create() const
{
return new CLASS;
}
};
m_factory_map[key] = new class_factory;
return key;
}
std::shared_ptr<INTERFACE> create(const std::string& key) const
{
const factory_map::const_iterator ifind = m_factory_map.find(key);
if(ifind == m_factory_map.end())
return 0;
return ifind->second->create();
}
private:
class ifactory
{
public:
virtual ~ifactory() {}
virtual std::shared_ptr<INTERFACE> create() const = 0;
};
typedef std::map<std::string, std::shared_ptr<ifactory> > factory_map;
factory_map m_factory_map;
};
像这样使用
factory<MyInterface> fact;
const std::string key1 = fact.register_class<MyClass1>();
const std::string key2 = fact.register_class<MyClass2>();
const std::string key3 = fact.register_class<MyClass3>();
std::shared_ptr<MyInterface> p1 = fact.create(key1);
std::shared_ptr<MyInterface> p2 = fact.create(key2);
std::shared_ptr<MyInterface> p3 = fact.create(key3);
答案 3 :(得分:0)
无论您使用配置文件还是其他方式,在某些时候您都需要将字符串转换为“新的某类”调用[或沿着这些行的某些内容]。这将涉及比较一个字符串,并选择正确的项目来创建新的。要么是if / else if / else if的长链,要么是一个表,然后你可以根据你得到表中第二个条目的某个整数常量在switch语句中进行选择。
另一种可能性当然是将问题完全移到另一个级别,并为每个类实现一个共享库,并根据它的名称加载相关库,然后在其中具有一个已定义名称/序号的函数。共享库,你可以调用“让我成为你的一种对象”。