C++ 库和自注册 类:客户端应用程序中的工厂映射为空
C++ Library & Self registering classes: Factory map empty in client application
假设有一个 C++ 库(我们称之为 lib
),它作为静态库包含在应用程序中(我们称之为 app
)。在 lib
中有一个基础 class node
。节点的每个 subclass 由一个 UUID 标识。
我采用自注册模式来确保新 classes 在工厂注册自己。工厂允许根据提供的 UUID 构建 node
subclass object。 app
通过 lib
的 factory::build()
函数构建 objects。
我的工厂是基于this brilliant blog post的代码。
我已经修改此代码以使用 UUID(使用 boost.uuid
)而不是字符串,因为所有正在创建的 classes 无论如何都需要它们分配的 UUID(出于外部依赖性原因) ).
我遇到的问题是,如果我不 "manually" 创建每个 node_template
subclass 的 object 实例(即 A
和B
),factory::m_generators
地图是空的。
这当然是因为各种 node
-subclass 从未实例化,因此从未注册过。
这是我的最小可运行示例 (live demo at 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 行中保留 object 实例,我就可以通过 factory
进一步构建 object。但是一旦我删除了(例如,将第 135 行更改为 #if 0
),工厂生成器映射为空。
我认为我理解这个问题,因为 class 永远不会注册自己,因为从来没有构造 object。但是,我不确定如何解决这个问题。
目前 lib
中有一个非常难看的 header 文件被 app
包含。 header 为每个 class 创建一个虚拟 object。这当然一点也不好,而且有点违背了自我注册的全部目的 class.
我在这里错过了什么?
在博客 post 中,您缺少静态成员 registered
(也称为“// The really fun part
”)。在基础 class 中拥有并实例化这样一个静态变量会强制它在所有派生的 class 中实例化,这会将 class 注册为副作用。
编辑: 博客中还有一段非常小但非常重要的代码post:
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();
};
假设有一个 C++ 库(我们称之为 lib
),它作为静态库包含在应用程序中(我们称之为 app
)。在 lib
中有一个基础 class node
。节点的每个 subclass 由一个 UUID 标识。
我采用自注册模式来确保新 classes 在工厂注册自己。工厂允许根据提供的 UUID 构建 node
subclass object。 app
通过 lib
的 factory::build()
函数构建 objects。
我的工厂是基于this brilliant blog post的代码。
我已经修改此代码以使用 UUID(使用 boost.uuid
)而不是字符串,因为所有正在创建的 classes 无论如何都需要它们分配的 UUID(出于外部依赖性原因) ).
我遇到的问题是,如果我不 "manually" 创建每个 node_template
subclass 的 object 实例(即 A
和B
),factory::m_generators
地图是空的。
这当然是因为各种 node
-subclass 从未实例化,因此从未注册过。
这是我的最小可运行示例 (live demo at 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 行中保留 object 实例,我就可以通过 factory
进一步构建 object。但是一旦我删除了(例如,将第 135 行更改为 #if 0
),工厂生成器映射为空。
我认为我理解这个问题,因为 class 永远不会注册自己,因为从来没有构造 object。但是,我不确定如何解决这个问题。
目前 lib
中有一个非常难看的 header 文件被 app
包含。 header 为每个 class 创建一个虚拟 object。这当然一点也不好,而且有点违背了自我注册的全部目的 class.
我在这里错过了什么?
在博客 post 中,您缺少静态成员 registered
(也称为“// The really fun part
”)。在基础 class 中拥有并实例化这样一个静态变量会强制它在所有派生的 class 中实例化,这会将 class 注册为副作用。
编辑: 博客中还有一段非常小但非常重要的代码post:
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();
};