37

Was ist eine gute Art, mit Objekten umzugehen und sie miteinander reden zu lassen?Spielobjekte, die miteinander sprechen

Bis jetzt waren alle meine Spiele Hobby/Student klein, so wurde dieses Problem in der Regel auf eine eher hässliche Weise gelöst, die zu engen Integration und zirkuläre Abhängigkeiten führen. Was für die Größe der Projekte, die ich tat, gut war.

Allerdings sind meine Projekte immer größer und komplexer geworden und jetzt möchte ich Code wiederverwenden und meinen Kopf zu einem einfacheren Ort machen.

Das Hauptproblem ich habe, ist im Allgemeinen entlang der Linien von Player Bedürfnissen über die Map zu wissen, und damit auch die Enemy, dies in der Regel in Einstellen vieler Zeiger und mit vielen Abhängigkeiten abgestiegen ist, und dies wird zu einem schnell Chaos.

Ich habe nach dem Vorbild eines Nachrichtenstilsystems gedacht. aber ich kann nicht wirklich sehen, wie dies die Abhängigkeiten reduziert, da ich immer noch die Zeiger überallhin senden würde.

PS: Ich denke, das wurde zuvor diskutiert, aber ich weiß nicht, was es genau die Notwendigkeit genannt wird, die ich habe.

Antwort

41

BEARBEITEN: Im Folgenden beschreibe ich ein grundlegendes Event-Messaging-System, das ich immer wieder verwendet habe. Und es ist mir aufgefallen, dass beide Schulprojekte Open Source und im Web sind. Sie können die zweite Version dieses Messagingsystems (und einiges mehr) bei http://sourceforge.net/projects/bpfat/ finden. Genießen Sie, und lesen Sie unten für eine mehr durch Beschreibung des Systems!

Ich habe ein generisches Nachrichtensystem geschrieben und es in eine Handvoll Spiele eingeführt, die auf der PSP sowie einige Unternehmensebene Anwendungssoftware veröffentlicht wurden. Der Sinn des Messagingsystems besteht darin, nur die Daten umzuleiten, die für die Verarbeitung einer Nachricht oder eines Ereignisses benötigt werden, je nachdem, welche Terminologie verwendet werden soll.

AA12

struct TEventMessage 
{ 
    int _iMessageID; 
} 

class IEventMessagingSystem 
{ 
    Post(int iMessageId); 
    Post(int iMessageId, float fData); 
    Post(int iMessageId, int iData); 
    // ... 
    Post(TMessageEvent * pMessage); 
    Post(int iMessageId, void * pData); 
} 

typedef float(*IEventMessagingSystem::Callback)(TEventMessage * pMessage); 

class CEventMessagingSystem 
{ 
    Init  (); 
    DNit  (); 
    Exec  (float fElapsedTime); 

    Post  (TEventMessage * oMessage); 

    Register (int iMessageId, IEventMessagingSystem* pObject, FObjectCallback* fpMethod); 
    Unregister (int iMessageId, IEventMessagingSystem* pObject, FObjectCallback * fpMethod); 
} 

#define MSG_Startup   (1) 
#define MSG_Shutdown   (2) 
#define MSG_PlaySound   (3) 
#define MSG_HandlePlayerInput (4) 
#define MSG_NetworkMessage  (5) 
#define MSG_PlayerDied   (6) 
#define MSG_BeginCombat  (7) 
#define MSG_EndCombat   (8) 

Und nun noch ein bisschen eine Erklärung: Schnelllauf nach unten verwendet der Liste der Objekte dieser etwas entlang der Linien von zu bewerkstelligen ist. Das erste Objekt, TEventMessage, ist das Basisobjekt zum Darstellen von Daten, die vom Nachrichtensystem gesendet werden. Standardmäßig wird immer die ID der Nachricht gesendet, wenn Sie sichergehen wollen, dass Sie eine Nachricht erhalten haben, die Sie erwartet haben (im Allgemeinen mache ich das nur im Debug).

