Динамически соотнести тип данных с произвольной текстовой меткой
Я хочу добиться того, чтобы я мог каким-либо образом процедурно создавать объекты, чьи типы зависят от соответственной текстовой метки. Это нужно, чтобы автоматически создавать множество однотипных объектов по поступившим извне данным. Лучшее, что я смог придумать - это конструкция типа
MyType* A = (A*)Factory.CreateObject("my_type_text_classname")
, которая требует явного приведения типа возвращаемого указателя, посколько соответствия хранятся в std::map, и все лежащие там значения должны быть однотипны. Я хочу избавиться от явного приведения типов.
Ответы (1 шт):
Непонятно зачем кастовать. Фабрика должна возвращать указатели на общего родителя (желательно умные указатели), и вы тоже должны хранить указатель на родителя (потому что если бы знали точный тип потомка, не пользовались бы фабрикой). Примерно так:
#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 *, ®ister_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.