Подтвердить что ты не робот

Проектирование механизма событий в С++

Я пытаюсь создать общий механизм передачи событий в С++, но не ограничиваясь зерном относительно "нового стиля" С++, и в то же время не перейдя за рамки шаблонов.

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

Мой пример использования тривиален для реализации в Ruby, Python или даже C, но с С++ я немного отстаю. Я посмотрел на Boost:: Signal и другие подобные библиотеки, но они кажутся слишком сложными или негибкими в соответствии с моим конкретным вариантом использования. (Boost, в частности, часто базируется на шаблонах, вплоть до полной путаницы, особенно таких, как boost:: bind или boost:: function.)


Здесь система с широкими штрихами:

  • Потребители прослушивают события, регистрируя себя непосредственно с объектами, которые создают события.

  • События - это просто имена строк, но каждое событие может содержать дополнительные данные.

  • Слушатели - это просто методы. Если бы это был С++ 11, я бы использовал lambdas, но мне нужна широкая переносимость компилятора, поэтому, используя методы на данный момент.

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

  • Очередь отправляется в строгом порядке запуска событий. (Так что, если производитель A запускает событие x, производитель B запускает y, а продление B триггеры z снова, тогда полный порядок - x, y, z.)

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


Здесь "идеальный" пример псевдокода потребителя:

SpaceshipController::create() {
    spaceship.listen("crash", &on_crash);
}

SpaceshipController::on_crash(CrashEvent event) {
    spaceship.unlisten("crash", &on_crash);
    spaceship.remove();
    add(new ExplosionDebris);
    add(new ExplosionSound);
}

И вот продюсер:

Spaceship::collided_with(CollisionObject object) {
    trigger("crash", new CrashEvent(object));
}

Все это хорошо и хорошо, но перевод на современный С++ - это то, где я встречаю трудности.


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

Я экспериментировал с использованием boost:: bind(), и я могу создать метод прослушивания следующим образом:

class EventManager
{
    template <class ProducerClass, class ListenerClass, class EventClass>
        void EventManager::listen(
            shared_ptr<ProducerClass> producer,
            string event_name,
            shared_ptr<ListenerClass> listener,
            void (ListenerClass::*method)(EventClass* event)
        )
    {
        boost::function1<void, EventClass*> bound_method = boost::bind(method, listener, _1);
        // ... add handler to a map for later execution ...
    }
}

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

Теперь можно слушать, делая:

void SpaceshipController::create()
{
    event_manager.listen(spaceship, "crash", shared_from_this(),
        &SpaceshipController::on_crash);
}

void SpaceshipController::on_crash(CrashEvent* event)
{
    // ...
}

Это довольно хорошо, хотя это многословно. Я ненавижу заставлять каждый класс наследовать enable_shared_from_this, а С++ требует, чтобы ссылки на методы включали имя класса, которое отстойно, но обе проблемы, вероятно, неизбежны.

К сожалению, я не вижу, как реализовать listen() таким образом, поскольку классы известны только во время компиляции. Мне нужно сохранить слушателей на карте каждого производителя, которая, в свою очередь, содержит карту для каждого события, например:

unordered_map<shared_ptr<ProducerClass>,
    unordered_map<string, vector<boost:function1<void, EventClass*> > > > listeners;

Но, конечно, С++ не позволяет мне. Я могу обмануть:

unordered_map<shared_ptr<void*>,
    unordered_map<string, vector<boost:function1<void, void*> > > > listeners;

но тогда это выглядит ужасно грязным.

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


Обратите внимание на то, что я явно пытаюсь избежать определения чистых классов интерфейса для каждого типа событий, Java-стиль:

class CrashEventListener
{
    virtual void on_crash(CrashEvent* event) = 0;
}

С количеством событий, которые я имею в виду, это будет ужасно, быстро.

Также возникает другая проблема: я хочу иметь мелкомасштабный контроль над обработчиками событий. Например, есть много продюсеров, которые просто предоставляют событие под названием "изменение". Я хочу, чтобы иметь возможность подключить продюсер. Событие "change" to on_a_change и, например, производитель B "change" event to on_b_change. Интерфейсы для каждого события сделали бы это неудобно в лучшем случае.


Имея это в виду, кто-то может указать мне в правильном направлении?

4b9b3361

Ответ 1

Обновление: Этот ответ объясняет один вариант, но я думаю, измененная версия этого решения на основе boost::any более чиста.


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

В этом случае самым простым решением является наличие у каждого космического корабля нескольких сигналов boost::, к которым могут подключаться слушатели. Когда корабль хочет сообщить о событии, он просто запускает соответствующий сигнал. (То есть вызовите сигнал через оператор(), как если бы это была функция.)

Эта система поразит пару ваших требований к марке (потребители сами регистрируются непосредственно с производителями событий, а обработчики - это просто методы), но это не решает проблему очереди событий. К счастью, для этого есть легкое решение.

Когда производитель событий (т.е. космический корабль) хочет уведомить своего слушателя (-ов) о событии, он не должен запускать сам сигнал. Вместо этого он должен упаковать вызов сигнала, используя boost:: bind, и передать результирующий объект функции в обработчик события (в виде функции boost::), которая добавляет его в свою очередь. Таким образом, все события, заданные обработчику событий, имеют только следующий тип: boost::function<void ()>

Когда пришло время сбросить очередь, обработчик событий просто вызывает все упакованные события в своей очереди, каждый из которых по сути является обратным вызовом, который вызывает сигнал производителя (космического корабля) для определенного события.

Здесь представлена ​​полная выборка. Функция main() демонстрирует простое "симуляцию" рабочей системы. Я даже запустил блокировку мьютекса в менеджере событий, так как я предполагаю, что к нему можно получить доступ более чем к одному потоку. Я не делал то же самое для контроллера или космических кораблей. Очевидно, простой однопоточный тест, поставляемый в функции main(), не обеспечивает безопасность потока диспетчера событий, но там ничего сложного не происходит.

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

В целом, эта реализация удовлетворяет всем вашим маркерам. (С примерами генерических событий, которые были выбраны как способ удовлетворения пункта № 2.) Если вы хотите увеличить тип сигнала "общий" с дополнительным параметром для "EventInfo" или что-то подобное, это легко сделать.

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

