BehavioralC++verifiedVerified
Observer Pattern in C++
Defines a one-to-many dependency so that when one object changes state, all its dependents are notified and updated automatically.
How to Implement the Observer Pattern in C++
1Step 1: Define the event and callback types
using Callback = std::function<void(const std::string&)>;
class EventEmitter {
struct Listener {
int id;
std::string event;
Callback cb;
};
std::vector<Listener> listeners_;
int nextId_ = 0;
public:2Step 2: Subscribe and unsubscribe
int on(const std::string& event, Callback cb) {
int id = nextId_++;
listeners_.push_back({id, event, std::move(cb)});
return id;
}
void off(int id) {
std::erase_if(listeners_, [id](const Listener& l) { return l.id == id; });
}3Step 3: Emit event to all matching listeners
void emit(const std::string& event, const std::string& data) {
for (const auto& l : listeners_)
if (l.event == event) l.cb(data);
}
};4Step 4: Demonstrate
int main() {
EventEmitter emitter;
int id = emitter.on("message", [](const std::string& data) {
std::cout << "Received: " << data << "\n";
});
emitter.emit("message", "Hello!");
emitter.off(id);
emitter.emit("message", "This won't print");
}#include <iostream>
#include <string>
#include <vector>
#include <map>
#include <functional>
#include <mutex>
#include <algorithm>
#include <format>
#include <any>
#include <memory>
// [step] Type-safe event system with templates
template <typename... Events>
class TypedEventEmitter;
// Single-event implementation
template <typename EventData>
class EventChannel {
struct Subscription {
int id;
std::function<void(const EventData&)> handler;
bool once;
};
mutable std::mutex mu_;
std::vector<Subscription> subs_;
int nextId_ = 0;
public:
int on(std::function<void(const EventData&)> handler) {
std::lock_guard lk(mu_);
int id = nextId_++;
subs_.push_back({id, std::move(handler), false});
return id;
}
int once(std::function<void(const EventData&)> handler) {
std::lock_guard lk(mu_);
int id = nextId_++;
subs_.push_back({id, std::move(handler), true});
return id;
}
void off(int id) {
std::lock_guard lk(mu_);
std::erase_if(subs_, [id](const Subscription& s) { return s.id == id; });
}
void emit(const EventData& data) {
std::lock_guard lk(mu_);
std::vector<int> toRemove;
for (const auto& sub : subs_) {
sub.handler(data);
if (sub.once) toRemove.push_back(sub.id);
}
for (int id : toRemove)
std::erase_if(subs_, [id](const Subscription& s) { return s.id == id; });
}
size_t listenerCount() const {
std::lock_guard lk(mu_);
return subs_.size();
}
};
// [step] Multi-channel event bus using string keys
class EventBus {
std::map<std::string, std::shared_ptr<EventChannel<std::any>>> channels_;
mutable std::mutex mu_;
std::shared_ptr<EventChannel<std::any>> getChannel(const std::string& event) {
std::lock_guard lk(mu_);
auto it = channels_.find(event);
if (it == channels_.end()) {
auto ch = std::make_shared<EventChannel<std::any>>();
channels_[event] = ch;
return ch;
}
return it->second;
}
public:
template <typename T>
int on(const std::string& event, std::function<void(const T&)> handler) {
return getChannel(event)->on([handler = std::move(handler)](const std::any& data) {
handler(std::any_cast<const T&>(data));
});
}
template <typename T>
int once(const std::string& event, std::function<void(const T&)> handler) {
return getChannel(event)->once([handler = std::move(handler)](const std::any& data) {
handler(std::any_cast<const T&>(data));
});
}
void off(const std::string& event, int id) {
getChannel(event)->off(id);
}
template <typename T>
void emit(const std::string& event, const T& data) {
getChannel(event)->emit(std::any(data));
}
};
// [step] RAII subscription guard for automatic cleanup
class SubscriptionGuard {
EventBus& bus_;
std::string event_;
int id_;
public:
SubscriptionGuard(EventBus& bus, std::string event, int id)
: bus_(bus), event_(std::move(event)), id_(id) {}
~SubscriptionGuard() { bus_.off(event_, id_); }
SubscriptionGuard(const SubscriptionGuard&) = delete;
SubscriptionGuard& operator=(const SubscriptionGuard&) = delete;
};
// [step] Demonstrate the event system
struct UserEvent {
std::string userId;
std::string action;
};
int main() {
EventBus bus;
// Subscribe with RAII guard
auto id = bus.on<UserEvent>("user:action",
std::function<void(const UserEvent&)>([](const UserEvent& e) {
std::cout << std::format("User {} did {}\n", e.userId, e.action);
}));
SubscriptionGuard guard(bus, "user:action", id);
// One-shot listener
bus.once<std::string>("app:started",
std::function<void(const std::string&)>([](const std::string& msg) {
std::cout << std::format("App started: {}\n", msg);
}));
bus.emit<std::string>("app:started", "v1.0");
bus.emit<UserEvent>("user:action", {"user-1", "login"});
bus.emit<UserEvent>("user:action", {"user-2", "purchase"});
// Guard auto-unsubscribes when it goes out of scope
}Observer Pattern Architecture
hourglass_empty
Rendering diagram...
lightbulb
Observer Pattern in the Real World
“Think of a newspaper subscription service. The publisher (subject) doesn't know exactly who its subscribers (observers) are—it just maintains a list. When a new edition is printed, it delivers a copy to every subscriber on the list automatically. Subscribers can cancel at any time without the publisher needing to change anything.”