Background
For a piece of software that I need to get extensible, I wanted to design a simple compile-time "plugin" system in C++.
My use case was that the program (actually a library) can accept several input formats at the same time according to a requested interface version. There is a factory that selects the right input parser (let's call that the plugin), but the idea was to find a mechanism that doesn't require to change a common file registering all the known plugins, but rather that each files can register itself by the simple fact of being compiled-in.
Here, plugins are just cpp files, as in the project it was not an option to compile a dynamic library and load the plugins from them. That's why it's a "compile-time plugin", though the word "plugin" may not make much sense in this case...
I refered to this SO question: Compile-time plugin / Automated Factory Registration with separate modules constraint
The plugin Mechanism
plugins.h
#include <list>
#include <string>
#include <map>
namespace PluginSystem {
/* Base class for plugins */
class IPlugin {
public:
virtual void DoSomething() = 0;
};
/*
* Base class for PluginRegistrar
* See PluginRegistrar below for explanations
*/
class IPluginRegistrar {
public:
virtual IPlugin* GetPlugin() = 0;
};
/*
* This is the factory, the common interface to "plugins".
* Plugins registers themselves here and the factory can serve them on
* demand.
* It is a Singleton
*/
class PluginFactory {
public:
/* Get Singleton instance */
static PluginFactory& Instance();
/* Register a new plugin */
void Register(IPluginRegistrar * registrar, std::string name);
/* Get an instance of a plugin based on its name */
/* throws out_of_range if plugin not found */
IPlugin* GetPlugin(std::string name);
private:
/* Holds pointers to plugin registrars */
std::map<std::string, IPluginRegistrar*> registry_;
/* Make constructors private and forbid cloning */
PluginFactory(): registry_() {};
PluginFactory(PluginFactory const&) {};
void operator=(PluginFactory const&) {};
};
/*
* Helper class that registers a plugin upon construction.
* Actually, the registrar registers itself, and the proxied plugin is only
* created on-demand. This mechanism can be shortened by directly
* registering and instance of the plugin, but the assumption here is that
* instanciating the plugin can be heavy and not necessary.
*/
template<class TPlugin>
class PluginRegistrar: public IPluginRegistrar {
public:
PluginRegistrar(std::string classname);
IPlugin* GetPlugin();
private:
/* That is not really used there, but could be useful */
std::string classname_;
};
/* template functions in header */
template<class TPlugin>
PluginRegistrar<TPlugin>::PluginRegistrar(std::string classname): classname_(classname) {
PluginFactory &factory = PluginFactory::Instance();
factory.Register(this, classname);
}
template<class TPlugin>
IPlugin*
PluginRegistrar<TPlugin>::GetPlugin() {
return new TPlugin();
}
}
/*
* Here is the trick: upon creation of the global variable, the class created
* out of the template will get instanciated once, and will register itself.
* The template contains the information to create a plugin instance.
* An unnamed namespace is used to enclose this later unused variable in the
* compilation unit.
*/
#define REGISTER_PLUGIN(CLASSNAME) \
namespace { \
static PluginSystem::PluginRegistrar<CLASSNAME> \
CLASSNAME##_registrar( #CLASSNAME ); \
};
plugins.cpp
#include "plugins.h"
namespace PluginSystem {
PluginFactory&
PluginFactory::Instance() {
static PluginFactory instance;
return instance;
}
void
PluginFactory::Register(IPluginRegistrar * registrar, std::string name) {
registry_[name] = registrar;
}
IPlugin*
PluginFactory::GetPlugin(std::string name) {
IPluginRegistrar* registrar;
registrar = registry_[name]; /* throws out_of_range if plugin unknown */
return registrar->GetPlugin();
}
}
How to use
plugin1.cpp
/* Could be also split in .h/.cpp, here kept in one file for simplicity */
#include <iostream>
#include "plugins.h"
class Plugin1: public PluginSystem::IPlugin {
void DoSomething() {
std::cout << "Plugin1" << std::endl;
}
};
REGISTER_PLUGIN(Plugin1)
main.cpp
#include <iostream>
#include "plugins.h"
int main()
{
PluginSystem::PluginFactory &factory = PluginSystem::PluginFactory::Instance();
PluginSystem::IPlugin* plugin;
plugin = factory.GetPlugin("Plugin1"); /* should try-catch that */
plugin->DoSomething();
return 0;
}
Questions
Actually, it's been a (very) long time I did not code C++, so any advice, suggestion, correction is very welcomed.
I had to conform MSVC 2008 (eek...), which maybe explains some design choices, but I'm also interested in how to code better with newer standards and better supporting compilers.
I hope to post back in SO the solution for others to find, as the single answer is hard to decrypt for a non-guru. But not before getting your precious inputs, of course!