While writing a C++ GUI application more or less from scratch I needed some form of an event-listener system, preferably using lambdas. An event should be able to have multiple listeners and the user should not have to worry about the lifetime of the objects. I came up with this bit using shared and weak pointers to determine object lifetime:
#include <functional>
#include <list>
template <typename ... Args> struct event:public std::shared_ptr<std::list<std::function<void(Args...)>>>{
using handler = std::function<void(Args...)>;
using listener_list = std::list<handler>;
struct listener{
std::weak_ptr<listener_list> the_event;
typename listener_list::iterator it;
listener(){ }
listener(event & s,handler f){
observe(s,f);
}
listener(listener &&other){
the_event = other.the_event;
it = other.it;
other.the_event.reset();
}
listener(const listener &other) = delete;
listener & operator=(const listener &other) = delete;
listener & operator=(listener &&other){
reset();
the_event = other.the_event;
it = other.it;
other.the_event.reset();
return *this;
}
void observe(event & s,handler f){
reset();
the_event = s;
it = s->insert(s->end(),f);
}
void reset(){
if(!the_event.expired()) the_event.lock()->erase(it);
the_event.reset();
}
~listener(){ reset(); }
};
event():std::shared_ptr<listener_list>(std::make_shared<listener_list>()){ }
event(const event &) = delete;
event & operator=(const event &) = delete;
void notify(Args... args){
for(auto &f:**this) f(args...);
}
listener connect(handler h){
return listener(*this,h);
}
};
Example usage:
#include <iostream>
using click_event = event<float,float>;
struct gui_element{
click_event click;
void mouse_down(float x,float y){ click.notify(x, y); }
};
int main(int argc, char **argv) {
gui_element A,B;
click_event::listener listener_1,listener_2;
listener_1.observe(A.click,[](float x,float y){ std::cout << "l1 : A was clicked at " << x << ", " << y << std::endl; });
listener_2.observe(B.click,[](float x,float y){ std::cout << "l2 : B was clicked at " << x << ", " << y << std::endl; });
{
auto temporary_listener = A.click.connect([](float x,float y){ std::cout << "tmp: A was clicked at " << x << ", " << y << std::endl; });
// A has two listeners, B has one listeners
A.mouse_down(1, 0);
B.mouse_down(0, 1);
}
listener_2 = std::move(listener_1);
// A has one listener, B has no listeners
A.mouse_down(2, 0);
B.mouse_down(0, 2);
}
Output:
l1 : A was clicked at 1, 0 tmp: A was clicked at 1, 0 l2 : B was clicked at 0, 1 l1 : A was clicked at 2, 0
While the usage is exactly how I wanted, I am not sure the implementation is elegant or optimal. Any way how to improve this?
eventintosignal,notifyintoemitandobserveintoconnect, it sems that what you've got are indeed signals :p \$\endgroup\$observesince it's the listener/observes which should "observe", not the event. Or at least I think so. \$\endgroup\$