A plugin is a class deriving from the host's base, plus one registration macro in its .cpp. This page covers every macro and when to use which.

REGISTER — default construction

#include <plugin/PluginRegister.hh>

class Square : public Shape { /* ... default-constructible ... */ };

REGISTER( Square, Shape );

REGISTER(Class, Base) registers Class under the key "Class" (its identifier, stringised). The catalog stores a factory that calls new Class(). Shape::create("Square") returns std::unique_ptr<Shape>( new Square ).

Use a different key with REGISTERBYNAME:

REGISTERBYNAME( rounded_square, Square, Shape );   // key "rounded_square"

REGISTERWITHCONFIG — typed construction

When a plugin needs constructor arguments, give it a Config struct and a constructor taking it, then register with both:

class Circle : public Shape
{
public:
    struct Config { unsigned int r = 0; };

    Circle() : r_( 0 ) {}
    explicit Circle( Config const& c ) : r_( c.r ) {}
private:
    unsigned int r_;
};

REGISTER_WITH_CONFIG( Circle, Shape, Circle::Config );

This registers two factories under "Circle":

  • the default one — Shape::create("Circle") still works, calls Circle();
  • a config one — Shape::create("Circle", Circle::Config{5}) calls Circle( Config{5} ).

The config travels as std::any. At creation the catalog does std::any_cast<Config const&> — a type mismatch throws std::bad_any_cast, and passing a config to a plugin registered with plain REGISTER throws std::runtime_error ("registered without config support").

REGISTERBYNAME_WITH_CONFIG( key, Class, Base, Config ) is the custom-key form.

Aliases — REGISTERAS, REGISTERBYNICK

Register the same class under an additional key, marked as an alias:

REGISTER( Ellipse, Shape );                 // "Ellipse"
REGISTERAS( oval, Ellipse, Shape );         // "oval"  -> same class, alias
REGISTERBYNICK( "◯", Ellipse, Shape );      // "◯"     -> alias, string-literal key

An alias behaves identically for create(). The difference shows up in catalog().names(): by default it omits aliases; names(true) includes them. Use aliases for backward-compatible renames or short forms without duplicating the class.

Lifetime of a registration

Every macro expands to a file-scope object in an anonymous namespace:

namespace {
    const volatile plugin::PluginRegisterer<Square, Shape>
        REGISTERER_Square_42( "Square", false );
}
  • Constructor — runs during shared-object load (static initialisation of the DSO). It calls Base::catalog().insert(key, ...).
  • Destructor — runs during shared-object unload. It calls Base::catalog().erase(key).

So a plugin's keys are present exactly while its DSO is loaded. plugin keeps DSOs loaded for the process lifetime (see Loading Shared Objects), so in practice registrations last until exit — but the erase-on-unload behaviour is what makes the catalog correct if you ever dlclose one yourself.

const volatile and the __LINE__-suffixed name keep the compiler from eliding the object or colliding two registrations in one file.

What create() returns

Situationcreate(key)create(key, config)
key registered, default factoryunique_ptr<Base>
key registered with configunique_ptr<Base> (default ctor)unique_ptr<Base>
key registered, plain REGISTER, config passedthrows std::runtime_error
config type ≠ registered typethrows std::bad_any_cast
key not registerednullptrnullptr

An unknown key is not an exception — check the pointer. A DSO that failed to load is the usual reason a key is missing; see the next page.

Next: Loading Shared Objects.