Вместо того, чтобы вручную создавать отображение из id в имя плагина и функцию фабрики вручную, вы можете использовать так называемую «саморегистрацию», и компилятор сделает большую часть работы за вас.
Фабрика статических плагинов
Во-первых, нам нужен класс фабрики, где отдельные плагины могут регистрироваться самостоятельно. Объявление может выглядеть примерно так:
class PluginFactory
{
public:
using PluginCreationFunctionT = PluginBaseClass(*)();
PluginFactory() = delete;
static bool Register(std::string_view plugin name,
PluginID id,
PluginCreationFunctionT creation_function);
static PluginBaseClass* Create(std::string_view name);
static PluginBaseClass* Create(PluginID id);
private:
static std::map<std::string_view, PluginCreationFunctionT> s_CreationFunctionByName;
static std::map<PluginID, PluginCreationFunctionT> s_CreationFunctionById;
};
Соответствующий исходный файл содержит
std::map<std::string_view, PluginFactory::PluginCreationFunctionT>
PluginFactory::s_CreationFunctionByName;
std::map<PluginID, PluginFactory::PluginCreationFunctionT>
PluginFactory::s_CreationFunctionById;
bool PluginFactory::Register(std::string_view const plugin name,
PluginId const id,
PluginCreationFunctionT const creation_function)
{
// assert that no two plugins accidentally try to register
// with the same name or id
assert(s_CreationFunctionByName.find(name) == s_CreationFunctionByName.end());
assert(s_CreationFunctionById.find(id) == s_CreationFunctionById.end());
s_CreateFunctionByName.insert(name, creation_function);
s_CreateFunctionById.insert(id, creation_function);
return true;
}
PluginBaseClass* PluginFactory::Create(std::string_view const name)
{
auto const it = s_CreationFunctionByName.find(name);
return it != s_CreationFunctionByName.end() ? it->second() : nullptr;
}
PluginBaseClass* PluginFactory::Create(std::string_view const id)
{
auto const it = s_CreationFunctionById.find(name);
return it != s_CreationFunctionById.end() ? it->second() : nullptr;
}
Обратите внимание, что Register
всегда возвращает true
- нам понадобится его вернутьзначение для использования функции Register
в качестве инициализатора глобальной переменной. Инициализация глобальной переменной приводит к тому, что компилятор генерирует код для вызова функции Register
во время запуска программы.
В вашей основной функции теперь вы можете получить экземпляр определенного плагина через
PluginBaseClass* thePlugin1 = PluginFactory::Create("PLUGIN1");
или через
PluginBaseClass* thePlugin1 = PluginFactory::Create(PluginID::Plugin1);
Изменение плагинов
Теперь необходимо изменить сами плагины, чтобы они регистрировались. Теоретически любая глобальная переменная может это сделать, но во избежание конфликтов имен между различными плагинами проще всего добавить статический член данных в каждый класс плагина, например,
class Plugin1 : public PluginBaseClass {
public:
...
private:
static bool s_IsRegistered;
};
, а затем добавить следующее в исходный файл. для плагина:
namespace {
PluginBaseClass* create_plugin1()
{
return new Plugin1{};
}
}
bool Plugin1::s_IsRegistered
= PluginFactory::Register("PLUGIN1", PluginID::Plugin1, create_plugin1);
Упрощение кода CMake
Теперь, когда компилятор генерирует отображение, вам больше не нужны определения препроцессора. Все, что теперь нужно сделать вашему CMake-коду, это добавить правильные каталоги и источники. Но это не обязательно должно быть частью основного файла CMake. Вместо этого вы можете поместить CMakeLists.txt в каждую из папок плагина, а затем включить их через add_subdirectory
или include
в основной файл CMakefile:
foreach(library ${plugins})
if(EXISTS ${CMAKE_CURRENT_LIST_DIR}/plugins/${library}/CMakeLists.txt)
message(STATUS "--> found ${library}"
include(${CMAKE_CURRENT_LIST_DIR}/plugins/${library}/CMakeLists.txt)
else()
message(FATAL "Unknown plugin ${library} requested!")
endif()
endforeach()
CMakeLists.txt
для плагина 1 вТогда папка plugins/plugin1
содержит только
include_directories(${CMAKE_CURRENT_LIST_DIR}/include)
file(GLOB sources_plugin1 "${CMAKE_CURRENT_LIST_DIR}/src/*.cpp" "${CMAKE_CURRENT_LIST_DIR}/src/*.f90")
list(APPEND sources ${sources_plugin1})
. В данном конкретном случае это может показаться незначительным улучшением, но наличие этих отдельных файлов CMakeLists.txt
теперь также позволяет условно добавлять зависимости.
Например, предположим, что Plugin2 - единственный плагин, который использует boost. С отдельным CMakeLists.txt
вы можете добавить все, что вам нужно, чтобы найти и использовать повышение к CMakeLists.txt
в Plugin2, не загрязняя основной CMakeLists.txt
файл.