Еще одно: поскольку вы выразили некоторое презрение к тому, что космические корабли наследуются от enable_shared_from_this, я привязал объект космического корабля (через weak_ptr) к обработчику сигнала во время подписки. Таким образом, космический корабль не должен явно предоставлять слушателю ручку для себя, когда он запускает сигнал.

Кстати, операторы вывода BEGIN/END Orbit в main() находятся там, чтобы показать вам, что события не принимаются слушателями до тех пор, пока не запустится диспетчер событий.

(Для справки: этот компилятор использует gcc и boost 1.46, но должен работать со старыми версиями boost.)

#include <iostream>
#include <vector>
#include <string>
#include <set>
#include <map>

#include <boost/bind.hpp>
#include <boost/function.hpp>
#include <boost/signals2.hpp>
#include <boost/foreach.hpp>
#include <boost/thread.hpp>
#include <boost/thread/mutex.hpp>
#include <boost/lexical_cast.hpp>

// Forward declarations
class Spaceship;
typedef boost::shared_ptr<Spaceship> SpaceshipPtr;
typedef boost::weak_ptr<Spaceship> SpaceshipWPtr;

class EventManager;
typedef boost::shared_ptr<EventManager> EventManagerPtr;

class EventManager
{
public:
    // Notify listeners of all recent events
    void TriggerAllQueuedEvents()
    {
        NotificationVec vecNotifications;

        // Open a protected scope to modify the notification list
        {
            // One thread at a time
            boost::recursive_mutex::scoped_lock lock( m_notificationProtection );

            // Copy the notification vector to our local list and clear it at the same time
            std::swap( vecNotifications, m_vecQueuedNotifications );
        }

        // Now loop over the notification callbacks and call each one.
        // Since we're looping over the copy we just made, new events won't affect us.
        BOOST_FOREACH( const EventNotificationFn & fn, vecNotifications )
        {
            fn() ;
        }
    }

    // Callback signature
    typedef void EventNotificationFnSignature();
    typedef boost::function<EventNotificationFnSignature> EventNotificationFn;

    //! Queue an event with the event manager
    void QueueEvent( const EventNotificationFn & event )
    {
        // One thread at a time.
        boost::recursive_mutex::scoped_lock lock(  m_notificationProtection );

        m_vecQueuedNotifications.push_back(event);
    }

private:
    // Queue of events
    typedef std::vector<EventNotificationFn> NotificationVec ;
    NotificationVec m_vecQueuedNotifications;

    // This mutex is used to ensure one-at-a-time access to the list of notifications
    boost::recursive_mutex m_notificationProtection ;
};


class Spaceship
{
public:
    Spaceship(const std::string & name, const EventManagerPtr & pEventManager)
    : m_name(name)
    , m_pEventManager(pEventManager)
    {
    }

    const std::string& name()
    {
        return m_name;
    }

    // Define what a handler for crash events must look like
    typedef void CrashEventHandlerFnSignature(const std::string & sound);
    typedef boost::function<CrashEventHandlerFnSignature> CrashEventHandlerFn;

    // Call this function to be notified of crash events
    boost::signals2::connection subscribeToCrashEvents( const CrashEventHandlerFn & fn )
    {
        return m_crashSignal.connect(fn);
    }

    // Define what a handler for mutiny events must look like
    typedef void MutinyEventHandlerFnSignature(bool mutinyWasSuccessful, int numDeadCrew);
    typedef boost::function<MutinyEventHandlerFnSignature> MutinyEventHandlerFn;

    // Call this function to be notified of mutiny events
    boost::signals2::connection subscribeToMutinyEvents( const MutinyEventHandlerFn & fn )
    {
        return m_mutinySignal.connect(fn);
    }

    // Define what a handler for generic events must look like
    typedef void GenericEventHandlerFnSignature();
    typedef boost::function<GenericEventHandlerFnSignature> GenericEventHandlerFn;

    // Call this function to be notified of generic events
    boost::signals2::connection subscribeToGenericEvents( const std::string & eventType, const GenericEventHandlerFn & fn )
    {
        if ( m_genericEventSignals[eventType] == NULL )
        {
            m_genericEventSignals[eventType].reset( new GenericEventSignal );
        }
        return m_genericEventSignals[eventType]->connect(fn);
    }

    void CauseCrash( const std::string & sound )
    {
        // The ship has crashed.  Queue the event with the event manager.
        m_pEventManager->QueueEvent( boost::bind( boost::ref(m_crashSignal), sound ) ); //< Must use boost::ref because signal is noncopyable.
    }

    void CauseMutiny( bool successful, int numDied )
    {
        // A mutiny has occurred.  Queue the event with the event manager
        m_pEventManager->QueueEvent( boost::bind( boost::ref(m_mutinySignal), successful, numDied ) ); //< Must use boost::ref because signal is noncopyable.
    }

    void CauseGenericEvent( const std::string & eventType )
    {
        // Queue the event with the event manager
        m_pEventManager->QueueEvent( boost::bind( boost::ref(*m_genericEventSignals[eventType]) ) ); //< Must use boost::ref because signal is noncopyable.
    }

private:
    std::string m_name;
    EventManagerPtr m_pEventManager;

    boost::signals2::signal<CrashEventHandlerFnSignature> m_crashSignal;
    boost::signals2::signal<MutinyEventHandlerFnSignature> m_mutinySignal;

    // This map needs to use ptrs, because std::map needs a value type that is copyable
    // (boost signals are noncopyable)
    typedef boost::signals2::signal<GenericEventHandlerFnSignature> GenericEventSignal;
    typedef boost::shared_ptr<GenericEventSignal> GenericEventSignalPtr;
    std::map<std::string, GenericEventSignalPtr > m_genericEventSignals;
};

