发布订阅模式是一种消息传递模式,发布者不会直接将消息发送给特定订阅者,而是通过事件通道广播消息,所有订阅该事件的监听者都会收到通知并执行对应逻辑。在C++中实现该模式时,我们可以用std::list来存储所有注册的监听者,方便进行添加、移除和遍历通知操作。

发布订阅模式的核心结构
实现该模式需要两个核心角色,分别是发布者和订阅者。订阅者通常是定义了事件处理函数的对象,发布者负责维护订阅者列表,在事件触发时通知所有订阅者。我们用std::list存储订阅者的回调函数,这样可以灵活管理多个监听者。
定义回调函数类型
首先我们需要定义统一的回调函数类型,确保所有订阅者的处理逻辑格式一致,这里使用std::function来包装可调用对象。
#include <iostream> #include <list> #include <functional> #include <string> // 定义事件回调类型,参数为事件消息字符串 using EventCallback = std::function<void(const std::string&)>;
实现发布者类
发布者类需要包含添加监听者、移除监听者、触发事件三个核心方法,内部用std::list存储所有的回调函数。
class Publisher {
private:
// 用std::list管理所有监听者的回调函数
std::list<EventCallback> m_callbacks;
// 用于生成回调的唯一标识,方便移除
int m_nextId = 0;
std::list<int> m_idList;
public:
// 添加监听者,返回监听者ID用于后续移除
int addListener(EventCallback callback) {
m_callbacks.push_back(callback);
m_idList.push_back(m_nextId);
return m_nextId++;
}
// 根据ID移除监听者
void removeListener(int listenerId) {
auto idIt = m_idList.begin();
auto cbIt = m_callbacks.begin();
while (idIt != m_idList.end()) {
if (*idIt == listenerId) {
m_idList.erase(idIt);
m_callbacks.erase(cbIt);
break;
}
++idIt;
++cbIt;
}
}
// 触发事件,通知所有监听者
void publish(const std::string& message) {
for (auto& callback : m_callbacks) {
if (callback) {
callback(message);
}
}
}
};
实现订阅者示例
订阅者可以是任意定义了对应处理逻辑的对象,这里我们定义两个简单的订阅者类,演示如何注册监听和接收事件。
class SubscriberA {
public:
void handleEvent(const std::string& msg) {
std::cout << "SubscriberA 收到消息: " << msg << std::endl;
}
};
class SubscriberB {
public:
void handleEvent(const std::string& msg) {
std::cout << "SubscriberB 收到消息: " << msg << std::endl;
}
};
完整测试代码
下面的代码演示了完整的发布订阅流程,包括注册监听者、触发事件、移除监听者的操作。
int main() {
Publisher pub;
SubscriberA subA;
SubscriberB subB;
// 注册监听者,绑定成员函数到回调
int idA = pub.addListener([&subA](const std::string& msg) {
subA.handleEvent(msg);
});
int idB = pub.addListener([&subB](const std::string& msg) {
subB.handleEvent(msg);
});
// 第一次触发事件
std::cout << "第一次发布事件:" << std::endl;
pub.publish("测试消息1");
// 移除SubscriberA的监听
pub.removeListener(idA);
// 第二次触发事件
std::cout << "n移除SubscriberA后发布事件:" << std::endl;
pub.publish("测试消息2");
return 0;
}
std::list管理监听者的优势
选择std::list管理监听者主要有几个原因:首先std::list插入和删除元素的时间复杂度是O(1),在频繁添加移除监听者的场景下性能稳定;其次std::list迭代器在插入删除其他元素时不会失效,遍历通知过程中如果允许动态添加移除监听者,也不容易出现迭代器失效问题;另外std::list支持在任意位置插入元素,方便后续扩展优先级排序等功能。
实现注意事项
- 回调函数需要做空判断,避免调用到空的std::function对象导致程序崩溃
- 移除监听者时要同时维护ID列表和回调函数列表的对应关系,避免迭代错位
- 如果监听者是临时对象,需要注意生命周期问题,避免回调时对象已经被销毁
- 多线程场景下使用需要加锁保护std::list的读写操作,避免数据竞争