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

Create an object according to a string in C++

本文关键字:字符串 创建对象 C++ 根据      更新时间:2023-10-16

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

Superclass instantiateSubclass(string s);

与其拥有巨大的if级联,不如使用配置文件来完成此操作。

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

配置文件应该包含像"subclass1"、"subclass2"这样的字符串,但是我如何根据字符串创建类呢?

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

注册您的课程:

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)();
}

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

使用以下代码,您可以注册从基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);

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

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