Als nächstes folgt die Interface-Klasse, die ein generisches Objekt für das Messaging-System gibt, das beim Callback für das Casting verwendet wird. Darüber hinaus bietet es auch eine "einfach zu bedienende" Schnittstelle für das Versenden verschiedener Datentypen an das Messaging-System.

Danach haben wir unseren Callback typedef, Einfach gesagt erwartet es ein Objekt vom Typ der Interface-Klasse und wird einen TEventMessage-Zeiger übergeben ... Optional können Sie den Parameter const, aber ich habe Trickle-up-Verarbeitung vor für verwendet Dinge wie Stack-Debugging und solche des Messaging-Systems.

Zuletzt und im Kern ist das CEventMessagingSystem-Objekt. Dieses Objekt enthält ein Array von Callback-Objektstapeln (oder verknüpfte Listen oder Warteschlangen oder wie auch immer Sie die Daten speichern möchten). Die Callback-Objekte, die oben nicht gezeigt sind, müssen einen Zeiger auf das Objekt sowie die Methode zum Aufrufen dieses Objekts beibehalten (und sind eindeutig durch diese definiert). Wenn Sie Register() registrieren, fügen Sie einen Eintrag im Objektstapel unter der Array-Position der Nachrichten-ID hinzu. Wenn Sie die Registrierung aufheben() entfernen Sie diesen Eintrag.

Das ist es im Grunde .. Nun hat dies die Voraussetzung, dass alles über das IEventMessagingSystem und das TEventMessage-Objekt wissen muss ...aber dieses Objekt sollte sich nicht so oft ändern und nur die Teile von Informationen weitergeben, die für die Logik entscheidend sind, die von dem aufgerufenen Ereignis diktiert wird. Auf diese Weise muss ein Spieler nicht direkt über die Karte oder den Gegner Bescheid wissen, um Ereignisse an ihn zu senden. Ein verwaltetes Objekt kann eine API auch für ein größeres System aufrufen, ohne dass etwas darüber bekannt sein muss.

Zum Beispiel: Wenn ein Feind stirbt, möchten Sie, dass er einen Soundeffekt ausgibt. Angenommen, Sie haben einen Sound-Manager, der die IEventMessagingSystem-Schnittstelle erbt, würden Sie einen Rückruf für das Messaging-System einrichten, das einen TEventMessagePlaySoundEffect oder etwas Ähnliches akzeptiert. Der Sound Manager würde diesen Callback dann registrieren, wenn Soundeffekte aktiviert sind (oder die Registrierung aufheben, wenn Sie alle Soundeffekte für einfache Ein-/Aus-Funktionen deaktivieren möchten). Als nächstes würde das feindliche Objekt auch vom IEventMessaging-System erben, ein TEventMessagePlaySoundEffect-Objekt zusammensetzen (es müsste MSG_PlaySound für seine Message-ID und dann die ID des zu spielenden Soundeffekts sein, sei es eine int-ID oder der Name des Sounds Effekt) und rufen Sie einfach Post (& oEventMessagePlaySoundEffect).

Jetzt ist dies nur ein sehr einfaches Design ohne Implementierung. Wenn Sie sofort ausgeführt werden, müssen Sie die TEventemessage-Objekte (die ich hauptsächlich in Konsolenspielen verwendete) nicht puffern. Wenn Sie sich in einer Multithread-Umgebung befinden, ist dies eine sehr gut definierte Möglichkeit, dass Objekte und Systeme, die in separaten Threads ausgeführt werden, miteinander kommunizieren. Sie sollten jedoch die TEventemessage-Objekte beibehalten, damit die Daten bei der Verarbeitung verfügbar sind.

Eine weitere Änderung ist für Objekte, die nur Post() Daten benötigen, können Sie eine statische Gruppe von Methoden im IEventMessagingSystem erstellen, so dass sie nicht von ihnen erben müssen (Das ist für den einfachen Zugriff und Callback-Fähigkeiten verwendet) , nicht direkt für Post() -Aufrufe benötigt).

