Event Bus and Observer Pattern in C++: Implementation in Modern C++

Online Python Trainer for Beginners

Learn Python easily without overwhelming theory. Solve practical tasks with automatic checking, get hints in Russian, and write code directly in your browser — no installation required.

Start Course

Event Bus and Observer Pattern in C++: From Theory to Modern Implementation



In modern C++ development, there is an increasing need for a flexible message exchange mechanism between components that should not know about each other directly. The Observer pattern and its evolution — Event Bus — solve this problem by allowing you to build loosely coupled, extensible systems. In this article, we will analyze how to implement an Event Bus in C++ using the standard library (C++17/20), and look at practical examples.



Observer Pattern: The Classic Foundation



The Observer pattern defines a "one-to-many" relationship between objects: when one object (the subject) changes its state, all dependent objects (observers) are automatically notified. In C++, the classic implementation looks like this:



#include <iostream>#include <vector>#include <memory>#include <algorithm>

// Base class for observerclass IObserver {public: virtual ~IObserver() = default; virtual void update(const std::string& message) = 0;};

// Subject (publisher)class Subject {private: std::vector<std::weak_ptr<IObserver>> observers_;public: void attach(std::shared_ptr<IObserver> observer) { observers_.push_back(observer); }

void detach(std::shared_ptr<IObserver> observer) { observers_.erase( std::remove_if(observers_.begin(), observers_.end(), [&observer](const std::weak_ptr<IObserver>& wp) { auto sp = wp.lock(); return !sp || sp == observer; }), observers_.end() ); }

void notify(const std::string& message) { for (auto it = observers_.begin(); it != observers_.end(); ) { if (auto sp = it->lock()) { sp->update(message); ++it; } else { it = observers_.erase(it); // remove "dead" references } } }};

// Concrete observerclass ConcreteObserver : public IObserver {private: std::string name_;public: ConcreteObserver(const std::string& name) : name_(name) {} void update(const std::string& message) override { std::cout << "[" << name_ << "] received: " << message << std::endl; }};

int main() { auto subject = std::make_shared<Subject>(); auto obs1 = std::make_shared<ConcreteObserver>("Observer 1"); auto obs2 = std::make_shared<ConcreteObserver>("Observer 2");

subject->attach(obs1); subject->attach(obs2); subject->notify("Event occurred!");

subject->detach(obs1); subject->notify("Only for the second");

return 0;}


This code works, but it has limitations: all observers must inherit IObserver, and messages must be strings. In practice, a more flexible system is needed.



Event Bus: Modern Evolution



Event Bus is a centralized bus through which components can publish events and subscribe to them. Key differences from the classic Observer:

  • Typed events — each event has its own data type.
  • No rigid hierarchy — subscribers do not have to inherit a common interface.
  • Global or local bus — you can have one Event Bus for the entire application or several isolated ones.


Implementing Event Bus in C++17/20 with std::any



We use std::any to store data of any type and std::function for handlers. To separate events by type, we apply std::type_index.



#include <iostream>#include <any>#include <function                    

Recommendations