Пусть будет библиотека C ++ (назовем ее lib
), которая будет включена в библиотеку как stati c (назовем ее app
). Внутри lib
есть базовый класс node
. Каждый подкласс узла идентифицируется UUID. Я использую шаблон саморегистрации, чтобы новые классы регистрировались на фабрике. Фабрика позволяет построить объект подкласса node
на основе предоставленного UUID. app
создает объекты с помощью функции lib
factory::build()
.
Моя фабрика основана на коде этого блестящего сообщения в блоге .
I Я адаптировал этот код для использования UUID (используя boost.uuid
) вместо строки, поскольку все создаваемые классы все равно нуждаются в UUID, назначенном ими (по причинам внешней зависимости).
Проблема, с которой я сталкиваюсь в том, что если я не «вручную» создаю экземпляр объекта каждого подкласса node_template
(то есть A
и B
), карта factory::m_generators
будет пустой. Это, конечно, связано с тем фактом, что различные node
-подклассы никогда не создавались и, следовательно, никогда не регистрировались.
Вот мой пример запускаемого минимума ( живая демонстрация в coliru ):
#include <iostream>
#include <unordered_map>
#include <functional>
#include <memory>
#include <boost/functional/hash.hpp>
#include <boost/uuid/uuid.hpp>
#include <boost/uuid/string_generator.hpp>
class node;
/**
* @brief The factory class to build @p node items.
*/
class factory
{
public:
using key_type = boost::uuids::uuid;
using key_hash = boost::hash<key_type>;
using generator = std::function<std::unique_ptr<node>()>;
template<typename Derived>
struct registrar
{
registrar(const key_type& key)
{
factory::instance().register_generator(key, [](){
return std::make_unique<Derived>();
});
}
registrar(const std::string& uuid_string)
{
try {
boost::uuids::string_generator gen;
registrar(gen(uuid_string));
} catch (...) {
;
}
}
};
static factory& instance() noexcept
{
static factory f;
return f;
}
bool register_generator(const key_type& key, generator&& generator)
{
auto [it, emplaced] = m_generators.try_emplace(key, std::move(generator));
return emplaced;
}
[[nodiscard]] std::unique_ptr<node> build(const key_type& key) const
{
if (const auto& it = m_generators.find(key); it not_eq m_generators.cend())
return it->second();
return nullptr;
}
[[nodiscard]] std::unique_ptr<node> build(const char* uuid_string) const noexcept
{
try {
boost::uuids::string_generator gen;
return build(gen(uuid_string));
} catch (...) {
return nullptr;
}
}
private:
std::unordered_map<key_type, generator, key_hash> m_generators;
factory() = default;
factory(const factory& other) = default;
factory(factory&& other) = default;
virtual ~factory() = default;
};
/**
* @brief The node base class.
*/
struct node
{
node(const std::string& uuid_string) :
m_uuid_string(uuid_string)
{
}
[[nodiscard]] const std::string& uuid_string() const noexcept {
return m_uuid_string;
}
private:
std::string m_uuid_string;
};
/**
* @brief A template for @p node subclasses.
*/
template <class derived>
struct node_template :
node,
factory::registrar<derived>
{
node_template(const std::string& uuid_string) :
node(uuid_string),
factory::registrar<derived>(uuid_string)
{
}
};
struct A : node_template<A> {
A() : node_template("63cb8eeb-b90b-46c7-aaa8-3a349fcba3c5") { }
};
struct B : node_template<B> {
B() : node_template("1f24abfc-936f-4524-ae3b-cc346335ecbb") { }
};
static void build_and_print(const std::string& uuid_string)
{
if (auto node = factory::instance().build(uuid_string.c_str()); node)
std::cout << "node.uuid_string() = " << node->uuid_string() << std::endl;
else
std::cout << "Cannot build node object: Unknown UUID." << std::endl;
}
int main(void)
{
////////////////////////////////////////////////////////////////////////////////////////////////////
/// PROBLEM: If I do not construct these objects, they never register themselves at the factory. ///
////////////////////////////////////////////////////////////////////////////////////////////////////
#if 1
A a;
B b;
#endif
// A
build_and_print("63cb8eeb-b90b-46c7-aaa8-3a349fcba3c5");
// B
build_and_print("1f24abfc-936f-4524-ae3b-cc346335ecbb");
// Unknown UUID
build_and_print("9b20cc29-c7ca-4796-acb2-6ca6b80fa934");
return 0;
}
Пока я держу экземпляры объектов в строках 136 и 137, я могу строить другие объекты через factory
. Но как только я удаляю (например, измените строку 135 на #if 0
), карта фабричных генераторов пуста.
Я думаю, что я понимаю проблему, поскольку в том, что классу никогда не удается зарегистрировать себя, поскольку никогда не бывает строящийся объект. Однако я не уверен, как решить проблему.
В настоящее время в lib
есть очень уродливый заголовочный файл, который включается app
. Заголовок создает фиктивный объект каждого класса. Это, конечно, совсем не красиво, но также побеждает саму цель самостоятельной регистрации класса.
Что мне здесь не хватает?