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?
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);
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With