http://coliru.stacked-crooked.com/a/c74b2feda7987307
hm I didn't mean to use this thread as a VCS. Each post I thought would be the end of it, but the OCD!
This is the first complete version.
This is actually the first time I've managed to fit the whole structure in my head, such that I can perceive the whole machine.
This version is fully documented, including a walk-through: if you load the file in your IDE of choice and search for "// [" it will locate //[1] //[2] etc giving you an index so you can click through by number.
I think this is a very good way to document complex code, I'm surprised I've never seen it done anywhere else.
I suppose it could still do with unit tests, but I'm happy to leave it for the time being (not sure how long that'll last).
Any suggestions for improving the code or documentation much appreciated!
π
// C++11 Event System
// π 06.03.2016
// 4th revision
#include <vector>
#include <iostream>
#include <algorithm>
#include <memory>
#include <string>
using namespace std;
/*
Usage:
class Sender {
Event<int> event;
void fireEvent() { event.fire(42); }
class Reciever : EventListener<Reciever> {
void handler(int k) {...}
reciever.connect(mySender.event, &Reciever::handler);
We have two principal objects: Event and EventListener
Event contains a list of subscribers, each subscriber holding an instance pointer and member pointer.
reciever.connect(event, &Reciever::) adds {&reciever, &Reciever::handler} to event's subscriber list
The reason we are using an EventListener base class is because there is a danger: What 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!
It would be nice not to force the user to manually disconnect.
We can do the work in EventListener's destructor.
But in order to do this, EventListener is going to need to maintain a list of events that need to have their subscribers removed.
So, EventListener's connect also has to add the event to its own list.
Event list will have to be a vector<EventBase*>, and EventBase will have to contain a virtual removeSubscriber(void* inst)
*/
// [1c] Each subscriber is for a different object type, so how to store them in a list?
// Answer: Make a base class!
template<class... Args>
class SubscriberBase {
public:
virtual void call(Args... args) = 0;
virtual void* getInstance() = 0;
virtual ~SubscriberBase() { /* derived dtor executes before hitting here */ } // deleting base object invokes derived dtor
};
// [1b] A subscriber stores:
// - a pointer to the subscribing object,
// - a pointer to the member function that needs to be called when the event fires.
// When it fires, each is invoked.
template<class T, class... Args>
class Subscriber : public SubscriberBase<Args...> {
private:
T* t;
void(T::*f)(Args...);
public:
Subscriber(T* t, void(T::*f)(Args...)) : t(t), f(f) { }
void call(Args... args) final { (t->*f)(args...); }
void* getInstance() final { return (void*)t; }
~Subscriber() final { cout << "~Subscriber() hit! \n"; }
};
class EventListenerBase {
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 removeSubscriber(void* t) = 0;
};
template<class... Args>
class Event : public EventBase {
private:
// Note: When the instance is destroyed, delete is called on each list element thanks to unique_ptr
using SmartBasePointer = unique_ptr<SubscriberBase<Args...>>;
// [1a] An event holds a list of subscribers.
std::vector<SmartBasePointer> subscribers;
public:
// [5] boum!
void fire(Args... args) {
for (auto& s : subscribers)
s->call(args...);
}
template<class T>
void addSubscriber(T* t, void(T::*f)(Args... args)) {
auto s = new Subscriber <T, Args...>(t, f);
subscribers.push_back(SmartBasePointer(s));
}
void removeSubscriber(void* t) final {
auto to_remove = std::remove_if(
subscribers.begin(),
subscribers.end(),
[t](auto& elt) { return elt->getInstance() == t; }
);
subscribers.erase(to_remove, subscribers.end()); // yup std::remove_if doesn't actually do the removing (gah)
}
// [3] ok, So we have the EventListener cleaning up after itself. Now let's get Event to also do that.
~Event() {
for (auto& s : subscribers)
((EventListenerBase*)(s->getInstance()))->removeEvent(this);
}
};
// [2a] The consumer's Reciever must derive from EventListener<Reciever>
template<class Reciever>
class EventListener : public EventListenerBase {
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 subscribers while adding event to the listeners event-list.
template<class... Args>
void connect(Event<Args...>& ev, void(Reciever::*listenerMethod)(Args... args)) {
ev.addSubscriber((Reciever*)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.
~EventListener() {
for (auto& e : events)
e->removeSubscriber((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 : EventListener<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;
}