Forest of UNIX

Generic Observer framework in C++17

Sometime ago I wanted to use the Observer design pattern, to emit events from an event broker (subject) to multiple event handlers (observers). I wanted to implement different events as their own classes or structs. I wanted the event handlers to be able to handle multiple events of different types. I did not want the Events themselves to contain any information pertaining to the type of the class/struct (no enumeration indicating the type of the event). When the Event Handler would receive an Event, the correct method for that Event should be called via method overloading. To summarize I want to dispatch received Events (dynamically or statically) to the fitting method.

I also wanted my Observer framework to be a standalone module that used generics so it could be reusable for different types.

The Observer framework

For my observer framework I decided on the following design:

Generic Observer

If you are familiar with the observer design pattern than this is nothing really special. If you where to implement this UML class diagram in C++ (in a single header file). You would get the following:

#ifndef OBSERVER_HPP
#define OBSERVER_HPP

#include <list>
#include <memory>


/* PS: This is a header only implementation of the above class diagram.
 * If you are planning on using this for a personal project you should split it up,
 * into different header and source files, for the sake of cleanness.
 */
namespace observer {
// Forward Declarations:
template<typename T>
class Subject;

// Classes:
template<typename T>
class Observer {
  private:
  Subject<T>* m_subject;

  protected:
  auto subject() const -> Subject<T>*
  {
    return m_subject;
  }

  public:
  Observer(const Subject<T>* t_subject)
        : m_subject{t_subject}
  {}

  virtual auto notify(T&& t_notification) -> void = 0;

  virtual ~Observer() = default;
};

// Aliases:
template<typename T>
using ObserverPtr = std::shared_ptr<Observer<T>>;

// Classes:
template<typename T>
class Subject {
  private:
  std::list<ObserverPtr<T>> m_observers;

  public:
  auto subscribe(ObserverPtr<T> t_observer) -> void
  {
    m_observers.push_back(std::move(t_observer));
  }

  auto unsubscribe(ObserverPtr<T> t_observer) -> void
  {
    m_observers.remove(std::move(t_observer));
  }

  auto notify(T&& t_notification) -> void
  {
    for(auto& observer : m_observers) {
      observer->notify(std::forward<T>(t_notification));
    }
  }

  virtual ~Subject() = default;
};
} // namespace observer

#endif // OBSERVER_HPP

This implementation would work for multiple types. Furthermore since this framework has the added bonus of using templates meaning that this observer package is not be dependent on any other type or class, until you instantiate it. Which means this observer package will only have dependencies to it not from it (that is why I call this a framework).

Using dynamic dispatch

But what I am more interested in is using this Observer implementation in the following way:

Using dynamic dispatch with the observer framework

Here we instantiate the the observer framework on an Event pointer. The events are inherit from the Event class. Then in the event handlers we will use dynamic dispatch to call the correct method, to handle the Event.

We would implement EventHandler like this:

#ifndef EVENT_HANDLER_HPP
#define EVENT_HANDLER_HPP

#include "events.hpp"
#include "observer.hpp"


namespace handler {
// Aliases (This should really be defined somewhere in the events package):
using EventSubject = observer::Subject<events::Event*>;
using EventObserver = observer::Observer<events::Event*>;

// Classes:
class EventHandler : public EventObserver {
  protected:
  auto notify(events::Event* t_event) -> void override
  {
    notify(t_event);
  }

  public:
  EventHandler(EventSubject* t_subject)
  : EventObserver{t_subject}
  {}

  auto notify(events::SomeEvent* t_event) -> void
  {
    // Handle SomeEvent
  }

  auto notify(events::AnotherEvent* t_event) -> void
  {
    // Handle AnotherEvent
  }
};
} // namespace handler

#endif // EVENT_HANDLER_HPP

Except this does not work! The auto EventHandler::notify(Event* t_event) -> void method will recursively call itself, until the program crashes. This happens because the base class observer::Observer<T> does not have an overload for any of the other Event types.

In C++ when you use dynamic dispatch it first looks for viable candidates in the vtable of the base class. In this case it finds a viable candidate auto Observer<Event*>::notify(Event* t_event) -> void it then resolves to our overriden method of auto EventHandler::notify(Event* t_event) -> void. Causing it to recursively call itself over and over until it finally crashes.

You could solve this by using a dynamic_cast but this is not really a scalable solution.

Static dispatch to the rescue

So we cannot use dynamic dispatching to achieve this behavior. Luckily we can achieve the desired behavior by using std::variant. First lets go back to the drawing table and do a redesign:

Generic observer

You might notice that we have substituted the Event class with the EventVariant class. This EventVariant is not actually a class but an alias (UML specification does not include aliases):

namespace events {
// Aliases:
using EventVariant = std::variant<SomeEvent, AnotherEvent>;
} // namespace events

We could now reimplement the EventHandler class as the following:

#ifndef EVENT_HANDLER_HPP
#define EVENT_HANDLER_HPP

#include "events.hpp"
#include "observer.hpp"


namespace handler {
// Aliases (This should really be defined somewhere in the events package):
using EventSubject = observer::Subject<events::EventVariant>;
using EventObserver = observer::Observer<events::EventVariant>;

// Classes:
class EventHandler : public EventObserver {
  protected:
  auto notify(events::EventVariant t_event) -> void override
  {
    std::visit([this](auto&& t_event) {
      this->notify(t_event);
    } , t_event);
  }

  public:
  EventHandler(EventSubject* t_subject)
  : EventObserver{t_subject}
  {}

  auto notify(events::SomeEvent t_event) -> void
  {
    // Handle SomeEvent
  }

  auto notify(events::AnotherEvent t_event) -> void
  {
    // Handle AnotherEvent
  }
};
} // namespace handler

#endif // EVENT_HANDLER_HPP

And this will now work. std::visit will now properly resolve the type for us causing the proper method to be called, this keeps the code nice and clean.

Using the Observer

You could now use the observer:

#include "observer.hpp"
#include "events.hpp"


int main()
{
  using namespace events;
  using namespace observer;
  using namespace broker;
  using namespace handler;

  // Define the Subject and Observer
  EventBroker broker;
  auto handler{std::make_shared<EventHandler>(&broker)};

  // Subscribe the Event Handler to the broker
  broker.subscribe(handler);

  // Notify the event handler
  broker.notify(SomeEvent{});
  broker.notify(AnotherEvent{});
}

Lastly I use PlantUML to generate beautiful UML diagrams.

Not a perfect solution

While this solution works its not exactly perfect. If you payed attention than you would see that the EventVariant is now dependent on every event that you woul want to dispatch. Meaning that if you add an event you have to manually add your event class to the EventVariant alias. This is a minor price to pay for the convenience it offers