The traditional Observer pattern uses virtual functions and an abstract base class. The subject keeps a list of observer pointers and calls their update() method when state changes.
Structure
The code
#include <iostream>
#include <vector>
#include <memory>
#include <algorithm>
// ── Observer interface ─────────────────────────────────────
class Observer {
public:
virtual ~Observer() = default;
virtual void update(int new_value) = 0;
};
// ── Subject (Publisher) ────────────────────────────────────
class Subject {
private:
int state_ = 0;
std::vector<Observer*> observers_;
public:
virtual ~Subject() = default;
void attach(Observer* observer) {
observers_.push_back(observer);
}
void detach(Observer* observer) {
auto it = std::find(observers_.begin(), observers_.end(), observer);
if (it != observers_.end()) {
observers_.erase(it);
}
}
void set_state(int new_state) {
if (state_ != new_state) {
state_ = new_state;
notify();
}
}
int get_state() const { return state_; }
private:
void notify() {
for (auto observer : observers_) {
observer->update(state_);
}
}
};
// ── Concrete observers ─────────────────────────────────────
class DisplayObserver : public Observer {
public:
void update(int new_value) override {
std::cout << "[Display] State changed to: " << new_value << "\n";
}
};
class LoggingObserver : public Observer {
public:
void update(int new_value) override {
std::cout << "[Logger] Recording state change: " << new_value << "\n";
}
};
class AlertObserver : public Observer {
private:
int threshold_;
public:
explicit AlertObserver(int threshold) : threshold_(threshold) {}
void update(int new_value) override {
if (new_value > threshold_) {
std::cout << "[Alert] WARNING: State " << new_value
<< " exceeds threshold " << threshold_ << "\n";
}
}
};
// ── Usage ──────────────────────────────────────────────────
int main() {
Subject subject;
DisplayObserver display;
LoggingObserver logger;
AlertObserver alert(50);
// Register observers
subject.attach(&display);
subject.attach(&logger);
subject.attach(&alert);
std::cout << "Setting state to 10:\n";
subject.set_state(10);
std::cout << "\nSetting state to 60:\n";
subject.set_state(60);
std::cout << "\nDetaching logger, setting state to 20:\n";
subject.detach(&logger);
subject.set_state(20);
return 0;
}
Output:
Setting state to 10:
[Display] State changed to: 10
[Logger] Recording state change: 10
Setting state to 60:
[Display] State changed to: 60
[Logger] Recording state change: 60
[Alert] WARNING: State 60 exceeds threshold 50
Detaching logger, setting state to 20:
[Display] State changed to: 20
Advantages
- Virtual functions make the intent clear.
- Each observer can keep its own internal state.
- You can overload
update()for different notification types. - Observers can take different constructor parameters, as
AlertObserver(threshold)does. - You control exactly when and how notification happens.
Disadvantages
- Raw pointers mean manual lifetime management. If an observer is deleted while attached, the subject keeps a dangling pointer.
- All observers share one interface, so type information is lost.
- You get no control over notification order.
- Each observer costs a vtable pointer.
- Adding a parameter to
update()means changing every observer implementation.
When to use
- You have a stable, known set of observer types.
- Observers need constructor parameters or internal state.
- You want explicit control over the notification process.
- Your framework supports polymorphism but not templates, as in pure C or old codebases.
Lifetime issues
If you delete an observer while it is still attached, the subject keeps a dangling pointer:
Subject subject;
DisplayObserver* observer = new DisplayObserver();
subject.attach(observer);
delete observer; // ❌ Observer is now deleted but still in subject's list
subject.set_state(10); // ❌ Calls deleted observer's update() → undefined behavior!
See Lifetime management for lifetime management strategies with RAII, smart pointers, and weak_ptr.
Next steps
- Function-based observer (std::function) uses
std::functionfor more flexibility without inheritance. - Qt signals and slots is a framework-provided mechanism with automatic lifetime management.
- Object-oriented event system provides object-oriented events for type-safe, polymorphic notifications.
- Lifetime management covers lifetime management with smart pointers.