I am trying to write an application that is loading its extensions dynamically during runtime. I used Boost Preprocessor library to write a preprocessor function that, given a list of names, declares a class for each name (and make all of them subclasses of some AbstractPlugin class) and then declares a Boost MPL sequence containing that classes. Then I wrote a class that tries a pointer to AbstractPlugin if it could be cast to any of the types in that MPL sequence. The problem here is that my preprocessor function needs a full list of all extensions I want to create and load. Is there some technique that lets me register each extension in a separate file?
Update:
I believe, my explanation of situation was too vague, so I decided to make it more specific.
I would like to define a collection of extension types. For each extension type there could be any number of extensions. During runtime the program loads external library, resolve the entry point function, call it and, as a result, get a pointer. Then it tries to cast that pointer to all registered extension types (using dynamic_cast
, so classes for extension types all inherit from some polymorphic base class). If a cast to some extension type succeeds, the casted pointer is used in a call to special handler for that extension type.
The number of extension types is known at compile time (while, obviously, the number of extensions is infinite). Using my aproach the loader class uses this knowledge to check whether there exists a handler for each extension type (if not, the program doesn't compile). Also, my aproach doesn't force classes for extension types know anything about the loader (so it is easy to modify the loader). But it would be more convenient if each extension type registered itself.
You can make all your classes self-registering in some sort of collection. Here's a skeleton approach:
Base.hpp:
#include <memory>
#include <unordered_map>
#include <string>
struct Base
{
virtual ~Base() = default;
using create_f = std::unique_ptr<Base>();
static void registrate(std::string const & name, create_f * fp)
{
registry()[name] = fp;
}
static std::unique_ptr<Base> instantiate(std::string const & name)
{
auto it = registry().find(name);
return it == registry().end() ? nullptr : (it->second)();
}
template <typename D>
struct Registrar
{
explicit Registrar(std::string const & name)
{
Base::registrate(name, &D::create);
}
// make non-copyable, etc.
};
private:
static std::unordered_map<std::string, create_f *> & registry();
};
Base.cpp:
#include "Base.hpp"
std::unordered_map<std::string, Base::create_f *> & Base::registry()
{
static std::unordered_map<std::string, Base::create_f *> impl;
return impl;
}
Now to use this in a client:
Derived.hpp:
#include "Base.hpp"
struct Derived : Base
{
static std::unique_ptr<Base> create() { return std::make_unique<Derived>(); }
// ...
};
Derived.cpp:
#include "Derived.hpp"
namespace
{
Base::Registrar<Derived> registrar("MyClass");
}
The constructor of the Base::Registrar<Derived>
takes care of registering the class Derived
under the name "MyClass"
. You can create instances of Derived
dynamically via:
std::unique_ptr<Base> p = Base::instantiate("MyClass");
The code could/should be improved by detecting repeat registrations, printing a list of available classes, etc. Note how we avoid any static initialization ordering problems my making the actual registry map object a block-static object, which is guaranteed to be initialized before its first use, and thus destroyed only after its last use.