strandfield / events

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

events

Collection of experiments on "events" in C++.

What's in there ?

Basic implementation of the Observer design pattern

The folder examples/observable contains a small program featuring a basic implementation of the Observer design pattern.

For more information of this design pattern, see the Observer page on refactoring.guru.

A more generic implementation with a Publisher / Subscriber system

The pubsub.h header file defines two class templates, Publisher and Subscriber, that can be used to quickly implement an Observer design pattern.

Theses classes can help reduce code duplication when the design pattern is implemented at multiple locations in the code base. They also offer a more systematic lifecycle management with automatic unsubscribing when either the publisher or the subscriber is destroyed.

Definition of both a "publisher" and a "subscriber" class is facilitated.

Publisher:

class MyPublisher : public Publisher<MySubscriber>
{
public:
  void greets();
};

Subscriber:

class MySubscriber : public Subscriber<MyPublisher>
{
public:
  explicit MySubscriber(MyPublisher* pub = nullptr);

  virtual void sayHello() = 0;
};

Definition of greets():

void MyPublisher::greets()
{
  notify(&MySubscriber::sayHello);
}

Usage (assuming GermanSubscriber is derived from MySubscriber):

MyPublisher pub;
GermanSubscriber sub;
pub.addSubscriber(&sub);
pub.greets(); // prints "Guten tag!", probably

EventEmitter

An EventEmitter class inspired by Node.js class EventEmitter.

The idea here is to allow emitting and listening to events without prior event registration. While the above methods require the definition of various classes describing the sets of events that can happen, the EventEmitter can be used as-is and supports any event. The reduces the amount of code that needs to be written ! 😄

The class can be used both using inheritance or as a member of a class.

Example (as a member):

class Person
{
public:
  EventEmitter events;
private:
  std::string m_name = "John Doe";
public:

  const std::string& name() const { return m_name; }

  void setName(std::string n)
  {
    if (n != m_name)
    {
      m_name = std::move(n);
      nameChanged(m_name);
    }
  }

  void nameChanged(const std::string& name)
  {
    events.emit(&Person::nameChanged, name);
  }
};

Usage:

int main() {
  Person p;
  p.events.on(&Person::nameChanged, [](std::string n) {
    std::cout << "Hello " << n << "!" << std::endl;
  });
  p.setName("Homer Simpson");
}

The EventEmitter class supports partial use of the signal's parameters, so the following also works:

Person p;
p.on(&Person::nameChanged, [&p](/* std::string */) {
  std::cout << "Hello " << p.name() << "!" << std::endl;
});
p.setName("Homer Simpson");

Object

A class, based on EventEmitter, that provides a connection mechanism between signals and slots similar to Qt's QObject.

Users are meant to derive from Object and use connect() for connection a signal to a slot and emit() for emitting signals.

Using this class could be considered safer than using EventEmitter directly as:

  • the set of "events" ought to be restricted to the "signals" declared in the object class (reducing the potential for surprises);
  • disconnection is done automatically when either object destroyed (as opposed to only when the emitter is destroyed).

Example:

class Button : public Object
{
public:
  void clicked()
  {
    emit(&Button::clicked);
  }
};

class Dialog : public Object
{
private:
  bool m_visible = false;
public:
  bool visible() const { return m_visible; }
  void open() { m_visible = true; opened(); }
  void opened() { emit(&Dialog::opened); }
};

int main()
{
  Button button;
  Dialog dialog;
  Object::connect(&button, &Button::clicked, &dialog, &Dialog::open);
  Object::connect(&dialog, &Dialog::opened, []() {
    std::cout << "Dialog opened!" << std::endl;
  });
  button.clicked();
}

Limitations & disclaimers

Thread-safety: 🧶

Code is NOT thread-safe ; was designed to be used in a single-threaded environment.

Building the code

Requirements:

  • a compiler with C++17 support
  • CMake

Step-by-step build instructions:

Create a build directory:

mkdir build && cd build

Generate the project:

cmake ..

Build (linux):

make

Build (Windows):

cmake --build . --config Release --target ALL_BUILD

About

License:MIT License


Languages

Language:C++ 97.9%Language:CMake 2.1%