class Controller
{
public:
    Controller( const std::set<SpaceshipPtr> & ships )
    {
        // For every ship, subscribe to all of the events we're interested in.
        BOOST_FOREACH( const SpaceshipPtr & pSpaceship, ships )
        {
            m_ships.insert( pSpaceship );

            // Bind up a weak_ptr in the handler calls (using a shared_ptr would cause a memory leak)
            SpaceshipWPtr wpSpaceship(pSpaceship);

            // Register event callback functions with the spaceship so he can notify us.
            // Bind a pointer to the particular spaceship so we know who originated the event.
           boost::signals2::connection crashConnection = pSpaceship->subscribeToCrashEvents(
               boost::bind( &Controller::HandleCrashEvent, this, wpSpaceship, _1 ) );

           boost::signals2::connection mutinyConnection = pSpaceship->subscribeToMutinyEvents(
               boost::bind( &Controller::HandleMutinyEvent, this, wpSpaceship, _1, _2 ) );

           // Callbacks for generic events
           boost::signals2::connection takeoffConnection =
               pSpaceship->subscribeToGenericEvents(
                   "takeoff",
                   boost::bind( &Controller::HandleGenericEvent, this, wpSpaceship, "takeoff" ) );

           boost::signals2::connection landingConnection =
               pSpaceship->subscribeToGenericEvents(
                   "landing",
                   boost::bind( &Controller::HandleGenericEvent, this, wpSpaceship, "landing" ) );

           // Cache these connections to make sure we get notified
           m_allConnections[pSpaceship].push_back( crashConnection );
           m_allConnections[pSpaceship].push_back( mutinyConnection );
           m_allConnections[pSpaceship].push_back( takeoffConnection );
           m_allConnections[pSpaceship].push_back( landingConnection );
        }
    }

    ~Controller()
    {
        // Disconnect from any signals we still have
        BOOST_FOREACH( const SpaceshipPtr pShip, m_ships )
        {
            BOOST_FOREACH( boost::signals2::connection & conn, m_allConnections[pShip] )
            {
                conn.disconnect();
            }
        }
    }

private:
    typedef std::vector<boost::signals2::connection> ConnectionVec;
    std::map<SpaceshipPtr, ConnectionVec> m_allConnections;
    std::set<SpaceshipPtr> m_ships;

    void HandleGenericEvent( SpaceshipWPtr wpSpaceship, const std::string & eventType )
    {
        // Obtain a shared ptr from the weak ptr
        SpaceshipPtr pSpaceship = wpSpaceship.lock();

        std::cout << "Event on " << pSpaceship->name() << ": " << eventType << '\n';
    }

    void HandleCrashEvent(SpaceshipWPtr wpSpaceship, const std::string & sound)
    {
        // Obtain a shared ptr from the weak ptr
        SpaceshipPtr pSpaceship = wpSpaceship.lock();

        std::cout << pSpaceship->name() << " crashed with sound: " << sound << '\n';

        // That ship is dead.  Delete it from the list of ships we track.
        m_ships.erase(pSpaceship);

        // Also, make sure we don't get any more events from it
        BOOST_FOREACH( boost::signals2::connection & conn, m_allConnections[pSpaceship] )
        {
            conn.disconnect();
        }
        m_allConnections.erase(pSpaceship);
    }

    void HandleMutinyEvent(SpaceshipWPtr wpSpaceship, bool mutinyWasSuccessful, int numDeadCrew)
    {
        SpaceshipPtr pSpaceship = wpSpaceship.lock();
        std::cout << (mutinyWasSuccessful ? "Successful" : "Unsuccessful" ) ;
        std::cout << " mutiny on " << pSpaceship->name() << "! (" << numDeadCrew << " dead crew members)\n";
    }
};

int main()
{
    // Instantiate an event manager
    EventManagerPtr pEventManager( new EventManager );

    // Create some ships to play with
    int numShips = 5;
    std::vector<SpaceshipPtr> vecShips;
    for (int shipIndex = 0; shipIndex < numShips; ++shipIndex)
    {
        std::string name = "Ship #" + boost::lexical_cast<std::string>(shipIndex);
        SpaceshipPtr pSpaceship( new Spaceship(name, pEventManager) );
        vecShips.push_back(pSpaceship);
    }

    // Create the controller with our ships
    std::set<SpaceshipPtr> setShips( vecShips.begin(), vecShips.end() );
    Controller controller(setShips);

    // Quick-and-dirty "simulation"
    // We'll cause various events to happen to the ships in the simulation,
    // And periodically flush the events by triggering the event manager

    std::cout << "BEGIN Orbit #1" << std::endl;
    vecShips[0]->CauseGenericEvent("takeoff");
    vecShips[0]->CauseCrash("Kaboom!");
    vecShips[1]->CauseGenericEvent("takeoff");
    vecShips[1]->CauseCrash("Blam!");
    vecShips[2]->CauseGenericEvent("takeoff");
    vecShips[2]->CauseMutiny(false, 7);
    std::cout << "END Orbit #1" << std::endl;

    pEventManager->TriggerAllQueuedEvents();

    std::cout << "BEGIN Orbit #2" << std::endl;
    vecShips[3]->CauseGenericEvent("takeoff");
    vecShips[3]->CauseMutiny(true, 2);
    vecShips[3]->CauseGenericEvent("takeoff");
    vecShips[4]->CauseCrash("Splat!");
    std::cout << "END Orbit #2" << std::endl;

    pEventManager->TriggerAllQueuedEvents();

    std::cout << "BEGIN Orbit #3" << std::endl;
    vecShips[2]->CauseMutiny(false, 15);
    vecShips[2]->CauseMutiny(true, 20);
    vecShips[2]->CauseGenericEvent("landing");
    vecShips[3]->CauseCrash("Fizzle");
    vecShips[3]->CauseMutiny(true, 0); //< Should not cause output, since this ship has already crashed!
    std::cout << "END Orbit #3" << std::endl;

    pEventManager->TriggerAllQueuedEvents();

    return 0;
}

При запуске вышеуказанная программа производит следующий вывод:

BEGIN Orbit #1
END Orbit #1
Event on Ship #0: takeoff
Ship #0 crashed with sound: Kaboom!
Event on Ship #1: takeoff
Ship #1 crashed with sound: Blam!
Event on Ship #2: takeoff
Unsuccessful mutiny on Ship #2! (7 dead crew members)
BEGIN Orbit #2
END Orbit #2
Event on Ship #3: takeoff
Successful mutiny on Ship #3! (2 dead crew members)
Event on Ship #3: takeoff
Ship #4 crashed with sound: Splat!
BEGIN Orbit #3
END Orbit #3
Unsuccessful mutiny on Ship #2! (15 dead crew members)
Successful mutiny on Ship #2! (20 dead crew members)
Event on Ship #2: landing
Ship #3 crashed with sound: Fizzle

Ответ 2

