根据C ++中的字符串创建对象

时间:2013-01-24 18:21:21

标签: c++ reflection

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

Superclass instantiateSubclass(string s);

我没有使用巨大的if-cascade,而是想使用配置文件来实现这一目标。

这允许我在不重新编译的情况下更改字符串s的可能值,我希望它能带来更简洁的代码。

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

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

4 个答案:

答案 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)

我之前做过类似的事情。构建字符串(类型)/函数指针(工厂)对的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在这些版本中的实现完全不正确。

答案 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语句中进行选择。

另一种可能性当然是将问题完全移到另一个级别,并为每个类实现一个共享库,并根据它的名称加载相关库,然后在其中具有一个已定义名称/序号的函数。共享库,你可以调用“让我成为你的一种对象”。