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 Also in library
Testing in Java: Frameworks and Tools (JUnit, TestNG, Mockito)
Task Queues in C# (.NET): System.Threading.Channels, Hangfire, and RabbitMQ
Wolverine for C# (.NET): Complete Guide with Examples
NestJS for JavaScript (Node.js): Complete Guide with Examples
Authorization and Authentication in Java: Spring Security and JWT