A plugin hierarchy is one abstract base class plus every concrete type that derives from it and registers itself. The host owns the base; plugins — often in separate shared objects — provide the derived types.
The base class
#include <plugin/Plugin.hh>
class Shape : public plugin::Plugin<Shape>
{
public:
Shape() = default;
virtual void setSize( unsigned int ) = 0;
virtual unsigned int getSize() const = 0;
virtual void printOn( std::ostream& ) const = 0;
};
Two things make this a plugin base:
- It derives from
plugin::Plugin<Shape>— itself, as the template argument (the Curiously Recurring Template Pattern). That injects the static memberscreate,loadandcatalogbound toShape. - It is abstract. Nothing forces this, but a plugin base with no pure virtuals is just a class — the point is a stable interface the host calls through a
Shape*.
Plugin<Base>'s destructor is virtual, so deleting a derived object through std::unique_ptr<Shape> (what create() returns) is well-defined.
One catalog per Base
Plugin<Base>::catalog() returns a reference to the PluginCatalog<Base> for that base. It is declared but not defined by the template — you provide the definition with one macro, in one translation unit the host links:
// Shape.cpp
#include "Shape.hh"
#include <plugin/PluginCatalog.hh>
CREATECATALOG( Shape );
CREATECATALOG(Shape) expands to an explicit specialisation of Plugin<Shape>::catalog() that returns a function-local static PluginCatalog<Shape>. Because it is one specialisation in one .o, every REGISTER in every DSO and the host all resolve to the same catalog object.
Put CREATECATALOG in the host, not in a plugin. If two DSOs each defined it you would get an ODR violation, or — with RTLD_LOCAL — two catalogs and a host that cannot see plugin registrations.
Sharing the header
Shape.hh is compiled into three kinds of target:
| Target | Includes Shape.hh | Links |
|---|---|---|
| the host executable | yes | dsold, plugin, Shape.cpp |
each plugin (MODULE lib) | yes | plugin (header-only) |
| — | — | — |
Ship Shape.hh (and any config structs) as the host's public plugin SDK. A plugin author needs only that header and plugin/PluginRegister.hh.
A convenience: streaming
The Shape example adds a free operator<< next to the class so std::cout << *shape works. This is ordinary C++, not part of the plugin machinery, but it is a common pattern for a base whose whole job is printOn:
inline std::ostream& operator<<( std::ostream& os, Shape& s )
{
s.printOn( os );
return os;
}
Multiple hierarchies in one program
Each base has its own catalog, keyed by type. A program can have Shape, Codec and Transport hierarchies at once; a DSO can register into more than one. Plugin<Shape> and Plugin<Codec> share no state.
Next: Writing a Plugin.

