I'm trying to mimic this Python functionality:
class Bla:
def __init__(self, arg):
self.arg = arg
{"bla": Bla}["bla"](None) # create a Bla class dynamically
I looked at this but it didn't help me...
My C module looks something like this:
// MyObj is an abstract class with all its subclasses using same signature constructor
class Factory{
MyObj create_obj(int n);
}
std::map<std::string, MyObj> my_map = {"example1", MyObj1};
my_map["example1"](0);
How can I make this work?
CodePudding user response:
An object factory normally looks like this (most non-essential details omitted).
class Animal { virtual ~Animal() = default; }
class Dog : public Animal {};
class Cat : public Animal {};
class AnimalFactory
{
public:
std::unique_ptr<Animal> make(std::string kind) {
if (kind == "cat") return std::make_unique<Cat>();
if (kind == "dog") return std::make_unique<Dog>();
throw FactoryException("Unsupported animal");
}
};
Now you can, instead of if-else
in make
, use a map. But this is an implementation detail.
class AnimalFactory
{
using fn = std::function<std::unique_ptr<Animal>()>;
std::map<std::string, fn> make_map = std::map<std::string, fn> {
{"cat", []() { return std::make_unique<Cat>(); }},
{"dog", []() { return std::make_unique<Dog>(); }}
};
public:
std::unique_ptr<Animal> make(std::string kind) {
auto fn = make_map.find(kind);
if (fn != make_map.end()) return fn->second();
else throw FactoryError("Unsupported animal");
}
};
Things to note:
- Objects made by the factory are polymorphic. (Otherwise there usually isn't a lot of sense in having a factory.)
- Objects are returned by pointer to their base class.
- The map, if any, maps strings to functions that make objects.
CodePudding user response:
C , in contrast to Python, is statically typed. Classes are not first-class objects and you can't store them.
In the following I assume that MyObj
is the abstract base that all "stored" types are meant to inherit from and that MyObj1
/MyObj2
are some of these types.
Instead of the classes themselves, you can store function pointers or std::function
s which return a std::unique_ptr<MyObj>
pointing to a newly-created object of the corresponding type, so that my_map["example1"](0);
has a well-defined static type. For example:
#include<functional> // for std::function
#include<memory> // for std::unique_ptr and std::make_unique
#include<type_traits> // for std::has_virtual_destructor_v
//...
template<typename T>
std::unique_ptr<MyObj> MakeMyObj(int n) {
static_assert(std::has_virtual_destructor_v<MyObj>);
return std::make_unique<T>(n);
}
// or std::map<std::string, std::unique_ptr<MyObj>(*)(int)>
std::map<std::string, std::function<std::unique_ptr<MyObj>(int)>> my_map = {
{ "example1", MakeMyObj<MyObj1> },
{ "example2", MakeMyObj<MyObj2> }
};
//...
auto x = my_map["example1"](0); // x has type std::unique_ptr<MyObj>
auto y = my_map["example2"](123); // y also has type std::unique_ptr<MyObj>
std::function
supports more types of callables than a function pointer approach would, but has significantly more runtime overhead. With the function pointer approach, trying to use an unassigned string argument will also result in undefined behavior, while the std::function
approach will throw an exception.
As mentioned in the other answer, a more straight-forward approach if you don't need to modify the map at runtime is to write a single factory function or class which directly switches on string arguments and throws an appropriate exception directly.
In any case it is important that this requires that MyObj
has a virtual
destructor. Otherwise the program will have undefined behavior. That is what the static_assert
is meant to protect against, since std::unique_ptr
itself currently unfortunately doesn't verify that.