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:

  1. It derives from plugin::Plugin<Shape> — itself, as the template argument (the Curiously Recurring Template Pattern). That injects the static members create, load and catalog bound to Shape.
  2. 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:

TargetIncludes Shape.hhLinks
the host executableyesdsold, plugin, Shape.cpp
each plugin (MODULE lib)yesplugin (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.