Динамически соотнести тип данных с произвольной текстовой меткой

Я хочу добиться того, чтобы я мог каким-либо образом процедурно создавать объекты, чьи типы зависят от соответственной текстовой метки. Это нужно, чтобы автоматически создавать множество однотипных объектов по поступившим извне данным. Лучшее, что я смог придумать - это конструкция типа MyType* A = (A*)Factory.CreateObject("my_type_text_classname"), которая требует явного приведения типа возвращаемого указателя, посколько соответствия хранятся в std::map, и все лежащие там значения должны быть однотипны. Я хочу избавиться от явного приведения типов.


Ответы (1 шт):

Автор решения: HolyBlackCat

Непонятно зачем кастовать. Фабрика должна возвращать указатели на общего родителя (желательно умные указатели), и вы тоже должны хранить указатель на родителя (потому что если бы знали точный тип потомка, не пользовались бы фабрикой). Примерно так:

#include <iostream>
#include <memory>
#include <stdexcept>
#include <string_view>

struct Base
{
    virtual ~Base() = default;

    virtual void blah() = 0;
};

struct A : Base
{
    void blah() override
    {
        std::cout << "I'm A!\n";
    }
};

struct B : Base
{
    void blah() override
    {
        std::cout << "I'm B!\n";
    }
};

std::unique_ptr<Base> MakeObject(std::string_view name)
{
    if (name == "a") return std::make_unique<A>();
    if (name == "b") return std::make_unique<B>();
    throw std::runtime_error("Unknown class name.");
}

int main()
{
    std::unique_ptr<Base> x = MakeObject("a");
    x->blah(); // I'm A!
}

А вот так я бы сделал в продакшене:

#include <algorithm>
#include <cstddef>
#include <functional>
#include <iostream>
#include <map>
#include <memory>
#include <type_traits>
#include <stdexcept>
#include <string>
#include <string_view>

struct BaseLow
{
    virtual ~BaseLow() = default;

    virtual void blah() = 0;
};

using ClassMap = std::map<std::string, std::unique_ptr<BaseLow> (*)(), std::less<>>;

ClassMap &GetClassMap()
{
    static ClassMap ret;
    return ret;
}

std::unique_ptr<BaseLow> MakeObject(std::string_view name)
{
    auto &map = GetClassMap();
    auto it = map.find(name);
    if (it == map.end())
        throw std::runtime_error("Unknown class name.");
    return it->second();
}

template <std::size_t N>
struct ConstString
{
    char value[N]{};

    constexpr ConstString() {}
    constexpr ConstString(const char (&str)[N])
    {
        std::copy_n(str, N, value);
    }

    [[nodiscard]] constexpr std::string_view view() const &
    {
        return std::string_view(value, value + N - 1);
    }

    [[nodiscard]] constexpr std::string_view view() const && = delete;
};

template <typename T, ConstString Name>
struct Base : BaseLow
{
  private:
    static std::nullptr_t Register()
    {
        bool ok = GetClassMap().try_emplace(std::string(Name.view()), []() -> std::unique_ptr<BaseLow> {return std::make_unique<T>();}).second;
        if (!ok)
            throw std::runtime_error("Duplicate class name.");
        return nullptr;
    }
    
    inline static const std::nullptr_t register_class = Register();
    static constexpr std::integral_constant<const std::nullptr_t *, &register_class> register_class_helper{};
};

struct A : Base<A, "a">
{
    void blah() override
    {
        std::cout << "I'm A!\n";
    }
};

struct B : Base<B, "b">
{
    void blah() override
    {
        std::cout << "I'm B!\n";
    }
};

int main()
{
    std::unique_ptr<BaseLow> x = MakeObject("a");
    x->blah(); // I'm A!
}

Или лучше с автоматическим определением имени типа, как описано здесь: C++ Get name of type in template.

→ Ссылка