3

我有一个超类的几个子类,并想根据给定的字符串创建特定类的实例

Superclass instantiateSubclass(string s);

我不想拥有一个巨大的 if 级联,而是想使用一个配置文件来实现这一点。

这使我可以在不重新编译的情况下更改 string s 的可能值,我希望它会产生更简洁的代码。

配置文件应该包含诸如“subclass1”、“subclass2”之类的字符串,但是如何根据字符串创建类呢?

基本上我需要从字符串到类的映射,这在 C++ 中可能吗?我认为其他语言为这个问题提供了反射等可能性。

4

4 回答 4

10

注册您的课程:

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)();
}
于 2013-01-24T18:27:08.203 回答
3

我以前做过类似的事情。构建字符串(类型)/函数指针(工厂)对的 unordered_map。每一个都指向一个创建该类型实例的静态函数。

这仍然需要您拥有那些小存根工厂函数,通常它们是创建该类型的单行代码。宏可用于生成工厂方法。

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 在这些版本中的实现完全不正确。

于 2013-01-24T18:27:32.470 回答
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);
于 2013-01-24T18:40:10.293 回答
0

无论您使用配置文件还是其他方式,在某些时候您都需要将字符串转换为“new someclass”调用[或类似的东西]。这将涉及比较一个字符串,并选择要新建的正确项目。要么是 if/else if/else if ... 的长链,要么是一个表,然后您可以根据从表中的第二个条目中获得的某个整数常量在 switch 语句中进行选择。

另一种可能性当然是将问题完全转移到不同的级别,并为每个类实现一个共享库,并根据其名称加载相关库,然后在共享库中具有定义名称/序号的功能你可以打电话来“让我成为你喜欢的对象之一”。

于 2013-01-24T18:27:24.747 回答