Это почти год спустя, но ответа на этот вопрос не было, поэтому здесь идет другой подход, не полагающийся на RTTI (который на самом деле не требуется для этого).

  • Все события происходят из базового класса событий, который предоставляет виртуальную функцию для получения UID
  • Все события, происходящие из указанного класса, должны иметь макрос, присутствующий в определении, реализующем некоторые "магические"

    class EventFoo : public IEvent
    {
    public:
        IMPLEMENT_EVENT(EventFoo)
        // Regular EventFoo specific stuff
    };
    
  • Макрос заботится о реализации упомянутой выше виртуальной функции, а также реализует статическую функцию, возвращающую тот же UID

    typedef unsigned char* EventUID;
    
    #define IMPLEMENT_EVENT(Clazz) \
        static EventUID StaticGetUID() { \
            static unsigned char sUID = 0; \
            return (EventUID)&sUID; /* This will be unique in the executable! */ \
        } \
        virtual EventUID GetUID() const { return StaticGetUID(); }
    
  • Обратите внимание, что это также тривиально, чтобы поддерживать наследование одного события с помощью этого подхода (статический unsigned char здесь используется только как getto RTTI, чтобы избежать компиляции с ним только для этого)

  • Слушатели реализуют функцию формы OnEvent (IEvent & _Event);

  • Слушатели покроют еще несколько макросов в определении, чтобы сделать косвенное

    #define EVENT_BINDING_START() virtual void OnEvent(IEvent& _Event) {
    #define EVENT_BIND(Function, EventType) if (_Event->GetUID() == EventType::StaticGetUID()) Function(static_cast<EventType&>(_Event)); return; /* return right away to handle event */
    #define EVENT_BINDING_END(BaseClazz) BaseClazz::OnEvent(_Event); } /* If not handled by us, forward call to parent class */
    
    class Listener : public IEventHandler
    {
    public:
        EVENT_BINDING_START
            EVENT_BIND(OnFoo, EventFoo)
        EVENT_BINDING_END(IEventHandler)
    
        void OnFoo(EventFoo& _Foo) { /* do stuff */ }
    };
    

Регистрация для событий довольно тривиальна, поскольку вам нужно только где-то сохранить список IEventHandler *. OnEvent (..) становится гигантским беспорядком switch/if-else, но вы избавлены от его реализации самостоятельно. Объявление также довольно чисто с использованием макросов. У вас также всегда есть возможность самостоятельно реализовать OnEvent(). Скорее, я бы не стал слишком беспокоиться. Производительность будет очень близка к оператору switch для большинства компиляторов, и если вы не будете обрабатывать множество событий в одном прослушивателе, все равно будет очень быстро. Вы также можете кэшировать значение UID локально в макросе, чтобы избежать вызова виртуального для каждого типа события для обработки в прослушивателе. После первого вызова виртуальной функции в событии vtable будет находиться в кеше процессора, и любой последующий вызов будет очень быстрым. Функции StaticGetUID в значительной степени всегда будут включены в сборку релизов, чтобы просто вернуть константу. В результате код OnEvent довольно быстр и компактен.

Сборка также очень чиста в x64 и powerpc (для заглушки макроса), не уверен в x86. Это делает переход в макрос довольно безболезненным, если вам действительно нужно.

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

Ответ 3

Хорошо, есть достаточно простое решение, которое я пропустил раньше. Это путь.

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

Я внедряю систему, в которой "слушатели" регистрируются сами с событием "продюсеры". Это в основном стандарт шаблоннаблюдателя(a.k.a. "сигналы и слоты"), но с несколькими завихрения.

В C++, какой самый простой способ управлять соединениями между моими слушателями и продюсерами?

Я рекомендую использовать для этого существующую библиотеку. Либо Boost :: сигналы или Boost :: сигналы2 будет работать хорошо. Конечно, вы можете свернуть свои собственные сигналы и реализацию слотов, но почему? boost :: signal дает вам чистое, проверенное, обобщенное и документированное решение, которое многие другие программисты C++ сразу поймут, когда увидят ваш код.

Каждый из моих производителей способен производить несколько разных типов событий, что означает, что все функции моего слушателя будут иметь разные подписи, верно? Так как тип повышения :: сигнал зависит от подписи функции, которая обрабатывает, каждый производитель должен иметь несколько различных типов сигналов. я не сможет поместить их в коллекцию (например, карту), что означает, что каждый один должен быть объявлен отдельно. Еще хуже, мне придется объявить отдельную функцию "getter" для каждого отдельного сигнала, чтобы слушатели могут подключиться к нему. Поговорим о шаблоне! Как я могу избежать этого?

Это сложная часть.

Как вы упомянули в своем вопросе, одним из "решений" было бы, чтобы ваш сигнал генерировал событие как тип void *. И ты прав: это совершенно грязно. Как показывает мой другой ответ на этот вопрос, есть безопасный способ избежать ручного определения отдельного сигнала для каждого события. Если вы пойдете по этому пути, компилятор поймает все ваши ошибки, но код для этого довольно уродлив.

Но возникает вопрос: действительно ли так важно отлавливать ошибки типов во время компиляции? Проблема с использованием "грязного" трюка void * заключается в том, что вы никогда не узнаете, допустили ли вы ошибку, пока не стало слишком поздно. Если вы подключите обработчик к событию неправильного типа, поведение будет неопределенным.

Boost предоставляет библиотеку под названием boost::any, которая решает эту проблему для нас. Концептуально он похож на подход void *, но позволяет узнать, есть ли проблема. Если вы используете boost :: any, то все ваши обработчики будут иметь одинаковую сигнатуру функции: void (const boost::any &). Конечно, если вы подключите неправильный обработчик к определенному событию, компилятор не пометит его за вас. Но вы узнаете довольно быстро, когда будете тестировать. Это потому, что boost::any выдает исключение, если вы пытаетесь привести его к неправильному типу. В вашем коде не будет утомительного шаблона, и никакие ошибки не останутся незамеченными (при условии, что тестирование завершено).

Примечание. Boost :: any требует компиляции кода с включенным RTTI.

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

Ответ на эту часть в основном не зависит от того, какую систему вы выберете для подключения своих продюсеров к слушателям. Просто используйте boost::bind, чтобы превратить вашу функцию уведомления о событии в "thunk", которая может быть выполнена вашим менеджером событий позже. Поскольку все заголовки имеют подпись void (), ваш менеджер событий может легко хранить список уведомлений о событиях, которые в данный момент находятся в очереди и ожидают выполнения.

