9

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.

Brian Tompsett - 汤莱恩
  • 5,195
  • 62
  • 50
  • 120
grisumbras
  • 750
  • 5
  • 10

3 Answers3

12

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.

Kerrek SB
  • 428,875
  • 83
  • 813
  • 1,025
  • I considered something like this, but I am more interested in a compile-time aproach. I am not sure that there exists one, but still, the land of c++ templates is full of miracles. – grisumbras Jun 24 '12 at 10:41
  • There is no static one. You cannot make classes register them self, because each implementation file has it's own compilation unit, and each may be placed in different library. The power of this solution is that when you load a dynamic library, the extension class became automagically available. It's well known and very useful pattern. – Arpegius Jun 24 '12 at 12:13
  • This is a very nice implementation. The only thing I don't understand is the = 0 on the static create() function. As far as I know that's invalid C++ since static functions can never be virtual. How is that part supposed to work? – Justin G Jun 21 '15 at 00:47
  • @Shenjoku: It didn't make any sense at all! Deleted. Thanks for spotting. No idea what I was thinking. (Also updated to use unique pointers.) – Kerrek SB Jun 21 '15 at 01:43
  • why hide the map implementation with the pimpl idiom? – stack user Jun 28 '16 at 12:10
  • @stackuser: That isn't what's happening here. There's nothing hidden, there's just a single, global map instance that needs to be initialized in the correct order, which is achieved by making it a function-local static variable. – Kerrek SB Jun 28 '16 at 14:58
  • 3
    There is a subtle issue in this answer. The `registrar` defined in Derived.cpp is not guaranteed to be constructed by the point `Base::instantiate()` is called in main. It is only guaranteed to be constructed when the Derived.cpp translation unit is odr-used. – jcai Jul 18 '17 at 01:55
  • 1
    How is the `Derived` class registered if it is not explicitly included somewhere? I mean, it is not even build... What am I missing? – Controller Aug 30 '19 at 10:55
  • 2
    @Controller: the registration happens as an effect of global initialization. It is, however, unspecified whether this will actually work, since the initializer is not required to run until something from its translation unit is used, which isn't happening here. In practice, this code works because vendors know that users rely on it, but it's not required to work by the standard. Care needs to be taken when linking to ensure that the linker doesn't discard the TU. – Kerrek SB Sep 02 '19 at 22:06
0

It is not difficult to implement such an extension framework using the abstract factory pattern.

http://en.wikipedia.org/wiki/Abstract_factory_pattern

You can register those abstract factory functions/objects in a global list, and do whatever you want to do base on it.

lenx.wei
  • 76
  • 3
-1

As it turns out what I want is impossible. The reason for that is "register" in this context means "put a type inside type sequence" and type sequences are immutable because they are types themselves. So one should either create this type sequence manually, or as some people suggested move the "registration" into runtime.

grisumbras
  • 750
  • 5
  • 10