HiveBrain v1.2.0
Get Started
← Back to all entries
patterncppMinor

Event-listener implementation

Submitted by: @import:stackexchange-codereview··
0
Viewed 0 times
implementationlistenerevent

Problem

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 
#include 

template  struct event:public std::shared_ptr>>{

  using handler = std::function;
  using listener_list = std::list;

  struct listener{
    std::weak_ptr 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(std::make_shared()){ }
  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

using click_event = event;

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.ob

Solution

As I said in the comments, your system is pretty close to a system of signals and slots. Congratulations if you never heard of the pattern before, that's an excellent way to implement an the observer design pattern! I still have few notes:

-
I don't know which compiler you use, but mine won't compile your code unless I include ` for std::shared_ptr. I suppose that it may be transitively included from or in your implementation. Never rely on transitive includes from the standard library and always include the exact header that contains what you need.

-
Qt, which is the emblematic library when it comes to signals and slots, tends to use past participle for its signas names. For example, instead of
click, it would be clicked:

button.clicked.connect(/* whatever */);


It allows to read the line easily as "when button is clicked, do whatever". Also, it makes the object look more like a trigger and less like an action.

-
From a design point of view,
connect being a method taking only one function, I would expect it to add the function to the list, not to return a listener.

-
You should encapsulate
std::shared_ptr>> instead of inheriting from it. Frankly, you don't want people to expose every method of std::shared_ptr`. You don't want people to think of pointers when they want events, right?

The truth is my answer isn't really personal. Since your class obviously looks like a signal class, everything I am saying tends to mean "make your design closer to those of existing signal classes like Boost.Signals2" :/

Code Snippets

button.clicked.connect(/* whatever */);

Context

StackExchange Code Review Q#98564, answer score: 6

Revisions (0)

No revisions yet.