Обнаружение производного класса во время компиляции - PullRequest
0 голосов
/ 16 сентября 2018

У меня есть базовый класс, из которого получено большое количество моих классов, например:

class BaseSystem
{
public:
   virtual void doThing() = 0;
}

Я хочу иметь возможность как-то пометить все производные типы класса, чтобы я мог найти их, когдаприложение запускается.В c # я бы сделал это путем отражения либо поиска атрибута, либо просто поиска чего-либо, производного от базового класса.

Есть ли аналогичный способ, которым я могу сделать это в c ++, где я могу создавать классы с чем-то и иметьих обнаружили во время компиляции и создали ли для них экземпляр в векторе?

Редактировать: Больше контекста проблемы, которую я пытаюсь решить.

Я создаю статическую библиотеку, которая позволяет программисту реализовывать шаблон Entity Component Systems для формирования базовых возможностейигровой движок.Идея состоит в том, что у библиотеки есть базовый класс, из которого они могут реализовывать системы, и системный менеджер сможет их обнаружить и запустить после запуска игры.

1 Ответ

0 голосов
/ 16 сентября 2018

Как отмечали другие в комментариях к вашему вопросу, в общем случае невозможно обнаружить все существующие производные классы определенного базового класса во время компиляции с C ++.

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

Основная идея

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

Такой реестр может выглядеть так:

class derived_registry
{
public:
    static std::size_t number_of_instances()
    {
        return _instances.size();
    }

    static base* instance(std::size_t const index)
    {
        assert(index < _instances.size());
        return _instances[index].get();
    }

    template <typename T, std::enable_if_t<std::is_base_of_v<base, T>, int> = 0>
    static std::size_t register_derived_class(std::unique_ptr<T> instance)
    {
        auto const index = _instances.size();
        _instances.emplace_back(std::move(instance));
        return index;
    }

private:
    static std::vector<std::unique_ptr<base>> _instances;
};

inline std::vector<std::unique_ptr<base>> derived_registry::_instances;

Любой производный класс base теперь должен зарегистрироваться, вызвав derived_registry::register_derived_class для инициализации статической переменной-члена, например, вот так:

// in derived1.h
class derived1 : public base 
{
public:
    derived1();

    void do_something() override;

private:
   static std::size_t _index;
};

// in derived1.cpp
std::size_t derived1::_index = derived_registry::register_derived_class(std::make_unique<derived1>());

derived1::derived1()
    : base{} 
{
}

void derived1::do_something()
{
    std::cout << "derived 1\n";
}

Это также показывает, почему важно определить вектор derived_registry::_instances как переменную inline: мы должны быть уверены, что derived_registry::register_derived_class вызывается только после того, как derived_registry::_instances уже инициализирован. Самый простой способ сделать это - использовать тот факт, что при наличии более одной переменной со статической продолжительностью хранения, определенной в одной единице перевода, они гарантированно будут инициализированы в том порядке, в котором они были определены. Поскольку мы определили derived_registry::_instances в заголовочном файле, мы гарантируем, что derived_registry::_instances инициализируется до derived1::_index, и, следовательно, до вызова derived_registry::register_derived_class.

Вы можете увидеть этот подход в действии на wandbox .

делает его надежным

Хотя приведенная выше реализация работает, она довольно громоздка, и все же есть вероятность, что кто-то добавит новый производный класс, но забудет его зарегистрировать.

Чтобы упростить регистрацию, вы можете использовать шаблон CRTP, как описано в ответе на этот вопрос , который StoryTeller связан в комментарии к вашему вопросу.

Хотя это может значительно упростить регистрацию, реестр по-прежнему работает корректно только тогда, когда каждый производный класс либо наследует базу CRTP, либо реализует саму регистрацию, которую легко забыть. Чтобы гарантировать, что нет другого выбора, кроме как наследовать от базового класса CRTP, вы можете дополнительно сделать конструктор base закрытым и сделать базовый класс CRTP единственным другом. Тогда нельзя случайно наследовать напрямую от base без регистрации нового производного класса.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...