Für alle Leute, die MVC erwähnen, ist es ein sehr gutes Muster, aber Sie können es auf so viele verschiedene Arten und auf verschiedenen Ebenen implementieren. Das aktuelle Projekt, an dem ich beruflich arbeite, ist ein MVC-Setup etwa 3 mal, da gibt es den globalen MVC der gesamten Anwendung und dann ist designtechnisch jedes MV und C auch ein eigenständiges MVC-Pattern. Also was ich versucht habe zu tun Hier wird erklärt, wie man ein C erzeugt, das generisch genug ist, um fast jede Art von M zu handhaben, ohne dass man in eine Ansicht gelangen muss ...

Zum Beispiel ein Objekt, wenn es "stirbt" ein Soundeffekt. Sie würden eine Struktur für das Soundsystem wie TEventMessageSoundEffect erstellen, die von der TEventMessage erbt und eine Soundeffekt-ID hinzufügt (sei es ein vorgeladener Int oder der Name der SFX-Datei, wie auch immer sie in Ihrem System verfolgt werden). Dann muss das Objekt nur noch ein TEventMessageSoundEffect-Objekt mit dem entsprechenden Death-Noise-Objekt zusammensetzen und den Post (& oEventMessageSoundEffect); Objekt. Angenommen, der Ton ist nicht stummgeschaltet (was möchten Sie die Sound-Manager aufheben.

EDIT: Um dies ein wenig in Bezug auf den Kommentar unten zu verdeutlichen: Jedes Objekt zum Senden oder Empfangen einer Nachricht muss nur Ich kenne die IEventMessagingSystem-Schnittstelle, und dies ist das einzige Objekt, das das EventMessagingSystem von allen anderen Objekten wissen muss.Das gibt Ihnen die Trennung: Jedes Objekt, das eine Nachricht empfangen möchte, registriert einfach (MSG, Object, Callback) s Wenn dann ein Objekt Post (MSG, Data) aufruft, sendet es dieses über die ihm bekannte Schnittstelle an das EventMessagingSystem, der EMS wird dann jedes registrierte Objekt des Ereignisses benachrichtigen.Sie könnten ein MSG_PlayerDied ausführen, das andere Systeme behandeln, oder das Der Player kann MSG_PlaySound, MSG_Respawn usw. aufrufen, damit die Nachrichten auf diese Nachrichten reagieren können Post (MSG, Data) als abstrahierte API zu den verschiedenen Systemen innerhalb einer Game Engine.

Oh! Eine andere Sache, auf die ich hingewiesen wurde. Das System, das ich oben beschrieben habe, passt in das Observer-Muster in der anderen gegebenen Antwort. Wenn Sie also eine allgemeinere Beschreibung wollen, um mir etwas mehr Sinn zu geben, ist das ein kurzer Artikel, der eine gute Beschreibung gibt.

Hoffe das hilft und viel Spaß!

+1

+1 für die gründliche Erklärung, aber ich habe auch eine Bemerkung: Sie sagten, dass * ein Spieler muss nicht über die Karte wissen *, um Ereignisse zu senden, aber Ihr Beispiel impliziert, dass ein sterbender Feind über jeden anderen wissen muss Teil des Programms, das benachrichtigt werden muss. Ich hätte erwartet, dass es einfach eine "Ich bin gerade gestorben" -Meldung senden würde, und dann würde Ihr Nachrichtensystem die Zuhörer informieren, die an diesem Ereignis interessiert sind (Ton abspielen, Punkte aktualisieren, usw.). Auf diese Weise sieht es so aus, als ob jede Entität eine Menge Nachrichten für ein einzelnes Ereignis senden muss (Ton abspielen, Punktzahl erhöhen). Oder habe ich es falsch verstanden? – Groo

+1

@Groo Ich konnte meine Antwort nicht genug verkürzen, also habe ich sie in meine Antwort oben bearbeitet. – James

+0

Hallo Mann, es ist über 5 Jahre her seit Ihrer Antwort, aber dieser Post ist gekommen, als ich nach einer einfachen pubsub Idee suchte, und ich muss sagen, dass ich die Quellen heruntergeladen habe, und abgesehen von den Kodierungsstandards Ich bin nicht daran gewöhnt und die Tatsache, dass C++ seit 2005 ein wenig fortgeschritten ist, ist sehr interessant für die Forschung und ich habe einige von EMS Skelett für mein C# Spiel verwendet. Es sieht wirklich erstaunlich und schwierig aus, was ihr drei Jungs gemacht habt, und ich hoffe, ich würde mehr davon lernen! –

4

Dies gilt wahrscheinlich nicht nur für Spielklassen, sondern für Klassen im allgemeinen Sinne. Das MVC (Model-View-Controller) -Muster zusammen mit Ihrer vorgeschlagenen Nachrichtenpumpe ist alles was Sie brauchen.

"Feind" und "Player" wird wahrscheinlich in den Modell-Teil von MVC passen, es spielt keine große Rolle, aber die Faustregel ist, haben alle Modelle und Ansichten interagieren über den Controller. Also, Sie würden Referenzen (besser als Zeiger) auf (fast) alle anderen Klassen-Instanzen aus dieser "Controller" -Klasse behalten wollen, nennen wir es ControlDispatcher. Fügen Sie eine Nachrichtenpumpe hinzu (hängt davon ab, für welche Plattform Sie codieren), instanziieren Sie sie zuerst (vor allen anderen Klassen und haben die anderen Objekte davon) oder zuletzt (und lassen Sie die anderen Objekte als Referenzen in ControlDispatcher gespeichert).

Natürlich muss die ControlDispatcher-Klasse wahrscheinlich weiter in spezialisiertere Controller aufgeteilt werden, nur um den Code pro Datei bei ungefähr 700-800 Zeilen zu halten (das ist die Grenze für mich mindestens) und es kann sogar haben Mehr Threads pumpen und verarbeiten Nachrichten nach Ihren Bedürfnissen.

Prost

+0

+1 Es gibt keine Notwendigkeit, Sachen neu zu erfinden ist, stimme ich zu. – Groo

-1

@kellogs Vorschlag von MVC gültig ist, und in einigen Spielen verwendet, obwohl seine viel häufiger in Web-Anwendungen und Frameworks. Es könnte zu viel und zu viel dafür sein.

Ich würde Ihr Design überdenken, warum muss der Spieler mit Feinden sprechen? Könnten sie nicht beide von einer Actor-Klasse erben? Warum müssen Schauspieler mit der Karte sprechen?

Während ich lese, was ich geschrieben habe, fängt es an, in ein MVC Rahmenwerk zu passen ... Ich habe offenbar in letzter Zeit zu viele Schienenarbeiten gemacht. Allerdings wäre ich willens zu wetten, sie müssen nur wissen, wie sie mit einem anderen Akteur kollidieren, und sie haben eine Position, die sowieso relativ zur Map sein sollte.

Hier ist eine Implementierung von Asteroids, dass ich arbeitete. Ihr Spiel ist vielleicht komplex und ist es wahrscheinlich auch.

+0

Spieler und Feind Sie müssen wissen, über Karte zu navigieren, es war nur ein grobes vereinfachtes Beispiel. –

15

die allgemeinen Lösungen für die Kommunikation zwischen Objekten zu vermeiden enge Kopplung:

  1. Mediator pattern
  2. Observer pattern
+1

Mediator-Muster ist genau dort in der MVC (wo der Controller der Vermittler ist). +1 für Beobachtermuster.In einigen Plattformen stark verwendet. – kellogs

+0

Hmmm .. Aus dem Artikel, den Sie verlinkt haben, sieht 'Relationship Manager' auf den ersten Blick ein wenig stinkend aus, es scheint wie ein Gott-Objekt. Es soll eine Art Singleton sein, der alles über jeden weiß. Der Artikel zeigt Member-Methoden einzelner Objekte ('Customer.AddOrder',' Customer.RemoveOrder'), die ihre Interna dem "Manager" offen legen und es dem Manager dann erlauben, die Arbeit für sie zu erledigen. Wo ist das OOP dann gelaufen? Um zu testen, ob einem Kunden eine einzelne Bestellung hinzugefügt wird, sollten Sie die gesamte Manager-Klasse nachahmen. Ich würde es bevorzugen, wenn du nur die ersten beiden Links behältst. – Groo

+0

Schöne Bemerkung von dir. Ich entferne den Link ;-). –

