I am trying to implement a typesafe event bus. I am stuck with the EventBus::subscribe function because it does not accept my concrete event handler. In an earlier version I had my AbstractEventHandler implemented as an abstract class only, without it being a template. I had no problem with that implementation. That’s why I assume that the actual issue is with the abstract template.
The code below is a stripped down version of my implementation. The first block consist of the “skeleton” of the event bus and its required classes whereas the second block shows an actual implementation of the event, event handler and the main.
The enum holds all the different events available. The abstract event is the base of which all concrete events are derived of. The event handler is an abstract template with an event as template class to ensure type-safety. The event bus is responsible for distributing all published events to its respective handlers.
enum EVENT_TYPE
{
ON_EVENT_1,
ON_EVENT_2
};
class AbstractEvent
{
public:
AbstractEvent() {};
virtual ~AbstractEvent() {};
virtual EVENT_TYPE type() = 0;
};
template<class T>
class AbstractEventHandler
{
public:
AbstractEventHandler() {};
virtual ~AbstractEventHandler() {};
virtual void on_event(T *event) = 0;
};
class EventBus
{
public:
EventBus() {};
virtual ~EventBus() {};
void subscribe(EVENT_TYPE type,
AbstractEventHandler<AbstractEvent> *eventHandler) {
// Add handler to vector for further use
}
void publish(AbstractEvent *event) {
// send event to each handler in respective vector
}
};
Below are my concrete event and event handler and the main()
class ConcreteEvent : public AbstractEvent
{
public:
ConcreteEvent() {};
virtual ~ConcreteEvent() {};
EVENT_TYPE type() {
return ON_EVENT_1;
};
};
class ConcreteEventHandler : public AbstractEventHandler<ConcreteEvent>
{
public:
ConcreteEventHandler() {}
virtual ~ConcreteEventHandler() {};
void on_event(ConcreteEvent *event) {
// Do something
};
};
int main()
{
EventBus *eventBus = new EventBus();
ConcreteEventHandler handler = ConcreteEventHandler();
// This failes!
eventBus->subscribe(ON_EVENT_1, &handler);
}
The compiler returns with an error saying that there is no matching function for call to
EventBus::subscribe(EVENT_TYPE, ConcreteEventHandler*)
and that the only candidates are
void EventBus::subscribe(EVENT_TYPE, AbstractEventHandler<AbstractEvent>*)
How can I implement my EventBus::subscribe method to accept concrete implementations of my abstract class?
Update: Solution
I have changed the method description of EventBus::subscribe to the following and it now works nicely:
template<typename T>
void subscribe(EVENT_TYPE type, AbstractEventHandler<T> *eventHandler) {
}
Thanks, Rohan, for your hints! They helped me to find this solution.
The reason is because,
ConcreteEventHandleris a subclass ofAbstractEventHandler<ConcreteEvent>and notAbstractEventHandler<AbstractEvent>.This might seem surprising, but
AbstractEventHandler<ConcreteEvent>cannot be a subclass ofAbstractEventHandler<AbstractEvent>even thoughConcreteEventis a subclass ofAbstractEvent.The reason is because, with templates, templating as you wish does not guarantee type safety. Let us look at an example. Let’s go over the standard paradigm of a base-class
Animaland sub-classesCatandDog. Let’s say we have a list of Animals:and a list of cats:
The following, is NOT a valid cast:
The reason is, because, if I am to do this,
I would actually add a
Dogto a list ofCats.cats.last()here would actually return aDog. So, in this case, you are essentially adding aDogto a list ofCats. I’ve seen enough Looney Tunes episodes to know that this is not a good idea:The above is definitely not true, as we all know that a
Dogcan onlybowbow().EDIT
To answer your question, here is what I suggest you do; Let
ConcreteEventHandlerinherit fromAbstractEventHandler<AbstractEvent>, and within the code, wherever you use aConcreteEvent, use adynamic_caseto cast theAbstractEventto aConcreteEvent. This will use run-time introspection, which might impact performance a little (also I have seen quite a few people opposed to using a dynamic cast), but you will be able to successfully perform a valid upcast of the datatype.