signals2基于Boost里的另一个库signals实现了线程安全的观察者模式。在signals2中,观察者模式被称为信号/插槽(signals/slots),它是一种函数回调机制,一个信号关联了多个插槽,当信号发出时,所有关联它的插槽都会被调用。
#include
using namespace boost::signals2;
signals2库的核心是signal类,其类摘要如下:
signal的模板参数很长,总共有7个参数,这里仅列出了最重要的前4个,而且除了第一个是必须的,其他都可以用默认值:
signal
signal继承自signal_base,而signal_base又继承自noncopyable,因此signal是不可拷贝的。如果把signal作为自定义类的成员变量,那么自定义类也将是不可拷贝的,除非使用shared_ptr/ref来间接持有它。
最重要的操作函数是connect(),它把插槽连接到信号上,相当于为信号(事件)增加了一个处理的handler
成员函数disconnect()可以断开插槽和信号之间的连接,它有两种形式:
成员函数disconnect_all_slots()可以一次性断开信号的所有插槽连接。
当前信号所连接的插槽数量可以用num_slots()获得
成员函数empty()相当于num_slots() == 0。disconnect_all_slots()将令empty()返回true
signal提供operator(),可以接受最多9个参数。当operator()被外界调用时意味着产生了一个信号,这将导致信号所关联的所有插槽被调用。插槽调用的结果使用合并器处理后返回,默认情况下是一个optional对象。
成员函数combiner()和set_combiner()分别用于换取和设置合并器对象,通过signal的构造函数也可以在创建的时候就传入一个合并器实例。但是除非想改用其他的合并方式,通常我们直接使用默认函数创建模板参数列表中指定的合并器对象。
当signal析构时,将自动断开所有插槽连接
signal就像是一个增强的function对象,它可以容纳(使用connect()连接)多个符合模板参数中函数签名类型的函数,形成一个插槽链表,然后在信号发生时一起调用。
比如,我们有如下两个无参函数,可以用在插槽:
怎么设置用在插槽呢?
接下来就可以使用connect()来连接插槽,最后用operator()来产生信号:
在connect()时我们省略了第二个参数connect_position,其默认值是at_back,表示插槽将插入信号到尾部,因此先调用slots1,再调用slots2
当然,也可以将slost2插入头部:
我们可以在connect()时指定插槽所在的组号。默认情况下组号是int类型,组号不一定要从0开始,它可以是任意数值,包括离散值、负值。
如果在连接时指定组号,那么每个编组都将是一个链表,其顺序规则如下:
默认情况下signal使用合并器optional_last_value< R>,它将使用optional对象返回最后被调用的插槽的返回值。
举个例子:
signal的operator()调用这时需要传入一个整数参数,这个参数会被signal存储一个拷贝,然后转发给各个插槽。最后signal将返回插槽链表末尾slots< 50 >()的计算结果,它是一个optional对象,必须用解引用操作符*来获取值:
默认的合并器optional_last_value< R>并没有太多意义,它通常用在我们并不关心插槽返回值或者返回值是void的时候,但是很多时候我们需要关心多个插槽的返回值。
这时候我们可以自定义合并器来处理插槽的返回值,把多个插槽的返回值合并为一个结果返回。
合并器应该是一个函数对象(不是函数或者函数指针),具有如下形式:
combiner的operator()的返回值类型可以是任意类型,完全由用户指定,不一定必须是optional或者是插槽的返回值类型。函数的模板参数InputIterator是插槽链表的返回值迭代器,可以使用它来遍历所有插槽的返回值,进行所需的处理。
举个例子,我们自定义一个合并器,它使用pair返回所有插槽的返回值之和以及其中的最大值:
怎么用呢?我们在signal声明时需要使用第二个模板参数------合并器类型:
它相当于:
使用:
也可以在构造是传入一个合并器实例,那么signal将会处理这个合并器的拷贝处理返回值。比如下面使用了一个有初值的合并器对象:
当信号调用完插槽后,可能需要将插槽从信号中断开。
要断开一个插槽,插槽必须能够等价比较,对于函数对象来说就是定义一个operator==。
但是这样很不方便,因为它必须知道与它连接的所有插槽的信息,还要求插槽对象必须是可以等价比较的。怎么办呢?
我们可以用connection对象来管理信号连接。