Способ фильтрации объектов по типу на фабрике

У меня есть фабрика, содержащая карту с именами объектов в качестве ключа и указателя для функции значения.

template<class T>
static T* makeObj()
{
    return new T();
}

using createFunction = std::function<void*()>;
map<std::string, createFunction> types;

template<class T>
static void add(const std::string& name)
{
    types.insert(name, makeObj<T>);
}

Я хочу добавить дополнительную функцию для получения отфильтрованного списка типов (получить список унаследованных классов), примерно так:

template<class baseType>
static std::list<std::string> getFilteredListOfTypes()
{
}

Вопрос в том, как это сделать. Первая идея — создать карту со структурой в качестве значения и сохранить там объект соответствующего типа, я могу использовать Dynamic_cast и проверить все типы, но сомневаюсь, что создание объекта — хорошая идея. Другой способ — использовать std::is_base_of:

template< class Base, class Derived >
struct is_base_of;

is_base_of принимает два типа: класс Base я могу получить из моего getFilteredListOfTypes, но как получить производный класс? Создайте объект и используйте decltype... но здесь снова создается объект.

Есть ли способ создать такую ​​функцию фильтра без создания объектов? Создавать здесь объект — плохая идея? Разве это плохая идея — создавать объект для каждого элемента карты? Меня немного пугает ситуация, когда на заводе будут сотни совсем не маленьких типов.

УПД AppFactory.h:

class AppFactory
{
public:
    template<class T>
    static T* makeObj()
    {
        return new T();
    }

    using createFunction = std::function<void*()>;
    using registerMap = tsl::robin_map<std::string, createFunction>;

    static registerMap& get();

    template<class T>
    static std::unique_ptr<T> createUnique(const std::string& name)
    {
        return std::unique_ptr<T>(create<T>(name));
    }

    template<class T>
    static std::shared_ptr<T> createShared(const std::string& name)
    {
        return std::shared_ptr<T>(create<T>(name));
    }

    template<class T>
    static T* create(const std::string& name)
    {
        if (get().contains(name))
        {
            return static_cast<T*>(get()[name]());
        }
        return nullptr;
    }

    template<class T>
    static bool add(const std::string& name)
    {
        auto resultPair = get().insert_or_assign(name, makeObj<T>);
        return resultPair.second;
    }
};