Ниже приведен пример полной реализации с использованием методов, описанных выше.

#include <iostream>
#include <vector>
#include <string>
#include <set>
#include <map>

#include <boost/bind.hpp>
#include <boost/function.hpp>
#include <boost/signals2.hpp>
#include <boost/foreach.hpp>
#include <boost/thread.hpp>
#include <boost/thread/mutex.hpp>
#include <boost/lexical_cast.hpp>
#include <boost/static_assert.hpp>
#include <boost/any.hpp>

// Forward declarations
class Spaceship;
typedef boost::shared_ptr<Spaceship> SpaceshipPtr;
typedef boost::weak_ptr<Spaceship> SpaceshipWPtr;

class EventManager;
typedef boost::shared_ptr<EventManager> EventManagerPtr;

// ******************************************************************
// EVENT DEFINITIONS
// ******************************************************************
struct TakeoffEvent
{
    static const std::string name ;
};
const std::string TakeoffEvent::name = "takeoff" ;

struct LandingEvent
{
    static const std::string name ;
};
const std::string LandingEvent::name = "landing" ;

struct CrashEvent
{
    static const std::string name ;

    CrashEvent(const std::string & s)
        : sound(s) {}

    std::string sound ;
};
const std::string CrashEvent::name = "crash" ;

struct MutinyEvent
{
    static const std::string name ;

    MutinyEvent(bool s, int n)
        : successful(s)
        , numDead(n) {}

    bool successful ;
    int numDead ;
};
const std::string MutinyEvent::name = "mutiny" ;
// ******************************************************************
// ******************************************************************

class EventManager
{
public:
    // Notify listeners of all recent events
    void FlushAllQueuedEvents()
    {
        NotificationVec vecNotifications;

        // Open a protected scope to modify the notification list
        {
            // One thread at a time
            boost::recursive_mutex::scoped_lock lock( m_notificationProtection );

            // Copy the notification vector to our local list and clear it at the same time
            std::swap( vecNotifications, m_vecQueuedNotifications );
        }

        // Now loop over the notification callbacks and call each one.
        // Since we're looping over the copy we just made, new events won't affect us.
        BOOST_FOREACH( const NamedNotification & nameAndFn, vecNotifications )
        {
            // Debug output
            std::cout << "Flushing " << nameAndFn.first << std::endl ;

            try
            {
                // call the listener(s)
                nameAndFn.second() ;
            }
            catch ( const boost::bad_any_cast & )
            {
                std::cout << "*** BUG DETECTED! Invalid any_cast. ***" << std::endl ;
            }
        }
    }

    // Callback signature
    typedef void EventNotificationFnSignature();
    typedef boost::function<EventNotificationFnSignature> EventNotificationFn;

    //! Queue an event with the event manager
    void QueueEvent( const std::string & name, const EventNotificationFn & nameAndEvent )
    {
        // One thread at a time.
        boost::recursive_mutex::scoped_lock lock(  m_notificationProtection );

        m_vecQueuedNotifications.push_back( NamedNotification(name, nameAndEvent) );
    }

private:
    // Queue of events
    typedef std::pair<std::string, EventNotificationFn> NamedNotification ;
    typedef std::vector<NamedNotification> NotificationVec ;
    NotificationVec m_vecQueuedNotifications;

    // This mutex is used to ensure one-at-a-time access to the list of notifications
    boost::recursive_mutex m_notificationProtection ;
};

class EventProducer
{
public:
    EventProducer( const EventManagerPtr & pEventManager )
        : m_pEventManager(pEventManager) {}

    typedef void SignalSignature(const boost::any &) ;
    typedef boost::function<SignalSignature> HandlerFn ;

    boost::signals2::connection subscribe( const std::string & eventName, const HandlerFn & fn )
    {
        // Create this signal if it does not exist yet
        if ( m_mapSignals.find(eventName) == m_mapSignals.end() )
        {
            m_mapSignals[eventName].reset( new EventSignal ) ;
        }
        return m_mapSignals[eventName]->connect(fn) ;
    }

    template <typename _Event>
    void trigger(const _Event & event)
    {
        // Do we have a signal for this (if not, then we have no listeners)
        EventSignalMap::iterator iterFind = m_mapSignals.find(event.name) ;
        if ( iterFind != m_mapSignals.end() )
        {
            EventSignal & signal = *iterFind->second ;

            // Wrap the event in a boost::any
            boost::any wrappedEvent = event ;

            m_pEventManager->QueueEvent( event.name, boost::bind( boost::ref(signal), wrappedEvent ) ) ;
        }
    }

protected:
    typedef boost::signals2::signal<SignalSignature> EventSignal ;
    typedef boost::shared_ptr<EventSignal> EventSignalPtr ;
    typedef std::map<std::string, EventSignalPtr> EventSignalMap ;
    EventSignalMap m_mapSignals ;

    EventManagerPtr m_pEventManager ;
};

typedef boost::shared_ptr<EventProducer> EventProducerPtr ;

class Spaceship : public EventProducer
{
public:

    Spaceship(const std::string & name, const EventManagerPtr & pEventManager)
        : EventProducer(pEventManager)
        , m_name(name)
    {
    }

    std::string & name()
    {
        return m_name ;
    }

private:

    std::string m_name;
};

class Listener
{
public:
    Listener( const std::set<SpaceshipPtr> & ships )
    {
        // For every ship, subscribe to all of the events we're interested in.
        BOOST_FOREACH( const SpaceshipPtr & pSpaceship, ships )
        {
            m_ships.insert( pSpaceship );

            // Bind up a weak_ptr in the handler calls (using a shared_ptr would cause a memory leak)
            SpaceshipWPtr wpSpaceship(pSpaceship);

            // Register event callback functions with the spaceship so he can notify us.
            // Bind a pointer to the particular spaceship so we know who originated the event.
            m_allConnections[pSpaceship].push_back( pSpaceship->subscribe( CrashEvent::name,
               boost::bind( &Listener::HandleCrashEvent, this, wpSpaceship, _1 ) ) );

            m_allConnections[pSpaceship].push_back( pSpaceship->subscribe( MutinyEvent::name,
               boost::bind( &Listener::HandleMutinyEvent, this, wpSpaceship, _1 ) ) );

            m_allConnections[pSpaceship].push_back( pSpaceship->subscribe( TakeoffEvent::name,
               boost::bind( &Listener::HandleTakeoffEvent, this, wpSpaceship, _1 ) ) );

            m_allConnections[pSpaceship].push_back( pSpaceship->subscribe( LandingEvent::name,
               boost::bind( &Listener::HandleLandingEvent, this, wpSpaceship, _1 ) ) );

            // Uncomment this next line to see what happens if you try to connect a handler to the wrong event.
            // (Connecting "landing" event to "crash" handler.)
            // m_allConnections[pSpaceship].push_back( pSpaceship->subscribe( LandingEvent::name,
            //   boost::bind( &Listener::HandleCrashEvent, this, wpSpaceship, _1 ) ) );
        }
    }

