在C++开发中,观察者模式常用于处理事件通知逻辑,但手动管理观察者的注册和注销容易出现遗漏,导致悬空指针或者内存泄漏。结合RAII模式封装观察者模式,可以让观察者的生命周期和注册状态自动绑定,同时简化回调列表的管理逻辑,实现轻量且安全的事件通知机制。

核心设计思路
整个封装的核心分为两部分,一部分是Observer类,负责RAII管理观察者的注册与注销,另一部分是Subject类,负责维护回调列表和触发通知。观察者对象销毁时,会自动从主题的回调列表中移除自身,无需手动调用注销接口。
Observer类设计
Observer类持有主题的弱引用和自身的回调迭代器,构造时自动注册回调,析构时自动注销,避免手动操作带来的问题。
Subject类设计
Subject类内部维护一个回调列表,提供注册、注销、通知接口,注册时返回迭代器给Observer用于后续注销,通知时遍历回调列表执行所有注册的回调函数。
完整源码实现
以下是完整的实现代码,包含头文件和简单的测试示例:
#include <iostream>
#include <vector>
#include <functional>
#include <memory>
#include <algorithm>
// 前置声明Subject类
class Subject;
// 观察者类,RAII管理注册与注销
class Observer {
public:
using Callback = std::function<void(int)>;
using Iterator = std::vector<Callback>::iterator;
// 构造时自动注册回调
Observer(Subject* subject, Callback cb);
// 析构时自动注销回调
~Observer();
// 禁止拷贝,允许移动
Observer(const Observer&) = delete;
Observer& operator=(const Observer&) = delete;
Observer(Observer&& other) noexcept;
Observer& operator=(Observer&& other) noexcept;
private:
Subject* subject_; // 主题弱引用,不持有所有权
Iterator it_; // 回调在列表中的迭代器
bool registered_; // 标记是否已注册
};
// 主题类,管理回调列表和通知逻辑
class Subject {
public:
using Callback = std::function<void(int)>;
Subject() = default;
~Subject() = default;
// 注册回调,返回迭代器用于注销
std::vector<Callback>::iterator Register(Callback cb) {
callbacks_.push_back(cb);
return --callbacks_.end();
}
// 注销指定迭代器的回调
void Unregister(std::vector<Callback>::iterator it) {
callbacks_.erase(it);
}
// 触发通知,执行所有注册的回调
void Notify(int value) {
for (auto& cb : callbacks_) {
if (cb) {
cb(value);
}
}
}
private:
std::vector<Callback> callbacks_; // 回调列表
friend class Observer;
};
// Observer构造函数实现
Observer::Observer(Subject* subject, Callback cb)
: subject_(subject), registered_(true) {
if (subject_) {
it_ = subject_->Register(cb);
}
}
// Observer析构函数实现
Observer::~Observer() {
if (registered_ && subject_) {
subject_->Unregister(it_);
registered_ = false;
}
}
// Observer移动构造函数实现
Observer::Observer(Observer&& other) noexcept
: subject_(other.subject_), it_(other.it_), registered_(other.registered_) {
other.registered_ = false;
}
// Observer移动赋值运算符实现
Observer& Observer::operator=(Observer&& other) noexcept {
if (this != &other) {
// 先注销当前注册的回调
if (registered_ && subject_) {
subject_->Unregister(it_);
}
subject_ = other.subject_;
it_ = other.it_;
registered_ = other.registered_;
other.registered_ = false;
}
return *this;
}
// 测试示例
int main() {
Subject subject;
// 创建两个观察者,自动注册回调
Observer obs1(&subject, [](int val) {
std::cout << "Observer1 received: " << val << std::endl;
});
Observer obs2(&subject, [](int val) {
std::cout << "Observer2 received: " << val << std::endl;
});
// 触发通知
subject.Notify(10);
// obs1离开作用域自动析构,自动注销
{
Observer obs3(&subject, [](int val) {
std::cout << "Observer3 received: " << val << std::endl;
});
subject.Notify(20);
}
// obs3已注销,不会收到通知
subject.Notify(30);
return 0;
}
代码逻辑解析
上述实现中,Subject的Register方法将回调加入callbacks_列表,返回对应的迭代器。Observer构造时调用Register获取迭代器,析构时调用Subject的Unregister方法移除对应回调,整个过程无需手动干预。
注意到Observer禁止了拷贝操作,只允许移动,是因为迭代器拷贝后如果原对象析构,会导致重复注销的问题。移动操作会将原对象的注册状态置为false,避免重复注销。
使用注意事项
Subject的生命周期必须长于所有关联的Observer,否则Observer析构时访问悬空的subject_指针会导致未定义行为。- 回调函数中不要做耗时操作,否则会阻塞
Notify方法的执行,影响所有观察者的通知效率。 - 如果需要线程安全的回调列表管理,可以在
Subject的Register、Unregister、Notify方法中添加互斥锁保护。
适用场景
这种轻量级封装适合中小型C++项目中的事件通知场景,比如UI组件的事件响应、模块间的消息传递等,不需要引入复杂的第三方信号槽库,代码量小且易于维护。