boost:signals2

signals2

是什么?

signals2基于Boost里的另一个库signals实现了线程安全的观察者模式。在signals2中,观察者模式被称为信号/插槽(signals/slots),它是一种函数回调机制,一个信号关联了多个插槽,当信号发出时,所有关联它的插槽都会被调用。

怎么引入?


#include 
using namespace boost::signals2;

类摘要

signals2库的核心是signal类,其类摘要如下:
boost:signals2_第1张图片
signal的模板参数很长,总共有7个参数,这里仅列出了最重要的前4个,而且除了第一个是必须的,其他都可以用默认值:

  • 第一个模板参数Signature,是一个函数类型,表示可以被signal调用的函数。比如:signal
  • 第二个模板参数Combiner,是一个函数对象,它被称为“合并器”,用来组合所有插槽的调用结果,默认是optional_last_value< R>,它使用optional库返回最后一个被调用的插槽的返回值
  • 第三个模板参数是Group是插槽编组的类型,默认使用int来标记组好,也可以改为std::string等,但是一般不用改
  • 第四个模板参数是GroupCompare与Group配合使用,用来确定编组的排序准则,默认是升序(std::less< Group>),因此要求Group必须定义了operator<

signal继承自signal_base,而signal_base又继承自noncopyable,因此signal是不可拷贝的。如果把signal作为自定义类的成员变量,那么自定义类也将是不可拷贝的,除非使用shared_ptr/ref来间接持有它。

操作函数

最重要的操作函数是connect(),它把插槽连接到信号上,相当于为信号(事件)增加了一个处理的handler

  • 插槽可以是任意的可调用对象,包括函数指针、函数对象,以及它们的bind/lambda表达式和function对象,signal内部使用function作为容器来保存这些可调用对象。
  • 连接时可以指定组号也可以不指定组号,当信号发生时将依据组号的排序准则依次调用插槽函数。
  • 如果连接成功,connect()将返回一个connection对象,表示信号与插槽之间的连接关系。它是一个轻量级对象,可以处理两者间的连接,比如断开、重新连接、测试连接状态

成员函数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的第四个模板参数时指定)
  • 每个编组的链表内部的插入顺序由at_back和at_front指定
  • 未被编组的插槽如果标识是at_front,将第一个调用
  • 未被编组的插槽如果标识是at_back,将最后调用

举个例子:
boost:signals2_第2张图片

boost:signals2_第3张图片
调用结果:
boost:signals2_第4张图片

如何获取插槽的返回值

默认情况下signal使用合并器optional_last_value< R>,它将使用optional对象返回最后被调用的插槽的返回值。

举个例子:

boost:signals2_第5张图片
在这里插入图片描述

在这里插入图片描述

signal的operator()调用这时需要传入一个整数参数,这个参数会被signal存储一个拷贝,然后转发给各个插槽。最后signal将返回插槽链表末尾slots< 50 >()的计算结果,它是一个optional对象,必须用解引用操作符*来获取值:

在这里插入图片描述

合并器

默认的合并器optional_last_value< R>并没有太多意义,它通常用在我们并不关心插槽返回值或者返回值是void的时候,但是很多时候我们需要关心多个插槽的返回值。

这时候我们可以自定义合并器来处理插槽的返回值,把多个插槽的返回值合并为一个结果返回。

合并器应该是一个函数对象(不是函数或者函数指针),具有如下形式:
boost:signals2_第6张图片

combiner的operator()的返回值类型可以是任意类型,完全由用户指定,不一定必须是optional或者是插槽的返回值类型。函数的模板参数InputIterator是插槽链表的返回值迭代器,可以使用它来遍历所有插槽的返回值,进行所需的处理。

举个例子,我们自定义一个合并器,它使用pair返回所有插槽的返回值之和以及其中的最大值:

boost:signals2_第7张图片
怎么用呢?我们在signal声明时需要使用第二个模板参数------合并器类型:

在这里插入图片描述
它相当于:
在这里插入图片描述

使用:

boost:signals2_第8张图片

也可以在构造是传入一个合并器实例,那么signal将会处理这个合并器的拷贝处理返回值。比如下面使用了一个有初值的合并器对象:

在这里插入图片描述

管理信号连接

当信号调用完插槽后,可能需要将插槽从信号中断开。

要断开一个插槽,插槽必须能够等价比较,对于函数对象来说就是定义一个operator==。

举个例子:
在这里插入图片描述

boost:signals2_第9张图片

但是这样很不方便,因为它必须知道与它连接的所有插槽的信息,还要求插槽对象必须是可以等价比较的。怎么办呢?

更灵活的管理信号连接

我们可以用connection对象来管理信号连接。

你可能感兴趣的:(C++,c++)