C++库和自注册类:客户端应用程序中的工厂映射为空

C++ Library & Self registering classes: Factory map empty in client application

本文关键字:工厂 映射 应用程序 客户端 注册 C++      更新时间:2023-10-16

让我们有一个C++库(我们称之为lib(,它作为静态库包含在应用程序中(我们称之为app(。在lib中有一个基类node。节点的每个子类都由一个 UUID 标识。 我采用自注册模式来确保新类在工厂自行注册。工厂允许基于提供的UUID构建node子类对象。app通过libfactory::build()函数构建对象。

我的工厂基于这篇精彩博客文章的代码。

我已经调整了这段代码,使其使用 UUID(使用boost.uuid(而不是字符串,因为所有正在创建的类都需要它们分配的 UUID(出于外部依赖原因(。

我遇到的问题是,如果我不"手动"创建每个node_template子类的对象实例(即AB(,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.标头创建每个类的虚拟对象。这当然不是一件好事,也违背了自我注册类的全部目的。

我在这里错过了什么?

从博客文章中,您缺少静态成员registered(也称为"// The really fun part"(。在基类中拥有并实例化这样的静态变量会强制在所有派生类中实例化它,这会将该类注册为副作用。

编辑:博客文章中还有另一段非常小但非常重要的代码:

Registrar() : Base(Key{}) { (void)registered; }

这将确保使用registered。因为static变量仅在第一次使用时实例化,否则不会调用该函数。

在您的情况下,将以下内容添加到node_template应该有效:

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)
{
(void) registered;
}
static bool do_register() {
derived d; // I am not sure if one should in some way force this to not be optimized away.
return true;
}
inline static bool registered = do_register();
};