Использование типа шаблона для генерации уникальных имен членов для этого типа шаблона - PullRequest
0 голосов
/ 10 января 2019

Этот вариант использования исходит из желания реализовать структуру данных шины событий времени компиляции, которая только прослушивает / регистрирует / отменяет регистрацию специально для предоставленных аргументов шаблона.

Начиная с наивной реализации, скажем, у нас есть следующие классы AListener, AEvent, BListener, BEvent.

Я хочу, чтобы мой EventBus класс выглядел так:

class EventBus {
    std::vector<AListener*> aListeners;
    std::vector<BListener*> bListeners;

public:
    void registerListener(AListener& listener);
    void unregisterListener(AListener& listener);
    void sendEvent(AEvent event);

    void registerListener(BListener& listener);
    void unregisterListener(BListener& listener);
    void sendEvent(BEvent event);
};

Есть ли способ, которым я могу шаблонизировать его и рекурсивно построить класс? Например:

EventBus<AListener, AEvent, BListener, BEvent> eventBus;

AListener aListener;
eventBus.registerListener(aListener);

AEvent aEvent;
eventBus.sendEvent(aEvent);

BListener bListener;
eventBus.registerListener(bListener);

BEvent bEvent;
eventBus.sendEvent(bEvent);

Желательно было бы сделать новый vector для каждого типа слушателя, потому что было бы неэффективно помещать все указатели в один список, так как длинный список неактуальных слушателей будет пустой тратой производительности. Поскольку в шине событий будет проходить множество событий, важна производительность. Желательно перебирать только те, которые нам нужны.

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

Моя проблема:

Как мне обойти название? Хотя я предполагаю, что специализировать шаблоны в рекурсивном определении с помощью перегрузки методов это нормально, потому что, надеюсь, компилятор сделает идеальную вещь ... Я не уверен, как обрабатывать разные имена членов.

Мой план состоял в том, чтобы взять список и вытащить двух членов, примерно так ( ПРИМЕЧАНИЕ. Это псевдокод, который почти наверняка не компилируется, или, если это происходит, просто случайно ) :

// Pseudocodey C++ template rough idea
template <typename Listener, typename Event, typename Args...>
class EventBus : public EventBus<Args...> {
    // ???

public:
    void registerListener(Listener& listener) {
        // emplace back
    }

    void unregisterListener(Listener& listener) {
        // erase remove
    }

    void sendEvent(Event event) {
        // send for each
    }
};

Конечно, если нет лучшего способа? Это возможно?

Ответы [ 3 ]

0 голосов
/ 10 января 2019

В C ++ 11 вы можете использовать шаблон переменной

template<class... MoreEventPairs> class EventBus {};

template<class Listener, class Event>
class EventBus<Listener, Event>
{
    private:
        std::vector<Listener *> Listeners;
    public:

       EventBus() {};
       ~EventBus() {};

       void registerListener(Listener& listener) {};    // dummy implementations here
       void unregisterListener(Listener& listener) {};
       void sendEvent(Event event) {};
};

template<class Listener, class Event, class ... MoreEventPairs>
class EventBus<Listener, Event, MoreEventPairs ...> : public EventBus<Listener, Event>,
                                                      public EventBus<MoreEventPairs ...>
{
    public:

        //  these are needed so name resolution works
        //    one needed for each function, on both inheritance paths

        using EventBus<Listener, Event>::registerListener;
        using EventBus<Listener, Event>::unregisterListener;
        using EventBus<Listener, Event>::sendEvent;

        using EventBus<MoreEventPairs ...>::registerListener;
        using EventBus<MoreEventPairs ...>::unregisterListener;
        using EventBus<MoreEventPairs ...>::sendEvent;
};

//   construct as

EventBus<ListenerA, EventA, ListenerB, EventB> bus;

Это работает, по существу, очищая два типа одновременно из пакета параметров. Он не будет компилироваться, если вы предоставите нечетное количество типов при построении (например, пропустите тип Event). Вы можете специализировать обработку для определенного типа Listener или связанного Event, используя специализацию шаблона с двумя параметрами.

До C ++ 11 вы могли использовать множественное наследование, но нужно было бы создать класс EventBus отдельно. Это больше усилий для поддержки, из-за необходимости реплицировать код для расширения.