0

Seien Sie vorsichtig mit einem "Mitteilungartzahl System", es hängt wahrscheinlich von Umsetzung, aber in der Regel Sie würden die statische Typprüfung verlieren und können dann einige Fehler sehr schwierig zu debuggen machen. Beachten Sie, dass die Methoden des Aufrufobjekts bereits ein nachrichtenähnliches System ist.

Vermutlich fehlen Ihnen einfach Abstraktionsniveaus, zum Beispiel könnte ein Spieler für die Navigation einen Navigator verwenden, anstatt alles über die Karte selbst zu wissen. Sie sagen auch, dass this has usually descended into setting lots of pointers, was sind diese Zeiger? Vermutlich geben Sie sie einer falschen Abstraktion hin .... Objekte direkt über andere zu informieren, ohne Schnittstellen und Zwischenprodukte zu durchlaufen, ist ein direkter Weg zu einem eng gekoppelten Design.

+0

Ja, ich habe sie direkt zugewiesen, was wohl mein Problem ist. –

0

Messaging ist definitiv ein guter Weg zu gehen, aber Messaging-Systeme können eine Menge Unterschiede haben. Wenn Sie Ihre Klassen sauber und schön halten möchten, schreiben Sie ihnen, dass sie ein Messaging-System ignorieren und stattdessen Abhängigkeiten von etwas Einfachem wie einem 'ILocationService' übernehmen, das dann implementiert werden kann, um Informationen wie die Map-Klasse zu veröffentlichen/anzufordern . Während Sie mit mehr Klassen enden werden, sind sie klein, einfach und fördern sauberes Design.

