Create object according to string in C ++

I have multiple subclasses of the same superclass and want to instantiate a specific class according to a given string

Superclass instantiateSubclass(string s);

      

Instead of having a huge if cascade, I want to work with a config file to accomplish this.

This allows me to change the possible values ​​for string s without recompiling, and I hope this results in more concise code.

The config file should contain lines like "subclass1", "subclass2", but how do I create the class line by line?

Basically I need a string to class mapping, is this possible in C ++? I think other languages ​​offer possibilities like reflecting this problem.

+3


source to share


4 answers


Register your classes:

struct Base;

struct Derived1 : Base
{
    static Base * create() { return new Derived1; }
};

std::map<std::string, Base * (*)()> registry = { {"derived1", &Derived1::create},
                                                 /* ... */
                                               };

      



To do:

Base * create_from_string(std::string const & s)
{
    auto it = registry.find(s);
    return it == registry.end() ? nullptr : (it->second)();
}

      

+9


source


I've done something similar before. Create an unordered_map of string (type) / function (factory) pairs. Each one points to a static function that instantiates the type.

It still requires you to have these little factory stub functions, usually the one-liners that create this type. Macros can be used to generate factory methods.

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

      



Only you know where the factory name belongs, I didn't put it in "anything" in my example.

Note. If you are using MSVC 10 or below (2010 or below) use types.push_back(TypeDirectory::value_type("Hello", &Hello::Create));

as emplacement_back has a completely wrong implementation in these versions.

+3


source


In the following code, you can register any number of classes derived from the base INTERFACE

and create them using the string returned from register_class

.

Outside, the sides will potentially differ between platforms and you will need to know what type (CLASS) .name () returns for each class to make sure you put the correct class key in your config file (it may not match the class name )

More on typeid to see what's going on here

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

      

Use it like this:

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

      

+1


source


If you are using a config file anyway, at some point you will need to translate the string into a "new socket" call [or something like that]. This will require comparing the string and selecting the correct element to create a new one. Either a long chain if / else if / else if ... or a table from which you can make a choice in a switch statement based on some integer constant, you've got a second record in the table.

Another possibility is, of course, to move the problem entirely to another level and implement a shared library for each class and load the appropriate library based on its name, and then use a specific name / ordinal function that you can call to "make me one of your objects ".

0


source







All Articles