    ~Listener()
    {
        // Disconnect from any signals we still have
        BOOST_FOREACH( const SpaceshipPtr pShip, m_ships )
        {
            BOOST_FOREACH( boost::signals2::connection & conn, m_allConnections[pShip] )
            {
                conn.disconnect();
            }
        }
    }

private:
    typedef std::vector<boost::signals2::connection> ConnectionVec;
    std::map<SpaceshipPtr, ConnectionVec> m_allConnections;
    std::set<SpaceshipPtr> m_ships;

    void HandleTakeoffEvent( SpaceshipWPtr wpSpaceship, const boost::any & wrappedEvent )
    {
        // Obtain a shared ptr from the weak ptr
        SpaceshipPtr pSpaceship = wpSpaceship.lock();

        std::cout << "Takeoff event on " << pSpaceship->name() << '\n';
    }

    void HandleLandingEvent( SpaceshipWPtr wpSpaceship, const boost::any & wrappedEvent )
    {
        // Obtain a shared ptr from the weak ptr
        SpaceshipPtr pSpaceship = wpSpaceship.lock();

        std::cout << "Landing event on " << pSpaceship->name() << '\n';
    }

    void HandleCrashEvent(SpaceshipWPtr wpSpaceship, const boost::any & wrappedEvent )
    {
        // Extract the crash event from the boost::any
        CrashEvent crash = boost::any_cast<CrashEvent>(wrappedEvent) ;

        // Obtain a shared ptr from the weak ptr
        SpaceshipPtr pSpaceship = wpSpaceship.lock();

        std::cout << pSpaceship->name() << " crashed with sound: " << crash.sound << '\n';

        // That ship is dead.  Delete it from the list of ships we track.
        m_ships.erase(pSpaceship);

        // Also, make sure we don't get any more events from it
        BOOST_FOREACH( boost::signals2::connection & conn, m_allConnections[pSpaceship] )
        {
            conn.disconnect();
        }
        m_allConnections.erase(pSpaceship);
    }

    void HandleMutinyEvent(SpaceshipWPtr wpSpaceship, const boost::any & wrappedEvent )
    {
        // Extract the mutiny event from the boost::any
        MutinyEvent mutiny = boost::any_cast<MutinyEvent>(wrappedEvent) ;

        SpaceshipPtr pSpaceship = wpSpaceship.lock();
        std::cout << (mutiny.successful ? "Successful" : "Unsuccessful" ) ;
        std::cout << " mutiny on " << pSpaceship->name() << "! (" << mutiny.numDead << " dead crew members)\n";
    }
};

int main()
{
    // Instantiate an event manager
    EventManagerPtr pEventManager( new EventManager );

    // Create some ships to play with
    int numShips = 5;
    std::vector<SpaceshipPtr> vecShips;
    for (int shipIndex = 0; shipIndex < numShips; ++shipIndex)
    {
        std::string name = "Ship #" + boost::lexical_cast<std::string>(shipIndex);
        SpaceshipPtr pSpaceship( new Spaceship(name, pEventManager) );
        vecShips.push_back(pSpaceship);
    }

    // Create the controller with our ships
    std::set<SpaceshipPtr> setShips( vecShips.begin(), vecShips.end() );
    Listener controller(setShips);

    // Quick-and-dirty "simulation"
    // We'll cause various events to happen to the ships in the simulation,
    // And periodically flush the events by triggering the event manager

    std::cout << "BEGIN Orbit #1" << std::endl;
    vecShips[0]->trigger( TakeoffEvent() );
    vecShips[0]->trigger( CrashEvent("Kaboom!") );
    vecShips[1]->trigger( TakeoffEvent() );
    vecShips[1]->trigger( CrashEvent("Blam!") );
    vecShips[2]->trigger( TakeoffEvent() );
    vecShips[2]->trigger( MutinyEvent(false, 7) );
    std::cout << "END Orbit #1\n" << std::endl;

    pEventManager->FlushAllQueuedEvents();
    std::cout << "\n" ;

    std::cout << "BEGIN Orbit #2" << std::endl;
    vecShips[3]->trigger( TakeoffEvent() );
    vecShips[3]->trigger( MutinyEvent(true, 2) );
    vecShips[4]->trigger( TakeoffEvent() );
    vecShips[4]->trigger( CrashEvent("Splat!") );
    std::cout << "END Orbit #2\n" << std::endl;

    pEventManager->FlushAllQueuedEvents();
    std::cout << "\n" ;

    std::cout << "BEGIN Orbit #3" << std::endl;
    vecShips[2]->trigger( MutinyEvent(false, 15) );
    vecShips[2]->trigger( MutinyEvent(true, 20) );
    vecShips[2]->trigger( LandingEvent() );
    vecShips[3]->trigger( CrashEvent("Fizzle.") );
    vecShips[3]->trigger( MutinyEvent(true, 0) ); //< Should not cause output, since this ship has already crashed!
    std::cout << "END Orbit #3\n" << std::endl;

    pEventManager->FlushAllQueuedEvents();
    std::cout << "\n" ;

    return 0;
}

Ответ 4

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

void Listener::on_event( Event* event )
{
   switch (event.type)
   {
   case (kCrashEvent):
        this->on_crash((CrashEvent*)event);
   ...
   }
}

Тогда ваша функция прослушивания будет выглядеть так:

void EventManager::listen( Listener* listener, EventType eventType )
{
    // Store the listener, and the type of event it listening for
    ...
}

В этом дизайне EventManager имеет всю информацию (включая типы), которая ему нужна для очереди и отправки событий, и у вас нет взрыва интерфейса-метода, о котором вы беспокоились с помощью модели java. Каждый класс слушателя просто реализует свой метод отправки on_event соответствующим образом для тех видов событий, которые они хотят прослушать, и как они хотят их слушать.