#define APPFACTORY_ADD(classname) \
namespace { static bool addName = AppFactory::add<classname>(#classname); }

AppFactory.cpp:

AppFactory::registerMap& AppFactory::get()
{
    static AppFactory::registerMap map;
    return map;
}

Использование фабрики:

...
APPFACTORY_ADD(SomeClass);
...
AppFactory::createUnique<SomeBaseClass>("SomeClass")
...

УПД 2 тестовая реализация getFilteredListOfTypes

struct typedata
{
    createFunction func;
    Object* obj;
};
using registerMap = tsl::robin_map<std::string, typedata>;
...
template<class T>
static bool add(const std::string& name)
{
    typedata data;
    data.func = makeObj<T>;
    data.obj = new T();
    auto resultPair = get().insert_or_assign(name, data);
    return resultPair.second;
}
...
template<class BaseType>
static std::list<std::string> getFilteredListOfTypes()
{
    std::list<std::string> typeList;
    for(auto it = get().begin(); it != get().end(); ++it)
    {
        if (dynamic_cast<BaseType*>(get()[it->first].obj))
        {
            typeList.push_back(it->first);
        }
    }
    return typeList;
}

Использование getFilteredListOfTypes

class A : public Object
...
class B : public A
...
class C : public B
...
class D : public C
...
class E : public B
...
lst = AppFactory::getFilteredListOfTypes<A>(); // lst -> {"A", "B", "C", "D", "E"}
lst = AppFactory::getFilteredListOfTypes<B>(); // lst -> {"B", "C", "D", "E"}
lst = AppFactory::getFilteredListOfTypes<C>(); // lst -> {"C", "D"}
lst = AppFactory::getFilteredListOfTypes<D>(); // lst -> {"D"}

Вы отказались от информации о типе, когда решили использовать void*. Теперь только объект времени выполнения знает свой собственный тип, и любое предоставленное решение на этом этапе будет в основном реализовывать отражение времени выполнения.

Yksisarvinen 23.06.2024 18:06

Основная идея — фильтровать список типов без объектов. Я хотел бы использовать is_base_of, но у меня есть один из необходимых типов для этой функции внутри функции «add» и другой необходимый тип внутри функции «getFilteredListOfTypes», и я не знаю, что я могу здесь сделать. Если я буду использовать созданные объекты для функции фильтра, то, конечно, я буду использовать динамический_cast

Andrii 23.06.2024 19:10

«Создать объект и использовать decltype» Это void *, ничего толкового.

Botje 23.06.2024 21:55

«makeObj» и «void*» будут использоваться клиентом. Я думаю, что могу сделать что-то вроде этого: template<class T> static void add(const std::string& name) { typeData data; data.createFunc = makeObj<T>; data.defaultObj = Т(); типы.вставка(имя, данные); }

Andrii 23.06.2024 23:29

Ваша фабрика ничего не знает о типах. Все, что у него есть, это map<string, void(*)(). Отсюда невозможно восстановить какую-либо информацию о типе.

n. m. could be an AI 24.06.2024 08:59

Не совсем понятно, чего вы хотите добиться. Ваша строка types.insert(name, makeObj<T>); даже не компилируется. std::is_base_of — это метапрограммирование шаблонов, оно работает только во время компиляции.

Alex Veleshko 24.06.2024 09:42

Добавьте, пожалуйста, пример использования на вашем заводе.

Alex Veleshko 24.06.2024 09:46
data.defaultObj = T(); Еще раз что за тип data.defaultObj?
n. m. could be an AI 24.06.2024 11:46

Я добавил весь код фабрики и его использование, чтобы прояснить вопрос.

Andrii 24.06.2024 13:45

Можете ли вы показать, как бы вы реализовали эту функцию, если бы вас не беспокоила стоимость создания объекта? decltype(T{}) не требует каких-либо затрат времени выполнения конструктора T, поэтому я до сих пор не понимаю, что вы имеете в виду.

Weijun Zhou 24.06.2024 13:53

Другими словами, кажется, что вы формулируете свой вопрос как оптимизацию существующей реализации (которую вы не показали), в то время как (другие и) я подозреваю, что здесь есть какая-то более фундаментальная проблема.

Weijun Zhou 24.06.2024 14:05

@WeijunZhou Дело не в оптимизации. Вопрос в том, как получить список всех классов, производных от некоторого класса X, который можно создать с помощью карты.

Yksisarvinen 24.06.2024 14:17

@Yksisarvinen Хорошо, наверное, я не совсем ясно выразился. На самом деле я имею в виду, что он спрашивает, как сделать что-то с некоторым ограничением, хотя неясно, как это можно сделать даже без ограничения, при этом ограничение «без создания объекта».

Weijun Zhou 24.06.2024 14:21

Судя по вашему редактированию, это работает только в том случае, если тип является производным от Object, поэтому вы также можете объявить прототип функции возвращающим Object* вместо void*. Это важная дополнительная информация, потому что, как правило, с void* мало что можно сделать, но Object* — это другое.

Weijun Zhou 24.06.2024 15:06

Да, я изменил void* на Object* для этой тестовой реализации, насколько я понимаю, в любом случае void* нужно будет изменить.

Andrii 24.06.2024 15:13

На самом деле я ищу какой-нибудь хитрый способ использования is_base_of (я на 90% уверен, что это невозможно, но продолжайте искать). Какой-то способ «хранить» информацию о типе внутри функции «add» и использовать эту информацию внутри «getFilteredListOfTypes», тогда для is_base_of будет два типа.

Andrii 24.06.2024 15:23
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
2
16
165
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

В этом ответе предлагается написать собственную крошечную структуру «отражения во время выполнения». Будем надеяться, что однажды C++ будет иметь правильное отражение во время компиляции (или времени выполнения), и этот ответ устареет. До тех пор...

TL;DR: Живая демоверсия (обозреватель компилятора)

Базовая структура отражения

Нашей целью будет создание экземпляра нешаблонного класса TypeInfo для каждого типа возвращаемого значения фабрики. У этого типа есть функция-член bool isBaseOf(TypeInfo const& other), которую можно использовать во время выполнения и вести себя как std::is_base_of.

#include <span>

struct TypeTag{};
using TypeId =  TypeTag const*;

// This is NOT a templated class.
class TypeInfo {
public:
    using DirectBases = std::span<TypeInfo const* const>;

    explicit constexpr TypeInfo(TypeId typeId, DirectBases directBases)
        : typeId_{ typeId }
        , directBases_{ directBases }
    {}

    constexpr bool isBaseOf(TypeInfo const& child) const {
        if (*this == child) {
            return true;
        }
        for (TypeInfo const* directBase : child.directBases_) {
            if (isBaseOf(*directBase)) {
                return true;
            }
        }
        return false;
    }

    constexpr bool operator==(TypeInfo const& other) const {
        return typeId_ == other.typeId_;
    }
protected:
    TypeId typeId_;
    DirectBases directBases_;
};

И карта времени компиляции typename T -> TypeInfo{...}, действующая как база данных информации об отражениях:

template<typename T>
struct TypeInfoOf;

template<typename T>
constexpr TypeInfo const& typeInfoOf() {
    return TypeInfoOf<T>::value;
}

При этом getFilteredListOfTypes можно реализовать как:

#include <list>
#include <string>
#include <unordered_map>

// NOTE (digression): I see a singleton anti-pattern here...
class AppFactory {
public:
    struct RegisterMapValue {
        // CreateFunction func;
        TypeInfo const* typeInfo;
    };

    using RegisterMap = std::unordered_map<std::string, RegisterMapValue>;

    static RegisterMap& get() {
        static RegisterMap map;
        return map;
    }

    template<class T>
    static void add(const std::string& name) {
        get()[name] = { /*func,*/ &typeInfoOf<T>() };
    }

    static std::list<std::string> getFilteredListOfTypes(TypeInfo const& base) {
        std::list<std::string> typeList;
        for(auto const& mapPair : get()) {
            if (base.isBaseOf(*mapPair.second.typeInfo)) {
                typeList.push_back(mapPair.first);
            }
        }
        return typeList;
    }

    template<class BaseType>
    static std::list<std::string> getFilteredListOfTypes() {
        return getFilteredListOfTypes(typeInfoOf<BaseType>());
    }
};

Теперь, чтобы создать эти объекты TypeId, нам нужно сгенерировать две вещи.

  • Уникальный TypeId объект для каждого типа.
  • Массив/вектор TypeInfo*, содержащий прямые основания объекта.

Генерация уникального TypeId

template<typename T>
struct UniqueId {
    static constexpr auto tag = TypeTag{};
};

template<typename T>
constexpr TypeId uniqueTypeIdOf() {
    return &UniqueId<T>::tag;
}

static_assert(uniqueTypeIdOf<std::string>() == uniqueTypeIdOf<std::string>());
static_assert(uniqueTypeIdOf<unsigned>() != uniqueTypeIdOf<std::string>());

Генерация базового массива

К сожалению, мы не можем автоматически получить список прямых баз типа. Мы просто создадим утилиту, которая принимает базовые типы в качестве входных данных, превращает их в объект std::array<TypeInfo*> и создает объект TypeInfo:

#include <array>
#include <type_traits>

template<typename T, typename... DirectBases>
struct TypeInfoMaker {
private:
    static_assert((std::is_base_of_v<DirectBases,T> && ...));
    static_assert((!std::is_same_v<T,DirectBases> && ...));

    using BasesArray = std::array<TypeInfo const*, sizeof...(DirectBases)>;

    static constexpr auto directBases = BasesArray{ {&typeInfoOf<DirectBases>()...} };
public:
    static constexpr auto value = TypeInfo{ uniqueTypeIdOf<T>(), directBases };
};

Заполнение карты TypeInfoOf

Именно здесь проявляется главный недостаток этого ответа: список базового класса дублируется между объявлением класса и конструкцией TypeInfo. Оба должны быть обновлены вместе вручную (если только не используются макросы, но это будет некрасиво).

2 static_assert в TypeInfoMaker обеспечивают некоторую защиту от ошибок: TypeInfoMaker выдаст ошибку времени компиляции, если база неверна (но будет принимать косвенные базы), и должна предотвращать циклические ссылки (TypeInfo::isBaseOf завершится). Мы мало что можем сделать против пропавших баз.

// User-provided class (unchanged)
class A {
public:
    virtual ~A() = default;
};

// The user has to declare A's reflection data as follows:
template<>
struct TypeInfoOf<A> : TypeInfoMaker<A> {};

class B : public A {};

template<>
struct TypeInfoOf<B> : TypeInfoMaker<B, A> {};

class C : public B {};

template<>
struct TypeInfoOf<C> : TypeInfoMaker<C, B> {};

class D : public C {};

template<>
struct TypeInfoOf<D> : TypeInfoMaker<D, C> {};

class E : public B {};

template<>
struct TypeInfoOf<E> : TypeInfoMaker<E, B> {};

С другой стороны, для заполнения карты TypeInfoOf требуется всего пара строк каждого типа.

Демо/использование

Объедините все предыдущие фрагменты кода и добавьте следующее:

#include <iostream>
#include <string_view>

template<typename T>
void printFilteredList(std::string_view typeName) {
    std::cout << "filteredListOfTypes<" << typeName << ">: ";
    for (auto const& value : AppFactory::getFilteredListOfTypes<T>()) {
        std::cout << value << ' ';
    }
    std::cout << '\n';
}

int main() {
    AppFactory::add<A>("A");
    AppFactory::add<B>("B");
    AppFactory::add<C>("C");
    AppFactory::add<D>("D");
    AppFactory::add<E>("E");

    printFilteredList<A>("A");
    printFilteredList<B>("B");
    printFilteredList<C>("C");
    printFilteredList<D>("D");
    printFilteredList<E>("E");
}

Живая демо-версия (проводник компилятора)

Другие вопросы по теме