Messaging bedeutet mehr als nur die Entkopplung, es ermöglicht Ihnen auch eine asynchrere, gleichzeitige und reaktive Architektur. Patterns of Enterprise Integration von Gregor Hophe ist ein großartiges Buch, das über gute Messaging-Muster spricht. Erlang OTP oder Scalas Implementierung des Actor Patterns haben mir eine Menge Anleitung gegeben.

3

Hier ist ein nettes Ereignissystem geschrieben für C++ 11, das Sie verwenden können. Es verwendet Vorlagen und intelligente Zeiger sowie Lambdas für die Delegierten. Es ist sehr flexibel. Im Folgenden finden Sie auch ein Beispiel. Mailen Sie mich an [email protected], wenn Sie Fragen dazu haben.

Mit diesen Klassen können Sie Ereignisse mit beliebigen an sie angehängten Daten senden und auf einfache Weise Funktionen binden, die bereits konvertierte Argumenttypen akzeptieren, die das System vor dem Aufruf Ihres Stellvertreters konvertiert und auf korrekte Konvertierung überprüft.

Grundsätzlich wird jedes Ereignis von der IEventData-Klasse abgeleitet (Sie können es IEvent nennen, wenn Sie möchten). Jeder "Frame", den Sie ProcessEvents() aufrufen, durchläuft zu diesem Zeitpunkt das Ereignissystem durch alle Delegaten und ruft die Stellvertreter auf, die von anderen Systemen bereitgestellt wurden, die jeden Ereignistyp abonniert haben. Jeder kann auswählen, welche Ereignisse er abonnieren möchte, da jeder Ereignistyp eine eindeutige ID hat. Sie können auch lambdas verwenden, um Veranstaltungen wie diese abonnieren: AddListener (MyEvent :: ID(), [&] (Shared_ptr ev) { mach dein Ding} ..

Wie auch immer, hier ist die Klasse mit all der Umsetzung :

#pragma once 

#include <list> 
#include <memory> 
#include <map> 
#include <vector> 
#include <functional> 

class IEventData { 
public: 
    typedef size_t id_t; 
    virtual id_t GetID() = 0; 
}; 

typedef std::shared_ptr<IEventData> IEventDataPtr; 
typedef std::function<void(IEventDataPtr&)> EventDelegate; 

class IEventManager { 
public: 
    virtual bool AddListener(IEventData::id_t id, EventDelegate proc) = 0; 
    virtual bool RemoveListener(IEventData::id_t id, EventDelegate proc) = 0; 
    virtual void QueueEvent(IEventDataPtr ev) = 0; 
    virtual void ProcessEvents() = 0; 
}; 


#define DECLARE_EVENT(type) \ 
    static IEventData::id_t ID(){ \ 
     return reinterpret_cast<IEventData::id_t>(&ID); \ 
    } \ 
    IEventData::id_t GetID() override { \ 
     return ID(); \ 
    }\ 

class EventManager : public IEventManager { 
public: 
    typedef std::list<EventDelegate> EventDelegateList; 

    ~EventManager(){ 
    } 
    //! Adds a listener to the event. The listener should invalidate itself when it needs to be removed. 
    virtual bool AddListener(IEventData::id_t id, EventDelegate proc) override; 

    //! Removes the specified delegate from the list 
    virtual bool RemoveListener(IEventData::id_t id, EventDelegate proc) override; 

    //! Queues an event to be processed during the next update 
    virtual void QueueEvent(IEventDataPtr ev) override; 

    //! Processes all events 
    virtual void ProcessEvents() override; 
private: 
    std::list<std::shared_ptr<IEventData>> mEventQueue; 
    std::map<IEventData::id_t, EventDelegateList> mEventListeners; 

}; 

//! Helper class that automatically handles removal of individual event listeners registered using OnEvent() member function upon destruction of an object derived from this class. 
class EventListener { 
public: 
    //! Template function that also converts the event into the right data type before calling the event listener. 
    template<class T> 
    bool OnEvent(std::function<void(std::shared_ptr<T>)> proc){ 
     return OnEvent(T::ID(), [&, proc](IEventDataPtr data){ 
      auto ev = std::dynamic_pointer_cast<T>(data); 
      if(ev) proc(ev); 
     }); 
    } 
protected: 
    typedef std::pair<IEventData::id_t, EventDelegate> _EvPair; 
    EventListener(std::weak_ptr<IEventManager> mgr):_els_mEventManager(mgr){ 

    } 
    virtual ~EventListener(){ 
     if(_els_mEventManager.expired()) return; 
     auto em = _els_mEventManager.lock(); 
     for(auto i : _els_mLocalEvents){ 
      em->RemoveListener(i.first, i.second); 
     } 
    } 

    bool OnEvent(IEventData::id_t id, EventDelegate proc){ 
     if(_els_mEventManager.expired()) return false; 
     auto em = _els_mEventManager.lock(); 
     if(em->AddListener(id, proc)){ 
      _els_mLocalEvents.push_back(_EvPair(id, proc)); 
     } 
    } 
private: 
    std::weak_ptr<IEventManager> _els_mEventManager; 
    std::vector<_EvPair>  _els_mLocalEvents; 
    //std::vector<_DynEvPair> mDynamicLocalEvents; 
}; 

Und die CPP-Datei:.

#include "Events.hpp" 

using namespace std; 

bool EventManager::AddListener(IEventData::id_t id, EventDelegate proc){ 
    auto i = mEventListeners.find(id); 
    if(i == mEventListeners.end()){ 
     mEventListeners[id] = list<EventDelegate>(); 
    } 
    auto &list = mEventListeners[id]; 
    for(auto i = list.begin(); i != list.end(); i++){ 
     EventDelegate &func = *i; 
     if(func.target<EventDelegate>() == proc.target<EventDelegate>()) 
      return false; 
    } 
    list.push_back(proc); 
} 

bool EventManager::RemoveListener(IEventData::id_t id, EventDelegate proc){ 
    auto j = mEventListeners.find(id); 
    if(j == mEventListeners.end()) return false; 
    auto &list = j->second; 
    for(auto i = list.begin(); i != list.end(); ++i){ 
     EventDelegate &func = *i; 
     if(func.target<EventDelegate>() == proc.target<EventDelegate>()) { 
      list.erase(i); 
      return true; 
     } 
    } 
    return false; 
} 

void EventManager::QueueEvent(IEventDataPtr ev) { 
    mEventQueue.push_back(ev); 
} 

void EventManager::ProcessEvents(){ 
    size_t count = mEventQueue.size(); 
    for(auto it = mEventQueue.begin(); it != mEventQueue.end(); ++it){ 
     printf("Processing event..\n"); 
     if(!count) break; 
     auto &i = *it; 
     auto listeners = mEventListeners.find(i->GetID()); 
     if(listeners != mEventListeners.end()){ 
      // Call listeners 
      for(auto l : listeners->second){ 
       l(i); 
      } 
     } 
     // remove event 
     it = mEventQueue.erase(it); 
     count--; 
    } 
} 

ich eine Eventlistener Klasse aus Gründen der Bequemlichkeit als Basisklasse für jede Klasse, die auf Ereignisse hören möchte Wenn Sie Ihre hörende Klasse ableiten aus dieser Klasse und liefern es w Mit Ihrem Event Manager können Sie Ihre Ereignisse mit der sehr komfortablen Funktion OnEvent (..) registrieren. Und die Basisklasse wird die abgeleitete Klasse automatisch von allen Ereignissen abbestellen, wenn sie zerstört wird. Dies ist sehr praktisch, da das Vergessen eines Delegaten aus dem Ereignismanager bei der Zerstörung Ihrer Klasse fast sicher zum Absturz Ihres Programms führt.

Eine nette Möglichkeit, eine eindeutige Typ-ID für ein Ereignis zu erhalten, indem Sie einfach eine statische Funktion in der Klasse deklarieren und dann ihre Adresse in einen int-Wert umwandeln. Da jede Klasse diese Methode für verschiedene Adressen verwendet, kann sie zur eindeutigen Identifizierung von Klassenereignissen verwendet werden. Sie können typename() auch in einen int umwandeln, um eine eindeutige ID zu erhalten. Es gibt verschiedene Möglichkeiten, dies zu tun.

So, hier ist ein Beispiel dafür, wie diese nutzen:

#include <functional> 
#include <memory> 
#include <stdio.h> 
#include <list> 
#include <map> 

#include "Events.hpp" 
#include "Events.cpp" 

using namespace std; 

class DisplayTextEvent : public IEventData { 
public: 
    DECLARE_EVENT(DisplayTextEvent); 

    DisplayTextEvent(const string &text){ 
     mStr = text; 
    } 
    ~DisplayTextEvent(){ 
     printf("Deleted event data\n"); 
    } 
    const string &GetText(){ 
     return mStr; 
    } 
private: 
    string mStr; 
}; 

class Emitter { 
public: 
    Emitter(shared_ptr<IEventManager> em){ 
     mEmgr = em; 
    } 
    void EmitEvent(){ 
     mEmgr->QueueEvent(shared_ptr<IEventData>(
      new DisplayTextEvent("Hello World!"))); 
    } 
private: 
    shared_ptr<IEventManager> mEmgr; 
}; 

class Receiver : public EventListener{ 
public: 
    Receiver(shared_ptr<IEventManager> em) : EventListener(em){ 
     mEmgr = em; 

     OnEvent<DisplayTextEvent>([&](shared_ptr<DisplayTextEvent> data){ 
      printf("It's working: %s\n", data->GetText().c_str()); 
     }); 
    } 
    ~Receiver(){ 
     mEmgr->RemoveListener(DisplayTextEvent::ID(), std::bind(&Receiver::OnExampleEvent, this, placeholders::_1)); 
    } 
    void OnExampleEvent(IEventDataPtr &data){ 
     auto ev = dynamic_pointer_cast<DisplayTextEvent>(data); 
     if(!ev) return; 
     printf("Received event: %s\n", ev->GetText().c_str()); 
    } 
private: 
    shared_ptr<IEventManager> mEmgr; 
}; 

int main(){ 
    auto emgr = shared_ptr<IEventManager>(new EventManager()); 


    Emitter emit(emgr); 
    { 
     Receiver receive(emgr); 

     emit.EmitEvent(); 
     emgr->ProcessEvents(); 
    } 
    emit.EmitEvent(); 
    emgr->ProcessEvents(); 
    emgr = 0; 

    return 0; 
}