boost库之事件处理(二)
一、概述
Boost.Signals所实现的模式被命名为"信号至插槽",它基于以下概念:当对应的信号被发出时,相关联的插槽即被执行,原则上,可以把"信号"和"插槽"分别替换为'事件'和'事件处理器',它提供了一个名为boost::signal的类,定义于boost/signal.hpp,实际上,这个头文件是唯一一个需要知道的,因为它会自动包含其他相关的头文件。在较新版本的boost库中,它定义于boost/signals2.hpp,并且提供了一个名为boost::signals2::signal的类。
二、Boost.Signals2的使用
Boost.Signals2是Boost库中的一个模块,用于实现信号和槽(signal/slot)机制。它提供了一种方便的方式来实现事件的发布和订阅,使不同的组件之间可以进行解耦的通信。
Boost.Signals2的核心是boost::signals2::signal
类。下面是该类的简单介绍和使用示例:
-
boost::signals2::signal
类的基本特点:- 类似于发布-订阅模式,通过信号(signal)和槽(slot)进行通信。
- 支持多个槽函数订阅同一个信号,当信号被触发时,所有订阅的槽函数都会被调用。
- 可以选择不同的连接策略(connection policies)来管理连接和断开连接的行为。
- 支持返回值的收集(collecting return values)和连接顺序的控制等高级功能。
-
使用Boost.Signals2的基本步骤:
- 包含头文件:
#include <boost/signals2.hpp>
- 定义信号类型:
boost::signals2::signal<Signature>
,其中Signature
是信号的函数签名,即槽函数的类型。 - 定义槽函数:可以是全局函数、成员函数、lambda表达式等。
- 连接信号和槽:使用
signal.connect(slot)
将槽函数与信号连接起来。 - 触发信号:通过调用
signal()
来触发信号,会自动调用所有连接的槽函数。
- 包含头文件:
#include <iostream> #include <boost/signals2.hpp> void func() { std::cout << "Hello World!" << std::endl; } int main() { boost::signals2::signal<void()> mySignal; mySignal.connect(&func); mySignal(); return 0; }
boost::signals2::signal实际上被实现为一个模板函数,具有被用作为事件处理器的函数的签名,该签名也是它的模板参数,在上述的例子中,只有签名为void()的函数可以被成功关联至信号mySignal。函数func()被connect()方法关联至信号s,由于func()符合所要求的void()签名,所以该关联成功建立,因为当信号mySignal被触发时,func()将被调用,信号是通过调用mySignal来触发的,就像普通的函数调用一样,这个函数的签名对应于作为模板参数传入的签名:因为void()不要求任何参数,所以括号内是空的,调用mySignal会引发一个触发器,进而执行相应的func()函数,之前用connect()关联的。
另外,Boost::Signals则提供了多对一的方式,如关联多个函数单个特定信号,示例如下:
#include <iostream> #include <boost/signals2.hpp> void func() { std::cout << "Hello"; } void func1() { std::cout << " World!" << std::endl; } int main() { boost::signals2::signal<void()> mySignal; mySignal.connect(&func); mySignal.connect(&func1); mySignal(); return 0; }
boost::signal可以通过反复调用connect()方法把多个函数赋值给单个特定的信号,当该信号触发时,这些函数被按照之前用connect()进行关联时的顺序来执行。另外执行的顺序也可以通过connect()方法的另一个重载版本来明确指定,该重载版本要求以一个int类型的值作为额外的参数
#include <iostream> #include <boost/signals2.hpp> void func() { std::cout << " Hello" << std::endl; } void func1() { std::cout << "World"; } int main() { boost::signals2::signal<void()> mySignal; mySignal.connect(1, &func); mySignal.connect(0, &func1); mySignal(); return 0; }
要释放某个函数与给定信号的关联,可以用disconnect()方法
#include <iostream> #include <boost/signals2.hpp> void func() { std::cout << "Hello"; } void func1() { std::cout << " World!" << std::endl; } int main() { boost::signals2::signal<void()> mySignal; mySignal.connect(&func); mySignal.connect(&func1); mySignal.disconnect(&func1); mySignal(); return 0; }
上述例子仅输出Hello,因为与func2()的关联在触发信号之前已经被释放。
除了connect和disconnect()以外,boost::signal还提供了几个方法
#include <iostream> #include <boost/signals2.hpp> void func() { std::cout << "Hello"; } void func1() { std::cout << " World!" << std::endl; } int main() { boost::signals2::signal<void()> mySignal; mySignal.connect(&func); mySignal.connect(&func1); std::cout << mySignal.num_slots() << std::endl; if (!mySignal.empty()) { mySignal(); } return 0; }
num_slots()返回已经关联函数的数量,如果没有函数被关联,则num_slots()返回0,在这种特定情况下,可以用empty()方法来代替,disconnect_all_slots()方法所做的实际上正是它的名字所表达的:释放所有已有的关联。
看完了函数如何被关联至信号,以及弄明白了信号触发时会发生什么事之后,还有一个问题,这些函数的返回值去了哪里?以下例子回答了这个问题:
#include <iostream> #include <algorithm> #include <boost/signals2.hpp> int func() { return 10; } int func1() { return 20; } template <typename T> struct min_element { typedef T result_type; template <typename InputIterator> T operator()(InputIterator first, InputIterator last) const { return T(first, last); } }; int main() { boost::signals2::signal<int(), min_element<std::vector<int>>> mySignal; mySignal.connect(&func); mySignal.connect(&func1); std::vector<int> v = mySignal(); std::cout << v.size() << std::endl; std::cout << *std::min_element(v.begin(), v.end()) << std::endl; return 0; }
上述例子把所有返回值保存在一个vector中,再由mySignal()返回
三、连接Connections
函数可以通过boost::signal所提供的connect()和disconnec()方法来进行管理,由于connect()会返回一个boost::signals::connection的值,它们可以通过其他方法来管理。
#include <iostream> #include <boost/signals2.hpp> void func() { std::cout << "Hello World!" << std::endl; } int main() { boost::signals2::signal<void()> mySignal; boost::signals2::connection con = mySignal.connect(func); mySignal(); con.disconnect(); return 0; }
boost::signal的disconnect()方法需要传入一个函数指针,而直接调用boost::signals2::connection对象上的disconnect()方法则可以省略该参数。
除了boost::signals2::connection以外,还有一个boost::signals2::scoped_connection的类,它会在析构时自动释放连接。
#include <iostream> #include <boost/signals2.hpp> void func() { std::cout << "Hello World!" << std::endl; } int main() { boost::signals2::signal<void()> mySignal; { boost::signals2::scoped_connection con = mySignal.connect(func); } mySignal(); return 0; }
因为连接对象con在信号触发之前被销毁了,所以func()不会被调用。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· DeepSeek 开源周回顾「GitHub 热点速览」
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· AI与.NET技术实操系列(二):开始使用ML.NET
· 单线程的Redis速度为什么快?