SpecificEventDispatcher.h:
namespace inp {
// Description: SpecificEventDispatcherBase is the base class for all
// SpecificEventDispatcher classes.
class SpecificEventDispatcherBase {
public:
virtual ~SpecificEventDispatcherBase() {}
virtual void UnregisterByID(uint32_t id) = 0;
virtual void UnregisterByObject(void* obj) = 0;
};
// Description: SpecificEventDispatcher is used to dispatch a specific type of
// event.
template<typename EventT>
class SpecificEventDispatcher : public SpecificEventDispatcherBase {
public:
// public functions --------------------------------------------------------
// register listeners: Registers a function or member function and returns
// a unique ID that can be used to unregister later.
uint32_t Register(int (*func)(const EventT&));
template<typename ClassT>
uint32_t Register(int (ClassT::*func)(const EventT&), ClassT* obj);
// unregister listeners
void UnregisterByID(uint32_t id);
void UnregisterByObject(void* obj);
// dispatch event
void Dispatch(const EventT& event);
private:
// types -------------------------------------------------------------------
struct Callback {
// typedefs
typedef std::function<int(const EventT&)> Function;
// ctor
Callback(Function func, void* obj) : function(func), object(obj) {
static uint32_t static_id = 0;
id = static_id;
++static_id;
}
// vars
Function function;
void* object; // I really don't like this reliance on void pointers...
uint32_t id;
};
// vars --------------------------------------------------------------------
std::vector<Callback> listeners_;
};
// public functions ------------------------------------------------------------
// register listeners
template<typename EventT>
uint32_t SpecificEventDispatcher<EventT>::Register(int (*func)(const EventT&)) {
Callback callback(func, NULL);
listeners_.push_back(callback);
return callback.id;
}
template<typename EventT> template<typename ClassT>
uint32_t SpecificEventDispatcher<EventT>::Register(int (ClassT::*func)(const EventT&), ClassT* obj) {
Callback callback(std::bind(func, obj, std::placeholders::_1), obj);
listeners_.push_back(callback);
return callback.id;
}
// unregister listeners
template<typename EventT>
void SpecificEventDispatcher<EventT>::UnregisterByID(uint32_t id) {
for (auto itr = listeners_.begin(); itr != listeners_.end(); ++itr) {
if (itr->id == id) {
listeners_.erase(itr);
return;
}
}
}
template<typename EventT>
void SpecificEventDispatcher<EventT>::UnregisterByObject(void* obj) {
for (auto itr = listeners_.begin(); itr != listeners_.end(); ++itr) {
if (itr->object == obj) {
listeners_.erase(itr);
return;
}
}
}
// dispatch event
template<typename EventT>
void SpecificEventDispatcher<EventT>::Dispatch(const EventT& event) {
for (auto itr = listeners_.begin(); itr != listeners_.end(); ++itr) {
itr->function(event);
}
}
} // namespace inp
EventDispatcher.h:
namespace inp {
// Description: EventDispatcher is used to dispatch a variety of event types to
// a variety of listeners.
class EventDispatcher {
public:
// ctors -------------------------------------------------------------------
EventDispatcher();
// public functions --------------------------------------------------------
// register listeners: Registers a function or member function and returns
// a unique ID that can be used to unregister later.
template<typename EventT>
uint32_t Register(int type, int (*func)(const EventT&));
template<typename ClassT, typename EventT>
uint32_t Register(int type, int (ClassT::*func)(const EventT&), ClassT* obj);
// unregister
void UnregisterByID(int type, uint32_t id);
void UnregisterByObject(int type, void* obj);
// dispatch events
template<typename EventT>
void Dispatch(int type, const EventT& event);
private:
// vars --------------------------------------------------------------------
std::map<int, SpecificEventDispatcherBase*> listeners_;
};
// public functions ------------------------------------------------------------
// register listeners
template<typename EventT>
uint32_t EventDispatcher::Register(int type, int (*func)(const EventT&)) {
// grab the iterator for the event id
auto itr = listeners_.find(type);
SpecificEventDispatcher<EventT>* sed = NULL;
if (itr == listeners_.end()) { // iterator not found
sed = new SpecificEventDispatcher<EventT>();
listeners_.insert(std::make_pair(type, sed));
} else { // iterator found
sed = static_cast<SpecificEventDispatcher<EventT>*>(itr->second);
}
// register the function
return sed->Register(func);
}
template<typename ClassT, typename EventT>
uint32_t EventDispatcher::Register(int type, int (ClassT::*func)(const EventT&), ClassT* obj) {
if (obj == NULL) { return NULL; } // disallow NULL objects
// grab the iterator for the event id
auto itr = listeners_.find(type);
SpecificEventDispatcher<EventT>* sed = NULL;
if (itr == listeners_.end()) { // iterator not found
sed = new SpecificEventDispatcher<EventT>();
listeners_.insert(std::make_pair(type, sed));
} else { // iterator found
sed = static_cast<SpecificEventDispatcher<EventT>*>(itr->second);
}
// register the function
return sed->Register(func, obj);
}
// dispatch events
template<typename EventT>
void EventDispatcher::Dispatch(int type, const EventT& event) {
// grab the iterator for the event id
auto itr = listeners_.find(type);
if (itr != listeners_.end()) { // iterator found
static_cast<SpecificEventDispatcher<EventT>*>(itr->second)->Dispatch(event);
}
}
} // namespace inp
EventDispatcher.cpp:
namespace inp {
// ctors -----------------------------------------------------------------------
EventDispatcher::EventDispatcher() {
// initializes the map to prevent "breaks strict-aliasing" warning
listeners_[-1] = NULL;
}
// public functions ------------------------------------------------------------
// unregister
void EventDispatcher::UnregisterByID(int type, uint32_t id) {
// grab the iterator for the event id
auto itr = listeners_.find(type);
// if the iterator was found, unregister
if (itr != listeners_.end()) { itr->second->UnregisterByID(id); }
}
void EventDispatcher::UnregisterByObject(int type, void* obj) {
if (obj == NULL) { return; } // disallow NULL objects
// grab the iterator for the event id
auto itr = listeners_.find(type);
// if the iterator was found, unregister
if (itr != listeners_.end()) { itr->second->UnregisterByObject(obj); }
}
} // namespace inp
Now, what I would like for this system is for registered objects to unregister themselves when their destructor is called, but in order to do this I would need to store a pointer to each dispatcher it was registered to, then loop through and unregister the object. And while that is simple enough, it creates a dependency to the dispatchers that I would prefer not to have. And if a dispatcher an object is registered to is deleted, destroying that object would cause the program to crash. So, my solution was to create an interface, IEventListener, that had 2 methods: OnRegisteredToDispatcher and OnUnregisteredFromDispatcher. With this, the dispatcher could call OnUnregisteredFromDispatcher when it was deleted, informing the object it was no longer registered. This also allowed me to remove the reliance on void pointers for storing the object for comparison (which I suppose could have also been avoided by removing the ability to unregister by object). But here, the issue becomes the actual implementation of IEventListener methods by the objects inheriting from it (and it implies a relationship between classes where one does not necessarily exist, but I'm not too worried about that). What I mean by this is that all classes inheriting from IEventListener will basically have the same code regarding the two methods:
class Listener : public IEventListener {
public:
virtual ~Listener() {
for (size_t x = 0; x < dispatchers_.size(); ++x) {
dispatchers_[x]->UnregisterByObject(this);
}
}
void OnRegisteredToDispatcher(EventDispatcher* d) {
dispatchers_.push_back(d);
}
void OnUnregisteredFromDispatcher(EventDispatcher* d) {
for (size_t x = 0; x < dispatchers_.size(); ++x) {
if (dispatchers_[x] == d) {
dispatchers_.erase(dispatchers_.begin() + x);
}
}
}
private:
std::vector<EventDispatcher*> dispatchers_;
}
With that in mind, I would want to have all my listeners inherit from that class instead, since all listeners should implement the interface's methods like that anyway. But then the problem with that is any class that already inherits from another class would have to rely on multiple implementation inheritance, which I am not too fond of.
So, should I just have all the listeners inherit from a class similar to Listener above and not worry about multiple implementation inheritance? Or is there another way to accomplish the same thing that I'm not thinking of? Or should I use smart pointers in the Callback class to check for the deleted objects (not ideal afaik; adds the overhead of checking the object associated with the callback each pass of the dispatch method's loop)?
Requirements for the system:
- Objects that register member functions should unregister themselves upon destruction
- If objects are required to be aware of dispatchers they are registered to, the object should know when one of those dispatchers has been destroyed
- Performance
Desired attributes for the system:
- Lessened dependence on void pointers
- Avoidance of multiple implementation inheritance
- Loosely coupled objects
Thanks in advance.