4

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?

Qiu
  • 5,651
  • 10
  • 49
  • 56
manabreak
  • 5,415
  • 7
  • 39
  • 96
  • 6
    Seems like a classic [factory pattern](http://stackoverflow.com/questions/5120768/how-to-implement-the-factory-method-pattern-in-c-correctly) is what you need here. – Fantastic Mr Fox Sep 24 '14 at 06:20
  • @Ben I'm not sure how that can solve the look-up problem. At the end of the day, you have to match some code to strings. – juanchopanza Sep 24 '14 at 06:24
  • 1
    A good reading on this is [Modern C++ Design, Generic Programming and Design Patterns Applied](http://www.amazon.de/Modern-Generic-Programming-Patterns-Applied/dp/0201704315), "Chapter 8: Object Factories" – Andreas Fester Sep 24 '14 at 06:27
  • @juanchopanza The `factory pattern` is one part of the solution - you need some (static) method/function which can create an object of a specific class. The second part is the mapping from name to factory method - that can easily be implemented through a map which has the name as key and the pointer to the factory method as value. The third part is the registration mechanism - each class needs to register its factory method with the map, using a unique name ("type identifier"). I think that OP already knows that in general, but looks for a solution when there are **many** classes – Andreas Fester Sep 24 '14 at 06:38
  • @Andreas It isn't really part of the solution. It is a was to implement a solution, but it doesn't solve the fundamental problem. For a simple mapping like the one shown in OP's example, it doesn't gain you much. A solution could be as simple as `unordered_map>`. – juanchopanza Sep 24 '14 at 06:38
  • 3
    The problem might be your design if you need to instantiate a different class by receiving a different enum/whatever each time.. and you have a large number of choices – Marco A. Sep 24 '14 at 06:42
  • 1
    possible duplicate of [Is there a way to instantiate objects from a string holding their class name?](http://stackoverflow.com/questions/582331/is-there-a-way-to-instantiate-objects-from-a-string-holding-their-class-name) – MatthiasB Sep 24 '14 at 06:47
  • @MarcoA. I don't really know how to do it differently - the different classes (and different behavior) are chosen based on input (a file). – manabreak Sep 24 '14 at 08:21

3 Answers3

8

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);
Mircea Ispas
  • 20,260
  • 32
  • 123
  • 211
  • 1
    This is exactly what I was looking for - I knew I could approach this somehow using lambdas! Of course, this isn't automatic, but like Andreas suggested, I can write a small tool to parse the headers and create the map using the parsed class names. Thanks! – manabreak Sep 24 '14 at 08:15
  • It can be (semi)automatic depending on the linker and if you accept using some globals/singletons. I will update the answer with a short sample – Mircea Ispas Sep 24 '14 at 08:20
  • 1
    The trailing return types are unnecessary. `std::function` accepts anything whose return type can be implicit converted to the return type specified in the template parameter. – T.C. Sep 24 '14 at 08:42
  • 1
    Notice that the second solution, while very clear, suffer from the order of initialization "fiasco". If you define a `register_type_global_xxx` in one T.U. and you try to create an object of the same type from another T.U. before `main` is invoked you might still get a `nullptr`. – sbabbi Sep 24 '14 at 09:47
  • @sbabbi Yes, I tried to be very specific that it's very dangerous and not recommended. AFAIK the standard requires globals to be instantiated before any function from the same T.U. is called. On all platforms I tried this, the globals are instantiated at the same time, but it's not portable, guaranteed or safe:) – Mircea Ispas Sep 24 '14 at 09:53
3

One way to solve this is to use the design pattern Prototype.

Basically, you wouldn't create the derived class objects by direct initialisation, but by cloning a prototype instead. Your create() function is actually a realisation of the Factory method design pattern. You can use Prototype inside the implementation, like this:

class Base
{
public:
  virtual ~Base() {}
  virtual Base* clone() = 0;
};

class DerivedA : public Base
{
public:
  virtual DerivedA* clone() override { return new DerivedA; }
};


Base* create(const std::string &name)
{
  static std::map<std::string, Base*> prototypes {
    { "DerivedA", new DerivedA },
    { "DerivedB", new DerivedB },
    { "DerivedC", new DerivedC }
  };
  return prototypes[name]->clone();
}

Error checking left out of the example for brevity.

In a real project, you should of course use a smart pointer (such as std::unique_ptr) instead of raw pointers to manage the objects' lifetimes.

Angew is no longer proud of SO
  • 167,307
  • 17
  • 350
  • 455
1

I could quite easily automate the map creation process to produce a std::map, but I have no idea what to store as the value.

You need to store a factory method as the value, e.g. a static method which creates an instance of your class:

class Base {};

class DerivedA : public Base {
public:
    static Base* create();

    ...
}

...

Base* DerivedA::create() {
    return new DerivedA();
}

You can then implement the name/lookup through a map like

typedef Base* (*FACTORY_FUNCTION)();
std::map<std::string, FACTORY_FUNCTION> factories;

...

factories["ClassA"] = ClassA::create;

if I do a factory using this map, I'd still need to write a factory method for each type

Since these factory methods are very simple, you can automate their creation by a simple code generation tool (e.g. with a simple shell script). You can either maintain a list of classes, or retrieve this list from your header files (e.g. by grepping for the class keyword and retrieve the succeeding class name, or even better by using some analysis tool which properly parses the header files).

With that information, you can automatically create the necessary code to automatically add the factory methods to each class. With the same approach, you could also generate the registration function which needs to be called once, so that your objects are getting registered.

Andreas Fester
  • 36,091
  • 7
  • 95
  • 123
  • I didn't down-vote, and can't see any reason to do so. Just a comment: why should the classes contain a static method to instantiate themselves? This seems like an unnecessary restriction, which makes the solution less general (i.e. it will only work for classes with this static member.) – juanchopanza Sep 24 '14 at 07:15
  • @juanchopanza So your question is why the factory method is a **static member** of the class - it could equally well also be a public method outside the class, or a static member inside a separate class which provides all the necessary factory functions, right? – Andreas Fester Sep 24 '14 at 07:22
  • I was thinking more of any callable that takes no parameters and returns a (smart) pointer. It could be a function, a functor, a lambda, anything. – juanchopanza Sep 24 '14 at 07:30
  • @juanchopanza Right - I think back then when I implemented the above approach the idea was simply to encapsulate the factory within the class (essentially, "the class should know how to create itself")... – Andreas Fester Sep 24 '14 at 07:47