Zdeněk
Zdeněk

Reputation: 468

C++ system for passing events with inheritance hierarchy

I am currently programming an event passing system for a game in C++ and I thought it would be useful if the events inherited from each other in a logical way.

This means I could for example raise an event of type NukeExplosion, which derives from Explosion (which would probably derive from an empty base class Event) and it would get passed to all listeners to an event of type NukeExplosion, as well as the more generic Explosion.

So far I was able to come up with two possible solutions:

I don't really like the second option, because it's error-prone, and requires me to write almost the same code in every event class.

The problem with the first option is that it might need to do a lot of dynamic_casts, and I would like to avoid that.

So, is there any other way which I haven't taken into accont, or is the first option the best I can do? Or should I completely drop the inheritance of events?

Upvotes: 1

Views: 1714

Answers (2)

Kerri Chandler
Kerri Chandler

Reputation: 54

I came here with almost exactly this question. The problem is basically that C++ won't let a function like handle (ExplosionEvent *e) accept an argument e with static type Event * even when the dynamic type of e is ExplosionEvent *. It would be a nice feature to have, but I'm not quite sure what else would have to change in the language.

The Visitor pattern is the cleanest solution I can think of. The drawbacks are that it's verbose and that it may not be cheaper than dynamic_cast<>.

Main.hpp:

#include <iostream>

class Event;
class Handler;

#include "Event.hpp"
#include "Handler.hpp"

Event.hpp:

#ifndef EVENT_H
#define EVENT_H

class Event 
{
public:
  virtual void accept (Handler *handler) { }
};

class ExplosionEvent : public Event 
{
  void accept (Handler *handler);
};

#endif // !EVENT_H

Event.cpp:

#include "Main.hpp"

void 
ExplosionEvent::accept (Handler *handler) 
{ 
  handler->handleExplosion (this);
}

Handler.hpp:

#ifndef HANDLER_H
#define HANDLER_H

class Handler
{
public:
  void handle (Event *event) { event->accept (this); }
  virtual void handleExplosion (ExplosionEvent *explosionEvent) { }
};

class ExplosionHandler : public Handler
{
  void handleExplosion (ExplosionEvent *explosionEvent);
};

#endif // !HANDLER_H

Handler.cpp:

#include "Main.hpp"

void
ExplosionHandler::handleExplosion (ExplosionEvent *explosionEvent)
{
  std::cout << "BOOM!" << std::endl;
}

Main.cpp:

#include "Main.hpp"

int
main (int argc, char *args)
{
  Event *event = new ExplosionEvent;
  Handler *handler = new ExplosionHandler;
  handler->handle (event);
}

Compile and run:

$ g++ -o boom *.cpp
$ ./boom 
BOOM!
$ 

Upvotes: 1

Eric Fortin
Eric Fortin

Reputation: 7603

Doing a dynamic cast for every listener will get really expensive for a large number of listeners so you probably will have to implement a map of typeid to listeners anyway.

My listener would have an HandleEvent which would take an Event object. This method would cast(with a static_cast) the base event to the event type it is expecting(it needs to trust the event dispatcher for the event registering mechanism).

I would also implement a method in the Event class which would return a new base event if valid because you might not want to send a base Event. This could be done with a macro but I have a feeling it could also be done with a template method although I haven't been able to make it work yet. The dispatcher would then get that base event before calling the event handlers and then call the handlers for the base event.

Upvotes: 0

Related Questions