template<class Listener, class Event> class ListenerBus
{
   private:
      std::vector<Listener *> Listeners;
   public:

      ListenerBus() {};
      ~ListenerBus() {};

      void registerListener(Listener& listener) {};    // dummy implementations here
      void unregisterListener(Listener& listener) {};
      void sendEvent(Event event) {};
};

//  AListener, AEvent, etc are concrete classes
class EventBus : public ListenerBus<AListener, AEvent>,
                 public ListenerBus<BListener, BEvent>
                // list other types here
{
    public:

      using ListenerBus<AListener, AEvent>::registerListener;
      using ListenerBus<AListener, AEvent>::unregisterListener;
      using ListenerBus<AListener, AEvent>::sendEvent;

      using ListenerBus<BListener, BEvent>::registerListener;
      using ListenerBus<BListener, BEvent>::unregisterListener;
      using ListenerBus<BListener, BEvent>::sendEvent;

      // need to replicate above for every base class for name resolution
};

//   construct as

EventBus bus;

Функции-члены registerListener(), unregisterListener() и sendEvent() не являются virtual, так как вы не хотите, чтобы они были переопределены EventBus (что затем будет затронуто скрытием править).

В дополнение к предположению об отсутствии отношений наследования между любыми классами Listener или Event, оба вышеупомянутых подхода предполагают, что классы Listener и Event являются разными типами (т. Е. Нет класса Listener или Event). перечислены более одного раза). Если вы нарушите это предположение, наиболее вероятным будет то, что вызовы некоторых функций-членов станут неоднозначными.

0 голосов
/ 10 января 2019

Я бы создал класс для обработки Listener / события:

template <typename Listener, Event>
class EventHandler {
    std::vector<Listener*> mListeners;
public:
    void registerListener(Listener& listener);
    void unregisterListener(Listener& listener);
    void sendEvent(Event event);
};

Тогда ваш класс, который обрабатывает все, будет:

template <typename ... Ts>
class EventBus : Ts...
{
public:
    using Ts::registerListener...;    // Requires C++17
    using Ts::unregisterListener...;  // Prior that you have to do it with recursion
    using Ts::sendEvent...;           // class EventBus<T, Rest...> : T, EventBus<Rest...> 
};

При использовании:

EventBus<EventHandler<AListener, AEvent>, EventHandler<BListener, BEvent>> eventBus;

Кстати, Event, вероятно, зависит от Listener, поэтому наличие typename Listener::Event кажется подходящим и удаляет все Event параметры шаблона.

0 голосов
/ 10 января 2019

Вы можете использовать кортеж и некоторые черты типа:

#include <iostream>
#include <vector>
#include <tuple>
#include <utility>

template<typename x_Event> class
t_EventTrait;

template<typename ... x_Listener> class
t_EventBus
{
    private: ::std::tuple<::std::vector<x_Listener *>...> m_listeners;

    public: template<typename xx_Listener> void
    Register_Listener(xx_Listener & listener)
    {
        ::std::get<::std::vector<xx_Listener *>>(m_listeners).emplace_back(&listener);
    }

    public: template<typename x_Event> void
    Send_Event(x_Event & event)
    {
        for(auto p_listener: ::std::get<::std::vector<typename t_EventTrait<x_Event>::t_Listener *>>(m_listeners))
        {
            p_listener->On_Event(event);
        }
    }
};

struct t_EventA {};
struct t_ListenerA { void On_Event(t_EventA &) { ::std::cout << "handling A\n"; } };
template<> class t_EventTrait<t_EventA>{ public: using t_Listener = t_ListenerA; };

struct t_EventB {};
struct t_ListenerB { void On_Event(t_EventB &) { ::std::cout << "handling B\n"; } };
template<> class t_EventTrait<t_EventB>{ public: using t_Listener = t_ListenerB; };

int main()
{
    t_EventBus<t_ListenerA, t_ListenerB> bus{};
    t_ListenerA a{};
    bus.Register_Listener(a);
    t_EventA ea{};
    bus.Send_Event(ea);
    t_ListenerB b{};
    bus.Register_Listener(b);
    t_EventB eb{};
    bus.Send_Event(eb);
    return 0;   
}

онлайн-компилятор

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