Ответ 5

Здесь модифицированная реализация образца, которая (1) требует меньше "шаблона" в классах реализации Listener, и (2) добавляет немного улучшенную информацию об отладке при событиях очередей в менеджере событий.

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

#include <iostream>
#include <vector>
#include <string>
#include <set>
#include <map>

#include <boost/bind.hpp>
#include <boost/function.hpp>
#include <boost/signals2.hpp>
#include <boost/foreach.hpp>
#include <boost/thread.hpp>
#include <boost/thread/mutex.hpp>
#include <boost/lexical_cast.hpp>
#include <boost/static_assert.hpp>

// Forward declarations
class Spaceship;
typedef boost::shared_ptr<Spaceship> SpaceshipPtr;
typedef boost::weak_ptr<Spaceship> SpaceshipWPtr;

class EventManager;
typedef boost::shared_ptr<EventManager> EventManagerPtr;

// ******************************************************************
// EVENT DEFINITIONS
// ******************************************************************
struct TakeoffEvent
{
    static const std::string name ;
};
const std::string TakeoffEvent::name = "takeoff" ;

struct LandingEvent
{
    static const std::string name ;
};
const std::string LandingEvent::name = "landing" ;

struct CrashEvent
{
    static const std::string name ;

    CrashEvent(const std::string & s)
        : sound(s) {}

    std::string sound ;
};
const std::string CrashEvent::name = "crash" ;

struct MutinyEvent
{
    static const std::string name ;

    MutinyEvent(bool s, int n)
        : successful(s)
        , numDead(n) {}

    bool successful ;
    int numDead ;
};
const std::string MutinyEvent::name = "mutiny" ;
// ******************************************************************
// ******************************************************************

class EventManager
{
public:
    // Notify listeners of all recent events
    void FlushAllQueuedEvents()
    {
        NotificationVec vecNotifications;

        // Open a protected scope to modify the notification list
        {
            // One thread at a time
            boost::recursive_mutex::scoped_lock lock( m_notificationProtection );

            // Copy the notification vector to our local list and clear it at the same time
            std::swap( vecNotifications, m_vecQueuedNotifications );
        }

        // Now loop over the notification callbacks and call each one.
        // Since we're looping over the copy we just made, new events won't affect us.
        BOOST_FOREACH( const NamedNotification & nameAndFn, vecNotifications )
        {
            // Debug output
            std::cout << "Flushing " << nameAndFn.first << std::endl ;

            // call the listener(s)
            nameAndFn.second() ;
        }
    }

    // Callback signature
    typedef void EventNotificationFnSignature();
    typedef boost::function<EventNotificationFnSignature> EventNotificationFn;

    //! Queue an event with the event manager
    void QueueEvent( const std::string & name, const EventNotificationFn & nameAndEvent )
    {
        // One thread at a time.
        boost::recursive_mutex::scoped_lock lock(  m_notificationProtection );

        m_vecQueuedNotifications.push_back( NamedNotification(name, nameAndEvent) );
    }

private:
    // Queue of events
    typedef std::pair<std::string, EventNotificationFn> NamedNotification ;
    typedef std::vector<NamedNotification> NotificationVec ;
    NotificationVec m_vecQueuedNotifications;

    // This mutex is used to ensure one-at-a-time access to the list of notifications
    boost::recursive_mutex m_notificationProtection ;
};

template <typename _Event>
class Producer
{
public:
    Producer( const EventManagerPtr & pEventManager )
        : m_pEventManager(pEventManager) {}

    typedef void SignalSignature(const _Event &) ;

    boost::signals2::connection subscribe( const boost::function<SignalSignature> & fn )
    {
        return m_signal.connect(fn) ;
    }

    void trigger(const _Event & event)
    {
        m_pEventManager->QueueEvent( event.name, boost::bind( boost::ref(m_signal), event ) ) ;
    }

protected:
    // Instantiate the tuple of signals
    boost::signals2::signal<SignalSignature> m_signal ;
    EventManagerPtr m_pEventManager ;
};

class Spaceship : public Producer<TakeoffEvent>
                , public Producer<LandingEvent>
                , public Producer<CrashEvent>
                , public Producer<MutinyEvent>
{
public:

    Spaceship(const std::string & name, const EventManagerPtr & pEventManager)
        : Producer<TakeoffEvent>(pEventManager)
        , Producer<LandingEvent>(pEventManager)
        , Producer<CrashEvent>(pEventManager)
        , Producer<MutinyEvent>(pEventManager)
        , m_name(name)
    {
    }

    std::string & name()
    {
        return m_name ;
    }

    template <typename _Event>
    boost::signals2::connection subscribe( const boost::function<void (const _Event &)> & fn )
    {
        // call the correct base class
        return Producer<_Event>::subscribe( fn ) ;
    }

    template <typename _Event>
    void trigger(const _Event & event = _Event() )
    {
        // call the correct base class
        Producer<_Event>::trigger(event) ;
    }

private:

    std::string m_name;
};

class Listener
{
public:
    Listener( const std::set<SpaceshipPtr> & ships )
    {
        // For every ship, subscribe to all of the events we're interested in.
        BOOST_FOREACH( const SpaceshipPtr & pSpaceship, ships )
        {
            m_ships.insert( pSpaceship );

            // Bind up a weak_ptr in the handler calls (using a shared_ptr would cause a memory leak)
            SpaceshipWPtr wpSpaceship(pSpaceship);

            // Register event callback functions with the spaceship so he can notify us.
            // Bind a pointer to the particular spaceship so we know who originated the event.
            m_allConnections[pSpaceship].push_back( pSpaceship->subscribe<CrashEvent>(
               boost::bind( &Listener::HandleCrashEvent, this, wpSpaceship, _1 ) ) );

            m_allConnections[pSpaceship].push_back( pSpaceship->subscribe<MutinyEvent>(
               boost::bind( &Listener::HandleMutinyEvent, this, wpSpaceship, _1 ) ) );

            m_allConnections[pSpaceship].push_back( pSpaceship->subscribe<TakeoffEvent>(
               boost::bind( &Listener::HandleGenericEvent<TakeoffEvent>, this, wpSpaceship, _1 ) ) );

            m_allConnections[pSpaceship].push_back( pSpaceship->subscribe<LandingEvent>(
               boost::bind( &Listener::HandleGenericEvent<LandingEvent>, this, wpSpaceship, _1 ) ) );
        }
    }

