Reputation: 63
I'm implementing an event system for a game. It uses an event queue, and a data structure to hold all registered event handlers for a given event type. It works fine so far registering handlers, but when it comes to unregistering them (something that will take place when a game object is destroyed, for instance) I'm having a bit of trouble regarding templates and casting.
I've defined an EventHandler as some sort of functor, partially based on Szymon Gatner's article on http://www.gamedev.net/reference/programming/features/effeventcpp/ . To be precise, I took the HandlerFunctionBase and MemberFunctionHandler class definitions and came up with:
class BaseEventHandler
{
public:
virtual ~BaseEventHandler(){}
void handleEvent(const EventPtr evt)
{
invoke(evt);
}
private:
virtual void invoke(const EventPtr evt)=0;
};
template <class T, class TEvent>
class EventHandler: public BaseEventHandler
{
public:
typedef void (T::*TMemberFunction)(boost::shared_ptr<TEvent>);
typedef boost::shared_ptr<T> TPtr;
typedef boost::shared_ptr<TEvent> TEventPtr;
EventHandler(TPtr instance, TMemberFunction memFn) : mInstance(instance), mCallback(memFn) {}
void invoke(const EventPtr evt)
{
(mInstance.get()->*mCallback)(boost::dynamic_pointer_cast<TEvent>(evt));
}
TPtr getInstance() const{return mInstance;}
TMemberFunction getCallback() const{return mCallback;}
private:
TPtr mInstance;
TMemberFunction mCallback;
};
Then the initial implementation for the unregisterHandler() method on the EventManager class I've thought of would go like this:
// EventHandlerPtr is a boost::shared_ptr<BaseEventHandler>.
// mEventHandlers is an STL map indexed by TEventType, where the values are a std::list<EventHandlerPtr>
void EventManager::unregisterHandler(EventHandlerPtr hdl,TEventType evtType)
{
if (!mEventHandlers.empty() && mEventHandlers.count(evtType))
{
mEventHandlers[evtType].remove(hdl);
//remove entry if there are no more handlers subscribed for the event type
if (mEventHandlers[evtType].size()==0)
mEventHandlers.erase(evtType);
}
}
To make "remove" work here I thought of overloading the == operator for BaseEventHandler, and then using a virtual method to perform the actual comparison...
bool BaseEventHandler::operator== (const BaseEventHandler& other) const
{
if (typeid(*this)!=typeid(other)) return false;
return equal(other);
}
and, on the template class EventHandler, implement the abstract method 'equal' like this:
bool equal(const BaseEventHandler& other) const
{
EventHandler<T,TEvent> derivedOther = static_cast<EventHandler<T,TEvent>>(other);
return derivedOther.getInstance() == this->getInstance() && derivedOther.getCallback()==this->getCallback();
}
Of course, I'm getting a compile error on the static_cast line. I'm not even sure that it is possible at all to do that cast (not necessarily using static_cast). Is there a way to perform it, or at least some workaround that does the trick?
Thanks in advance =)
Upvotes: 6
Views: 1951
Reputation: 63
Thank you both, Mark and Steve: that led me on the right direction. There was another problem since I was trying to cast a const to a non-const but this one was much easier to spot after that.
This is what I've got in the end, after a few tweaks:
void EventManager::unregisterHandler(EventHandlerPtr hdl,TEventType evtType)
{
if (!mEventHandlers.empty() && mEventHandlers.count(evtType))
{
TEventHandlerList::iterator newEnd=remove_if(mEventHandlers[evtType].begin(),
mEventHandlers[evtType].end(),EventHandlerComparisonFunctor(hdl));
mEventHandlers[evtType].erase(newEnd,mEventHandlers[evtType].end());
if (mEventHandlers[evtType].size()==0)
mEventHandlers.erase(evtType);
}
}
I changed remove with remove_if because boost::shared_ptr implements the == operator by directly comparing the pointers, instead of their contents. The terribly-named EventHandlerComparisonFunctor is now the responsible to perform the equality check.
It is implemented like this:
class EventHandlerComparisonFunctor
{
private:
EventHandlerPtr mInstance;
public:
EventHandlerComparisonFunctor(EventHandlerPtr instance):mInstance(instance){}
bool operator()(EventHandlerPtr& other) const
{
return *(mInstance.get())==*(other.get());
}
};
And finally, the equal method in EventHandler (@gf, the method was indeed declared in the EventHandler template, but for some reason I cut it out to paste the class code in here, my mistake)
bool equal(const BaseEventHandler& other) const
{
EventHandler<T,TEvent>& derivedOther = static_cast<EventHandler<T,TEvent>&>(const_cast<BaseEventHandler&>(other));
return derivedOther.getInstance() == this->getInstance() && derivedOther.getCallback()==this->getCallback();
}
Everything is working OK now.
Upvotes: 0
Reputation: 96241
In general when closing templates, you need to make sure that > are separated by spaces so the compiler doesn't parse them as a right-shift operator.
Here you're trying to static cast a reference to a non-reference, which even if it worked could invoke object slicing. You need to static cast to a derived reference.
bool equal(const BaseEventHandler& other) const
{
EventHandler<T,TEvent>& derivedOther = static_cast<EventHandler<T,TEvent>&>(other);
return derivedOther.getInstance() == this->getInstance() && derivedOther.getCallback()==this->getCallback();
}
Upvotes: 2