Reputation: 468
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:
dynamic_cast
on the event for each set of listeners to the same event type. If it succeeds, I can then pass the event to all the listeners in the set.typeid
operator in conjunction with a map of listeners.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_cast
s, 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
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
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