    ~Listener()
    {
        // Disconnect from any signals we still have
        BOOST_FOREACH( const SpaceshipPtr pShip, m_ships )
        {
            BOOST_FOREACH( boost::signals2::connection & conn, m_allConnections[pShip] )
            {
                conn.disconnect();
            }
        }
    }

private:
    typedef std::vector<boost::signals2::connection> ConnectionVec;
    std::map<SpaceshipPtr, ConnectionVec> m_allConnections;
    std::set<SpaceshipPtr> m_ships;

    template <typename _Event>
    void HandleGenericEvent( SpaceshipWPtr wpSpaceship, const _Event & event)
    {
        // Obtain a shared ptr from the weak ptr
        SpaceshipPtr pSpaceship = wpSpaceship.lock();

        std::cout << "Event on " << pSpaceship->name() << ": " << _Event::name << '\n';
    }

    void HandleCrashEvent(SpaceshipWPtr wpSpaceship, const CrashEvent & crash)
    {
        // Obtain a shared ptr from the weak ptr
        SpaceshipPtr pSpaceship = wpSpaceship.lock();

        std::cout << pSpaceship->name() << " crashed with sound: " << crash.sound << '\n';

        // That ship is dead.  Delete it from the list of ships we track.
        m_ships.erase(pSpaceship);

        // Also, make sure we don't get any more events from it
        BOOST_FOREACH( boost::signals2::connection & conn, m_allConnections[pSpaceship] )
        {
            conn.disconnect();
        }
        m_allConnections.erase(pSpaceship);
    }

    void HandleMutinyEvent(SpaceshipWPtr wpSpaceship, const MutinyEvent & mutiny )
    {
        SpaceshipPtr pSpaceship = wpSpaceship.lock();
        std::cout << (mutiny.successful ? "Successful" : "Unsuccessful" ) ;
        std::cout << " mutiny on " << pSpaceship->name() << "! (" << mutiny.numDead << " dead crew members)\n";
    }
};

int main()
{
    // Instantiate an event manager
    EventManagerPtr pEventManager( new EventManager );

    // Create some ships to play with
    int numShips = 5;
    std::vector<SpaceshipPtr> vecShips;
    for (int shipIndex = 0; shipIndex < numShips; ++shipIndex)
    {
        std::string name = "Ship #" + boost::lexical_cast<std::string>(shipIndex);
        SpaceshipPtr pSpaceship( new Spaceship(name, pEventManager) );
        vecShips.push_back(pSpaceship);
    }

    // Create the controller with our ships
    std::set<SpaceshipPtr> setShips( vecShips.begin(), vecShips.end() );
    Listener controller(setShips);

    // Quick-and-dirty "simulation"
    // We'll cause various events to happen to the ships in the simulation,
    // And periodically flush the events by triggering the event manager

    std::cout << "BEGIN Orbit #1" << std::endl;
    vecShips[0]->trigger( TakeoffEvent() );
    vecShips[0]->trigger( CrashEvent("Kaboom!") );
    vecShips[1]->trigger( TakeoffEvent() );
    vecShips[1]->trigger( CrashEvent("Blam!") );
    vecShips[2]->trigger( TakeoffEvent() );
    vecShips[2]->trigger( MutinyEvent(false, 7) );
    std::cout << "END Orbit #1\n" << std::endl;

    pEventManager->FlushAllQueuedEvents();
    std::cout << "\n" ;

    std::cout << "BEGIN Orbit #2" << std::endl;
    vecShips[3]->trigger( TakeoffEvent() );
    vecShips[3]->trigger( MutinyEvent(true, 2) );
    vecShips[4]->trigger( TakeoffEvent() );
    vecShips[4]->trigger( CrashEvent("Splat!") );
    std::cout << "END Orbit #2\n" << std::endl;

    pEventManager->FlushAllQueuedEvents();
    std::cout << "\n" ;

    std::cout << "BEGIN Orbit #3" << std::endl;
    vecShips[2]->trigger( MutinyEvent(false, 15) );
    vecShips[2]->trigger( MutinyEvent(true, 20) );
    vecShips[2]->trigger( LandingEvent() );
    vecShips[3]->trigger( CrashEvent("Fizzle.") );
    vecShips[3]->trigger( MutinyEvent(true, 0) ); //< Should not cause output, since this ship has already crashed!
    std::cout << "END Orbit #3\n" << std::endl;

    pEventManager->FlushAllQueuedEvents();
    std::cout << "\n" ;

    return 0;
}

Ответ 6

Самый простой способ, который я всегда нашел, - это аналогичные случаи: root - все события из полиморфной пустой базы (класс с только виртуальной структурой), каждый из которых является классом, несущим парадигмы событий:

struct event { virtual ~event() {} };
struct crash: public event { object* collider; };

Диспетчер - это функтор, который принимает событие & и просматривает коллекцию (обычно std:: list) полиморфных внутренних мостов, таких как

struct bridge
{
    virtual ~bridge() {}
    virtual bool same_as(const bridge* p) const=0; //to implement unlisten
    virtual bool on_ev(event& ev)=0;
};

template<class E, class T>
struct fnbridge: public bridge
{
    T* pt;
    bool(T::*mfn)(E&);

    virtual bool on_ev(event& ev)
    {
        E* pe = dynamic_cast<E*>(&ev);
        return pe && (pt->*mfn)(*pe);
    }

    virtual bool same_as(const bridge* p)
    {
        const fnbridge* pb = dynamic_cast<const fnbridge*>(p);
        return pb && pb->pt == pt && pb->mfn == mfn;
    }
};

Теперь вы можете обернуть std::list<bridge*> в класс, добавляя мосты на "listen" (фактически template<class T, class E>void listen(T& t, bool(T::*mfn)(E&)) и удаляя unlisten через remove_if с предикатом, который вызывает same_as. Эта оболочка также является функтором, принимающим событие, итерируя в списке, вызывающем on_ev, в конце концов разбивая цикл, если сохраняете true.

Каждый раз, когда я пытался избежать dynamic_cast -s, я обнаружил, что сам пытался перепрограммировать его с помощью тегов типов и т.д., поэтому... для решения времени исполнения. пусть RTTI сыграет свою роль.

Ответ 7

Модель событий Qt поучительна