Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Instantiation of class by classname

I have multiple classes that share a common base class, like this:

class Base {};

class DerivedA : public Base {};
class DerivedB : public Base {};
class DerivedC : public Base {};

Now, I need to know which of these derived classes to instantiate during runtime (based on input). For example, if input is "DerivedA", I need to create a DerivedA object. The input is not necessarily a string, it could be an integer as well - the point is that there is a key of some sort and I need a value to match the key.

The problem is, though, how do I instantiate the class? C++ does not have built-in reflection like C# or Java. A commonly suggested solution I've found is to use a factory method like this:

Base* create(const std::string& name) {
    if(name == "DerivedA") return new DerivedA();
    if(name == "DerivedB") return new DerivedB();
    if(name == "DerivedC") return new DerivedC();
}

This would be sufficient if there's only a couple of classes, but becomes cumbersome and probably slow if there's tens or hundreds of derived classes. I could quite easily automate the map creation process to produce a std::map<std::string, ***>, but I have no idea what to store as the value. AFAIK, pointers to constructors are not allowed. Again, if I do a factory using this map, I'd still need to write a factory method for each type, making it even more cumbersome than the example above.

What would be an efficient way to handle this problem, especially when there's lots of derived classes?

like image 812
manabreak Avatar asked Dec 19 '22 11:12

manabreak


1 Answers

You can always store std::function<Base*()> as you always return pointers to Base from your create function:

class Base {};

class DerivedA : public Base {};
class DerivedB : public Base {};
class DerivedC : public Base {};

Base* create(const std::string& type)
{
    static std::map<std::string, std::function<Base*()>> type_creator_map =
    {
        {"DerivedA", [](){return new DerivedA();}},
        {"DerivedB", [](){return new DerivedB();}},
        {"DerivedC", [](){return new DerivedC();}}
    };

    auto it = type_creator_map.find(type);
    if(it != type_creator_map.end())
    {
        return it->second();
    }

    return nullptr;
}

As Angew suggested, you should return std::unique_ptr instead of raw pointers. If the user of create function wants a raw pointer or a std::shared_ptr he/she can just "grab" the raw pointer and use it.

UPDATE:

Next method provides a convenient semi-automatic way of registering new types without changing old code.

I don't recommend using it because it depends on the linker (the moment of creating global variables might be delayed), they way you compile the code(executable, static library, dynamic library), it allocates memory before main() starts and it creates weird named global variables.

Use it only if you really know what you are doing and know on what platforms you are using the code!

class Base {};

std::map<std::string, std::function<Base*()>>& get_type_creator_map()
{
    static std::map<std::string, std::function<Base*()>> type_creator_map;
    return type_creator_map;
}

template<typename T>
struct RegisterTypeHelper
{
    RegisterTypeHelper(const std::string& id)
    {
        get_type_creator_map()[id] = [](){return new T();};
    }
};

Base* create(const std::string& type)
{
    auto& type_creator_map = get_type_creator_map();
    auto it = type_creator_map.find(type);
    if(it != type_creator_map.end())
    {
        return it->second();
    }

    return nullptr;
}

#define RegisterType(Type) static RegisterTypeHelper<Type> register_type_global_##Type(#Type)

class DerivedA : public Base {};
RegisterType(DerivedA);

class DerivedB : public Base {};
RegisterType(DerivedB);

class DerivedC : public Base {};
RegisterType(DerivedC);
like image 94
Mircea Ispas Avatar answered Dec 30 '22 12:12

Mircea Ispas