Библиотека C ++ и классы самостоятельной регистрации: карта-фабрика пуста в клиентском приложении - PullRequest
0 голосов
/ 17 января 2020

Пусть будет библиотека 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. Заголовок создает фиктивный объект каждого класса. Это, конечно, совсем не красиво, но также побеждает саму цель самостоятельной регистрации класса.

Что мне здесь не хватает?

1 Ответ

1 голос
/ 17 января 2020

Из сообщения в блоге вам не хватает участника c stati registered (также называемого "// The really fun part"). Наличие и создание такой переменной stati c в базовом классе приводит к ее созданию во всех производных классах, и это регистрирует класс как побочный эффект.

EDIT : есть еще один очень маленький, но очень важный фрагмент кода в сообщении в блоге:

    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();
};
...