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 通过 libfactory::build() 函数构建 objects。

我的工厂是基于this brilliant blog post的代码。

我已经修改此代码以使用 UUID(使用 boost.uuid)而不是字符串,因为所有正在创建的 classes 无论如何都需要它们分配的 UUID(出于外部依赖性原因) ).

我遇到的问题是,如果我不 "manually" 创建每个 node_template subclass 的 object 实例(即 AB),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();
};