I am currently working on a game and found myself in need of an event handler. I wrote an event handler similar to this one some time ago, but decided to update it using variadic templates (this is my first time using them). I really wanted something with the simple usage that C# events provide. I found some examples that emulate that usage very well, but were a bit more complex than I'd like.
If anyone has any tips for improving this header-only event handling system, or spots any glaring flaws, I would really appreciate it.
#ifndef _EVENT_HANDLER_H_
#define _EVENT_HANDLER_H_
#include <list>
namespace Utilities
{
template <typename... Args>
class Event;
///////////////////////////////////////////////////////////////////////////////////////////
// DelegateContainer
///////////////////////////////////////////////////////////////////////////////////////////
template <typename... Args>
class DelegateContainer
{
friend class Event<Args...>;
protected:
std::list<Event<Args...>*> _subscriptions;
public:
DelegateContainer() { }
DelegateContainer(const DelegateContainer& d)
{ _subscriptions.assign(d._subscriptions.begin(), d._subscriptions.end()); }
DelegateContainer(DelegateContainer&& d)
{
_subscriptions.assign(d._subscriptions.begin(), d._subscriptions.end());
d._subscriptions.clear();
}
~DelegateContainer();
DelegateContainer& operator=(const DelegateContainer& d)
{
_subscriptions.assign(d._subscriptions.begin(), d._subscriptions.end());
return *this;
}//operator=
DelegateContainer& operator=(DelegateContainer&& d)
{
_subscriptions.assign(d._subscriptions.begin(), d._subscriptions.end());
d._subscriptions.clear();
return *this;
}//operator=
virtual bool operator()(Args...) = 0;
};
///////////////////////////////////////////////////////////////////////////////////////////
// Delegate
///////////////////////////////////////////////////////////////////////////////////////////
template <typename T, typename... Args>
class Delegate final : public DelegateContainer<Args...>
{
private:
T* _t;
void (T::*_callback)(Args...);
public:
Delegate() : _t(0),
_callback(0)
{ }
Delegate(T* t, void(T::*callback)(Args...)) : _t(t),
_callback(callback)
{ }
Delegate(const Delegate& d) : _t(d._t),
_callback(d._callback)
{ }
Delegate(Delegate&& d) : _t(d._t),
_callback(d._callback)
{
_subscriptions.assign(d._subscriptions.begin(), d._subscriptions.end());
d._subscriptions.clear();
}
~Delegate() { }
Delegate& operator=(const Delegate& d)
{
_subscriptions.assign(d._subscriptions.begin(), d._subscriptions.end());
_callback = d._callback;
_t = d._t;
return *this;
}//operator=
Delegate& operator=(Delegate&& d)
{
_subscriptions.assign(d._subscriptions.begin(), d._subscriptions.end());
d._subscriptions.clear();
_callback = d._callback;
_t = d._t;
return *this;
}//operator=
public:
bool operator()(Args... args)
{
if (_t && _callback)
{
((*_t).*(_callback))(args...);
return true;
}
return false;
}//operator()
};
///////////////////////////////////////////////////////////////////////////////////////////
// Event
///////////////////////////////////////////////////////////////////////////////////////////
template <typename... Args>
class Event
{
private:
std::list<DelegateContainer<Args...>*> _delegates;
public:
Event() { }
Event(const Event& e)
{ _delegates.assign(e._delegates.begin(), e._delegates.end()); }
Event(Event&& e)
{
_delegates.assign(e._delegates.begin(), e._delegates.end());
e._delegates.clear();
}
~Event()
{
auto itr = _delegates.begin();
while(itr != _delegates.end())
(*itr++)->_subscriptions.remove(this);
}
Event& operator=(const Event& e)
{
_delegates.assign(e._delegates.begin(), e._delegates.end());
return *this;
}//operator=
Event& operator=(Event&& e)
{
_delegates.assign(e._delegates.begin(), e._delegates.end());
e._delegates.clear();
return *this;
}//operator=
Event& operator+=(DelegateContainer<Args...>& d)
{
d._subscriptions.push_back(this);
_delegates.push_back(&d);
return *this;
}//operator+=
Event& operator-=(DelegateContainer<Args...>& d)
{
d._subscriptions.remove(this);
_delegates.remove(&d);
return *this;
}//operator-=
void operator()(Args... args)
{
auto itr = _delegates.begin();
while(itr != _delegates.end())
if ((*itr)->operator()(args...))
itr++;
else
itr = _delegates.erase(itr);
}//operator()
};
///////////////////////////////////////////////////////////////////////////////////////////
// DelegateContainer destructor
///////////////////////////////////////////////////////////////////////////////////////////
template <typename... Args>
DelegateContainer<Args...>::~DelegateContainer()
{
auto itr = _subscriptions.begin();
while(itr != _subscriptions.end())
(*itr++)->operator-=(*this);
}
}//Utilities
#endif // _EVENT_HANDLER_H_
The DelgateContainer
is used as a base for Delegate
so the Event
can call Delegate
s without any knowledge of the object the Delegate
belongs to.
~DelegateContainer()
is defined at the end because it needs to be able to unsubscribe from any Event
s it is subscribed to before destruction.
Use case:
#include <iostream>
#include "EventHandler.h"
using namespace Utilities;
class Activator
{
public:
Event<int, char> _myEvent;
Activator() { }
void Update()
{
_myEvent(1, 'A');
std::cout << std::endl;
}//Update
};
class Listener
{
public:
int _i;
Delegate<Listener, int, char> _myDelegate;
Listener(int i) : _i(i)
{
_myDelegate = Delegate<Listener, int, char>(this, &Listener::DelegateMethod);
}
void DelegateMethod(int i, char c)
{
std::cout << ' ' << i * _i << ' ' << c << std::endl;
}//DelegateMethod
};
int main(int argc, char* argv[])
{
Activator activator;
Listener listener0 = Listener(0);
Listener listener1 = Listener(1);
Listener* listener2 = new Listener(2);
activator._myEvent += listener0._myDelegate;
activator._myEvent += listener1._myDelegate;
activator._myEvent += listener2->_myDelegate;
activator.Update();
activator._myEvent -= listener0._myDelegate;
activator.Update();
activator._myEvent += listener0._myDelegate;
activator.Update();
delete listener2;
activator.Update();
return 0;
}
The use case has the following output:
0 A 1 A 2 A 1 A 2 A 1 A 2 A 0 A 1 A 0 A