I've implemented an events system in C++11. I've now got it pretty much as good as I can get it. It feels like I have found an optimal design pattern, and I'm using sensible variable names to illustrate what is going on.
Still not sure what the best words are: e.g. Invoker, Dispatcher, or Notifier? Event.fire() or .publish() or .trigger()? Reciever, Listener, or Subscriber? etc...
As is a fairly involved machinery, I've taken trouble to document. I would be very grateful if anyone can offer more concise or improved documentation, as I find that my ability to document something starts to decrease once I get close to fully understanding it, as I forget the sequence of key observations necessary to grok the structure. What would you modify if you were to assimilate this into your code base?
// C++11 Event System
// π 06.03.2016
// 5th revision
#include <vector>
#include <iostream>
#include <algorithm>
#include <memory>
#include <string>
using namespace std;
/*
Usage:
Event<int> event;
class Reciever : EventManager<Reciever> {
void handler(int k) {...}
reciever.connect(event, &Reciever::handler);
event.fire(42);
Internals:
Terminology
An Invoker is a machine for invoking a particular member function of a particular instance of a particular object type.
e.g.
class T { void f(int i) {cout<<i;} }; T t; I = Invoker(&t, &T::t); I(42);
We have two principal objects:
Event<Args…> needs to hold a list of Invokers (as InvokerBase*), such that when the event fires,
every connected invoker does its invoke().
EventManager<T> needs to hold a list of Event* (as EventBase*) so that when a listener is destroyed,
all events connecting into it are told to remove all relevant invokers.
We have to use a InvokerBase base class as invokers of potentially of different types
We have to use an EventBase base class as events are potentially of different types
Details:
Event<Args…> contains a list of Invoker<T>-s, each invoker holding a T* t and a void(T::f*)(Args…).
Event<Args…> also contains a void fire(Args… args), which calls t->f(…args) on every invoker.
Note that two invokers may have different T-s. So how to store a list of such?
Answer: make them all inherit from a common InvokerBase<Args…> which contains a virtual call(Args… args)
So why do we need an EventManager? Can't we just have an Event class that does the above?
What happens if a receiver is destroyed? Next time the event fires it will attempt to invoke
a method on an instance that no longer exists. UB hazard alert!
We could provide Event with a removeInvoker(void* t) method, and require the consumer to invoke
this from its destructor. But it would be nicer & safer to have this happen automatically.
So our listener class T will derive from EventManager<T>, and in EventManager's destructor
we can call removeInvoker(this) for every event that T has been connected to.
In order to do this, EventManager is obviously going to need to maintain a list of such events.
And again, we have the same problem of holding on a list of different events, maybe Event<float>, Event<int,bool>, etc.
So again we will have to make an EventManagerBase, containing a virtual removeInvoker(void*) which EventManager will override.
Luckily we can cast to a void* — given a particular foo instance, we can request an event to remove every invoker that points to this object.
*/
// [1c] Each invoker is for a different object type, so how to store them in a list?
// Answer: Make a base class!
template<class... Args>
class InvokerBase {
public:
virtual void call(Args... args) = 0;
virtual void* getInstance() = 0;
virtual ~InvokerBase() { /* derived dtor executes before hitting here */ } // deleting base object invokes derived dtor
};
// [1b] A invoker has to invoke a method on an instance of some object, and hence needs:
// - a pointer to the object,
// - a pointer to the member function that needs to be invoked
// When it fires, each is invoked.
template<class T, class... Args>
class Invoker : public InvokerBase<Args...> {
private:
T* t;
void(T::*f)(Args...);
public:
Invoker(T* t, void(T::*f)(Args...)) : t(t), f(f) { }
void call(Args... args) final { (t->*f)(args...); }
void* getInstance() final { return (void*)t; }
~Invoker() final { cout << "~Invoker() hit! \n"; }
};
class EventManagerBase {
public:
virtual void removeEvent(void* ev) = 0;
};
// [2c] As these events will have different sigs, again we need the same base-class trick.
class EventBase {
public:
virtual void removeInvoker(void* t) = 0;
};
template<class... Args>
class Event : public EventBase {
private:
// Note: When the instance is destroyed, or items removed from vector, delete is called relevant element(s) thanks to unique_ptr
using InvokerBase_smartPointer = unique_ptr<InvokerBase<Args...>>;
// [1a] An event holds a list of invokers.
std::vector<InvokerBase_smartPointer> invokers;
public:
// [5] boum!
void fire(Args... args) {
for (auto& i : invokers)
i->call(args...);
}
template<class T>
void addInvoker(T* t, void(T::*f)(Args... args)) {
auto i = new Invoker <T, Args...>(t, f);
invokers.push_back(InvokerBase_smartPointer(i));
}
void removeInvoker(void* t) final {
auto to_remove = std::remove_if(
invokers.begin(),
invokers.end(),
[t](auto& i) { return i->getInstance() == t; }
);
invokers.erase(to_remove, invokers.end()); // yup std::remove_if doesn't actually do the removing (gah)
}
// [3] ok, So we have the EventManager cleaning up after itself. Now let's get Event to also do that.
~Event() {
for (auto& i : invokers)
((EventManagerBase*)(i->getInstance())) -> removeEvent(this);
}
};
// [2a] The consumer's Reciever must derive from EventManager<Reciever>
template<class Derived>
class EventManager : public EventManagerBase {
private:
// [2b] ... which holds a list of all events holding a subscription to us...
std::vector<EventBase*> events;
public:
// [4] This is the heart of the operation.
// Simultaneously add listener to event's invokers while adding event to the listeners event-list.
template<class... Args>
void connect(Event<Args...>& ev, void(Derived::*listenerMethod)(Args... args)) {
ev.addInvoker((Derived*)this, listenerMethod); // [1a]
events.push_back(&ev);
}
void removeEvent(void* ev) final {
auto to_remove = std::remove_if(
events.begin(),
events.end(),
[ev](auto& elt) { return elt==(EventBase*)ev; }
);
events.erase(to_remove, events.end()); // yup std::remove_if doesn't actually do the removing (gah)
}
// [2d] ...so that when the reciever dies, we notify each event to remove it's subscription.
~EventManager() {
for (auto& e : events)
e->removeInvoker((void*)this);
}
};
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// [0] Example usage:
class Sender {
private:
string name;
public:
Event<float> eventFloat;
Event<bool, int> eventB; // etc.
Sender(string s) : name(s) { }
void triggerEvent() {
cout << name << "::triggerEvent() ~ Firing event with: 42\n";
eventFloat.fire(42.0f);
}
};
struct Reciever : EventManager<Reciever> {
string name;
Reciever(string s) : name(s) { cout << s << "()\n"; }
~Reciever() { cout << "~" << name << "()\n"; }
void gotEvent(float x) { cout << name << "::gotEvent hit with value: " << x << endl; }
};
int main() {
// Event source and listener unaware of each other's existence.
Sender sender("sender");
Reciever reciever1("reciever1");
reciever1.connect(sender.eventFloat, &Reciever::gotEvent);
{
Reciever reciever2("reciever2");
reciever2.connect(sender.eventFloat, &Reciever::gotEvent);
sender.triggerEvent(); // get 2 hits
}
sender.triggerEvent(); // get 1 hit
return 0;
}