[2026] C++ Observer Pattern: Complete Guide | Events, Callbacks & Signal–Slot Patterns
이 글의 핵심
Observer pattern in C++: decouple publishers and subscribers, weak_ptr, typed events, signal/slot style—patterns, pitfalls, and production examples.
Behavioral patterns—including Observer—are surveyed in C++ behavioral patterns #20-1 and overview #20-2. For event/listener-heavy APIs elsewhere, compare JavaScript patterns.
What is the Observer pattern? Why use it?
Problem Scenario: State Change Notification
Problem: When the data model changes, multiple UI components need to be updated. Directly calling each component creates strong coupling. 다음은 cpp를 활용한 상세한 구현 코드입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
// Bad example: strong coupling
// 타입 정의
class DataModel {
public:
void setValue(int v) {
value = v;
// Call UI component directly
chart->update(value);
label->update(value);
logger->log(value);
}
private:
int value;
Chart* chart;
Label* label;
Logger* logger;
};
Problem:
- strong coupling:
DataModelmust know all UI components - Difficult to expand:
DataModelneeds to be modified when adding a new component - No reuse: Difficult to reuse
DataModelin other projects Solution: Observer Pattern separates Subject and Observer. Subject only manages the Observer list and notifies with notify() when state changes. 다음은 cpp를 활용한 상세한 구현 코드입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며, 반복문으로 데이터를 처리합니다, 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
// Good example: loose coupling
// 타입 정의
class DataModel {
public:
void setValue(int v) {
value = v;
notify(value); // Notify all Observers
}
void attach(std::shared_ptr<Observer> obs) {
observers.push_back(obs);
}
private:
int value;
std::vector<std::weak_ptr<Observer>> observers;
void notify(int value) {
for (auto& obs : observers) {
if (auto ptr = obs.lock()) {
ptr->update(value);
}
}
}
};
아래 코드는 mermaid를 사용한 구현 예제입니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
// 실행 예제
flowchart TD
subject["Subject (DataModel)"]
obs1["Observer 1 (Chart)"]
obs2["Observer 2 (Label)"]
obs3["Observer 3 (Logger)"]
subject -->|notify| obs1
subject -->|notify| obs2
subject -->|notify| obs3
obs1 -.->|attach| subject
obs2 -.->|attach| subject
obs3 -.->|attach| subject
index
- Basic structure
- Prevent memory leak with weak_ptr
- Observer by event type
- Signal/Slot Pattern
- Frequently occurring problems and solutions
- Production Patterns
- Complete Example: Stock Market Monitor
1. basic structure
Minimum Observer
다음은 cpp를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고, 클래스를 정의하여 데이터와 기능을 캡슐화하며, 반복문으로 데이터를 처리합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
#include <iostream>
#include <vector>
#include <memory>
class Observer {
public:
virtual void update(int value) = 0;
virtual ~Observer() = default;
};
class Subject {
public:
void attach(std::shared_ptr<Observer> obs) {
observers.push_back(obs);
}
void notify(int value) {
for (auto& obs : observers) {
obs->update(value);
}
}
private:
std::vector<std::shared_ptr<Observer>> observers;
};
class ConcreteObserver : public Observer {
public:
ConcreteObserver(const std::string& name) : name_(name) {}
void update(int value) override {
std::cout << name_ << " received: " << value << '\n';
}
private:
std::string name_;
};
int main() {
Subject subject;
auto obs1 = std::make_shared<ConcreteObserver>("Observer1");
auto obs2 = std::make_shared<ConcreteObserver>("Observer2");
subject.attach(obs1);
subject.attach(obs2);
subject.notify(42);
// Observer1 received: 42
// Observer2 received: 42
}
2. Prevent memory leaks with weak_ptr
Problem: Circular references
아래 코드는 cpp를 사용한 구현 예제입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며. 코드를 직접 실행해보면서 동작을 확인해보세요.
// ❌ Misuse: Circular reference with shared_ptr
class Subject {
std::vector<std::shared_ptr<Observer>> observers; // strong reference
};
// Subject owns Observer, Observer owns Subject → circular reference
Solved: weak_ptr
다음은 cpp를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고, 클래스를 정의하여 데이터와 기능을 캡슐화하며, 반복문으로 데이터를 처리합니다, 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
#include <iostream>
#include <vector>
#include <memory>
class Observer {
public:
virtual void update(int value) = 0;
virtual ~Observer() = default;
};
class Subject {
public:
void attach(std::shared_ptr<Observer> obs) {
observers.push_back(obs); // Save as weak_ptr
}
void notify(int value) {
// Remove expired Observer
observers.erase(
std::remove_if(observers.begin(), observers.end(),
{
return wp.expired();
}),
observers.end()
);
// alarm
for (auto& obs : observers) {
if (auto ptr = obs.lock()) {
ptr->update(value);
}
}
}
private:
std::vector<std::weak_ptr<Observer>> observers;
};
class ConcreteObserver : public Observer {
public:
ConcreteObserver(const std::string& name) : name_(name) {}
~ConcreteObserver() {
std::cout << name_ << " destroyed\n";
}
void update(int value) override {
std::cout << name_ << " received: " << value << '\n';
}
private:
std::string name_;
};
int main() {
Subject subject;
{
auto obs1 = std::make_shared<ConcreteObserver>("Observer1");
subject.attach(obs1);
subject.notify(42); // Observer1 received: 42
} // destroy obs1
subject.notify(100); // Expired Observer will not be notified
}
output of power:
Observer1 received: 42
Observer1 destroyed
3. Observer by event type
Various event handling
다음은 cpp를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고, 클래스를 정의하여 데이터와 기능을 캡슐화하며, 반복문으로 데이터를 처리합니다, 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
#include <iostream>
#include <vector>
#include <memory>
#include <string>
class Event {
public:
virtual ~Event() = default;
};
class ValueChangedEvent : public Event {
public:
ValueChangedEvent(int v) : value(v) {}
int value;
};
class ErrorEvent : public Event {
public:
ErrorEvent(const std::string& m) : message(m) {}
std::string message;
};
class Observer {
public:
virtual void onEvent(const Event& event) = 0;
virtual ~Observer() = default;
};
class Subject {
public:
void attach(std::shared_ptr<Observer> obs) {
observers.push_back(obs);
}
void notifyEvent(const Event& event) {
for (auto& obs : observers) {
if (auto ptr = obs.lock()) {
ptr->onEvent(event);
}
}
}
private:
std::vector<std::weak_ptr<Observer>> observers;
};
class ConcreteObserver : public Observer {
public:
void onEvent(const Event& event) override {
if (auto* ve = dynamic_cast<const ValueChangedEvent*>(&event)) {
std::cout << "Value changed: " << ve->value << '\n';
} else if (auto* ee = dynamic_cast<const ErrorEvent*>(&event)) {
std::cout << "Error: " << ee->message << '\n';
}
}
};
int main() {
Subject subject;
auto obs = std::make_shared<ConcreteObserver>();
subject.attach(obs);
subject.notifyEvent(ValueChangedEvent(42));
subject.notifyEvent(ErrorEvent("Something went wrong"));
}
4. Signal/Slot Pattern
Qt-style signals/slots
다음은 cpp를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고, 클래스를 정의하여 데이터와 기능을 캡슐화하며, 반복문으로 데이터를 처리합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
#include <iostream>
#include <vector>
#include <functional>
template<typename....Args>
class Signal {
public:
using Slot = std::function<void(Args...)>;
void connect(Slot slot) {
slots.push_back(slot);
}
void emit(Args....args) {
for (auto& slot : slots) {
slot(args...);
}
}
private:
std::vector<Slot> slots;
};
class Button {
public:
Signal<> clicked;
void click() {
std::cout << "Button clicked\n";
clicked.emit();
}
};
int main() {
Button button;
button.clicked.connect( {
std::cout << "Handler 1: Button was clicked\n";
});
button.clicked.connect( {
std::cout << "Handler 2: Logging click event\n";
});
button.click();
// Button clicked
// Handler 1: Button was clicked
// Handler 2: Logging click event
}
5. Frequently occurring problems and solutions
Problem 1: Circular references
Symptom: Memory leak.
Cause: Subject and Observer refer to each other as shared_ptr.
아래 코드는 cpp를 사용한 구현 예제입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며. 코드를 직접 실행해보면서 동작을 확인해보세요.
// ❌ Misuse: Circular reference with shared_ptr
class Subject {
std::vector<std::shared_ptr<Observer>> observers;
};
// ✅ Correct usage: weak_ptr
class Subject {
std::vector<std::weak_ptr<Observer>> observers;
};
Issue 2: Observer removal during notification
Symptom: Iterator invalidation, crash.
Cause: Observer calls detach() during notify().
다음은 cpp를 활용한 상세한 구현 코드입니다. 반복문으로 데이터를 처리합니다, 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
// ❌ Incorrect use: Removed during notification
void notify() {
for (auto& obs : observers) {
obs->update(); // call detach() in update() → invalidate iterator
}
}
// ✅ Correct use: Notify with copy
void notify() {
auto copy = observers; // copy
for (auto& obs : copy) {
if (auto ptr = obs.lock()) {
ptr->update();
}
}
}
Problem 3: Reentry
Symptom: Infinite loop.
Cause: Calling Subject’s setValue() in Observer’s update() → notify() again.
다음은 cpp를 활용한 상세한 구현 코드입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며, 에러 처리를 통해 안정성을 확보합니다, 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
// ❌ Misuse: Reentrancy
void Observer::update(int value) {
subject->setValue(value + 1); // infinite loop
}
// ✅ Correct use: Prevent re-entrancy
class Subject {
void notify() {
if (notifying) return; // Prevent re-entry
notifying = true;
// ....alarm ...
notifying = false;
}
private:
bool notifying = false;
};
6. production pattern
Pattern 1: Priority Observer
다음은 cpp를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고, 클래스를 정의하여 데이터와 기능을 캡슐화하며, 반복문으로 데이터를 처리합니다, 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
#include <map>
#include <memory>
class Subject {
public:
void attach(std::shared_ptr<Observer> obs, int priority = 0) {
observers[priority].push_back(obs);
}
void notify(int value) {
// Notifications in order of highest priority
for (auto it = observers.rbegin(); it != observers.rend(); ++it) {
for (auto& obs : it->second) {
if (auto ptr = obs.lock()) {
ptr->update(value);
}
}
}
}
private:
std::map<int, std::vector<std::weak_ptr<Observer>>> observers;
};
Pattern 2: Asynchronous notification
다음은 cpp를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고, 클래스를 정의하여 데이터와 기능을 캡슐화하며, 반복문으로 데이터를 처리합니다, 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
#include <thread>
#include <future>
class Subject {
public:
void notifyAsync(int value) {
auto copy = observers;
std::thread([copy, value]() {
for (auto& obs : copy) {
if (auto ptr = obs.lock()) {
ptr->update(value);
}
}
}).detach();
}
private:
std::vector<std::weak_ptr<Observer>> observers;
};
7. Complete Example: Stock Market Monitor
다음은 cpp를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고, 클래스를 정의하여 데이터와 기능을 캡슐화하며, 반복문으로 데이터를 처리합니다, 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
#include <iostream>
#include <vector>
#include <memory>
#include <string>
#include <algorithm>
class StockObserver {
public:
virtual void onPriceChanged(const std::string& symbol, double price) = 0;
virtual ~StockObserver() = default;
};
class StockMarket {
public:
void attach(std::shared_ptr<StockObserver> obs) {
observers.push_back(obs);
}
void detach(std::shared_ptr<StockObserver> obs) {
observers.erase(
std::remove_if(observers.begin(), observers.end(),
[&obs](const std::weak_ptr<StockObserver>& wp) {
auto sp = wp.lock();
return !sp || sp == obs;
}),
observers.end()
);
}
void setPrice(const std::string& symbol, double price) {
prices[symbol] = price;
notifyPriceChanged(symbol, price);
}
private:
std::map<std::string, double> prices;
std::vector<std::weak_ptr<StockObserver>> observers;
void notifyPriceChanged(const std::string& symbol, double price) {
auto copy = observers;
for (auto& obs : copy) {
if (auto ptr = obs.lock()) {
ptr->onPriceChanged(symbol, price);
}
}
}
};
class PriceDisplay : public StockObserver {
public:
PriceDisplay(const std::string& name) : name_(name) {}
void onPriceChanged(const std::string& symbol, double price) override {
std::cout << "[" << name_ << "] " << symbol << ": $" << price << '\n';
}
private:
std::string name_;
};
class PriceAlert : public StockObserver {
public:
PriceAlert(const std::string& symbol, double threshold)
: symbol_(symbol), threshold_(threshold) {}
void onPriceChanged(const std::string& symbol, double price) override {
if (symbol == symbol_ && price > threshold_) {
std::cout << "ALERT: " << symbol << " exceeded $" << threshold_ << '\n';
}
}
private:
std::string symbol_;
double threshold_;
};
int main() {
StockMarket market;
auto display = std::make_shared<PriceDisplay>("MainDisplay");
auto alert = std::make_shared<PriceAlert>("AAPL", 150.0);
market.attach(display);
market.attach(alert);
market.setPrice("AAPL", 145.0); // [MainDisplay] AAPL: $145
market.setPrice("AAPL", 155.0); // [MainDisplay] AAPL: $155
// ALERT: AAPL exceeded $150
}
organize
| concept | Description |
|---|---|
| Observer Pattern | Subject notifies Observer of state change |
| Purpose | Loosely coupled, event-driven architecture |
| Structure | Subject (attach, notify), Observer (update) |
| Advantages | Scalability, reusability, dynamic subscriptions |
| Disadvantages | Circular references, uncertain notification order, performance overhead |
| Use Case | UI updates, event system, MVC patterns |
| Observer Pattern is a core design pattern that implements loose coupling in event-based systems. |
FAQ
Q1: When do I use the Observer Pattern?
A: Used when changes in the state of one object need to be notified to multiple objects and loose coupling is required.
Q2: Why do you use weak_ptr?
A: Used to prevent circular references and automatically remove Observers.
Q3: Is the notification order guaranteed?
A: Not guaranteed. If you need priority, use the Priority Observer pattern.
Q4: What is the difference from signal/slot?
A: Signal/Slot is a variant of the Observer Pattern that is type safe and connects function objects directly (Qt style).
Q5: What is the performance overhead?
A: Proportional to the number of Observers. This can be alleviated with asynchronous notifications.
Q6: What are the Observer Pattern learning resources?
A:
- “Design Patterns” by Gang of Four
- “Head First Design Patterns” by Freeman & Freeman
- Refactoring Guru: Observer Pattern One-line summary: Observer Pattern allows you to implement event-driven architecture and achieve loose coupling. Next, it would be a good idea to read Strategy Pattern.
Good article to read together (internal link)
Here’s another article related to this topic.
- C++ weak_ptr | “Weak Pointers” Guide
- C++ Factory Pattern Complete Guide | Object creation encapsulation and extensibility
Practical tips
These are tips that can be applied right away in practice.
Debugging tips
- If you run into a problem, check the compiler warnings first.
- Reproduce the problem with a simple test case
Performance Tips
- Don’t optimize without profiling
- Set measurable indicators first
Code review tips
- Check in advance for areas that are frequently pointed out in code reviews.
- Follow your team’s coding conventions
Practical checklist
This is what you need to check when applying this concept in practice.
Before writing code
- Is this technique the best way to solve the current problem?
- Can team members understand and maintain this code?
- Does it meet the performance requirements?
Writing code
- Have you resolved all compiler warnings?
- Have you considered edge cases?
- Is error handling appropriate?
When reviewing code
- Is the intent of the code clear?
- Are there enough test cases?
- Is it documented? Use this checklist to reduce mistakes and improve code quality.
Keywords covered in this article (related search terms)
This article will be helpful if you search for C++, observer, pattern, event, callback, signal-slot, etc.
Related articles
- C++ Adapter Pattern Complete Guide | Interface conversion and compatibility
- Complete Guide to C++ Command Pattern | Undo and macro system
- C++ CRTP Complete Guide | Static polymorphism and compile-time optimization
- C++ Decorator Pattern Complete Guide | Dynamic addition and combination of functions
- C++ Factory Pattern Complete Guide | Object creation encapsulation and extensibility