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, callsCircle(); - a config one —
Shape::create("Circle", Circle::Config{5})callsCircle( 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
| Situation | create(key) | create(key, config) |
|---|---|---|
| key registered, default factory | unique_ptr<Base> | — |
| key registered with config | unique_ptr<Base> (default ctor) | unique_ptr<Base> |
key registered, plain REGISTER, config passed | — | throws std::runtime_error |
| config type ≠ registered type | — | throws std::bad_any_cast |
| key not registered | nullptr | nullptr |
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.

