1、signals2库

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

signals2位于名字空间boost::signals2,使用需要包含头文件"boost/signals2.hpp",而且在VC下编译signals2时,应该在stdafx.h中加入预声明#define _SCL_SECURE_NO_WARNINGS,或者在项目属性->C/C++->预处理器->预处理定义中添加_SCL_SECURE_NO_WARNINGS。

2、signal

在signals2中使用signal来表示信号,它是一个模板类。插槽可以是函数指针、函数对象、bind表达式、function对象。调用signals的成员函数connect()来将插槽连接到信号上,相当于为信号注册一个处理的handler。对signal对象调用()即为产生一个信号(signal提供了operator()),从而导致连接的所有插槽被调用。产生信号即调用signal的operator()的返回值是一个optional类型的对象,可以使用解引用操作符*来获得真正的返回值,而且默认情况下,这个返回值是最后被调用的插槽的返回值。signal会把参数传递给所有连接的插槽。

就像function一样,signal也是一个模板类,第一个模板参数是插槽函数类型签名,除了第一个模板参数外其他的模板参数都有缺省值,所以可以只传入第一个参数。

signal::connect()的第一个参数为一个插槽,第二个参数为插入的位置,插入位置决定了插槽的调用顺序,默认值为at_back往后插入,at_front为往前插入,返回值是一个connection对象,可以这个对象来管理连接,如断开连接(disconnect)、测试连接(connected)等。

其它成员函数:

num_slots()用来获得连接的插槽的个数,empty()用来判断连接的插槽数量是否为0。

combiner()用来获取合并器,set_combiner()用来设置合并器,不过一般是在signal构造函数中直接设置合并器。

disconnect()用来断开连接,disconnect_all_slots()用来断开所有连接的插槽,signal对象析构时会自动调用disconnect_all_slots()。

使用示例:

#include "boost/signals2.hpp"

int slots1(int n)
{
int iRet = n * n;
cout << "slot1 called, return value: " << iRet << endl;
return iRet;
} int slots2(int n, int x)
{
int iRet = n * n * x;
cout << "slot2 called, return value: " << iRet << endl;
return iRet;
} int slots3(int n)
{
int iRet = n * n;
cout << "slot3 called, return value: " << iRet << endl;
return iRet;
} int slots4(int n)
{
int iRet = n * n;
cout << "slot4 called, return value: " << iRet << endl;
return iRet;
} int main()
{
boost::signals2::signal<int(int)> sig; boost::signals2::connection con1 = sig.connect(slots1);
boost::signals2::connection con2 = sig.connect(bind(slots2, _1, ));
boost::signals2::connection con3 = sig.connect(slots3, boost::signals2::at_front);
boost::signals2::connection con4 = sig.connect(slots4); bool b = con4.connected();
con4.disconnect();
b = con4.connected(); int iRet = *sig();
cout << "return value: " << iRet << endl; return ;
}

程序输出为:

3 、signal的模板参数

前边我们说过,signal是一个模板类,它的第一个模板参数是插槽函数类型签名,其余模板参数都有缺省值。

①、signal<>的第二个模板参数

默认情况下,产生信号即调用signal对象的operator()的返回值是最后被调用的插槽的返回值,eg:

#include "boost/signals2.hpp"

template<int N>
class MySlots //模板函数对象类,可以用来生成一系列的插槽
{
public:
int operator()(int x)
{
cout << "slot" << N << " called" << endl;
return x * N;
}
}; int main()
{
boost::signals2::signal<int(int)> sig; boost::signals2::connection con1 = sig.connect(MySlots<>());
boost::signals2::connection con2 = sig.connect(MySlots<>());
boost::signals2::connection con4 = sig.connect(MySlots<>()); int iRet = *sig(); //iRet为30 return ;
}

signal<>的第二个模板参数被称为“合并器”,它是一个函数对象,可以用来自定义signal()的返回结果,即调用signal的operator()产生信号后返回值。上面说过,缺省的合并器会返回一个optional类型的对象,它是最后被调用的插槽的返回值,我们可以通过自定义合并器来将多个插槽的返回值合并为一个结果以返回给用户。合并器的两个模板参数是迭代器类型,利用他们可以遍历所有插槽的返回值,合并器的返回值由合并器对象的operator()返回值来确定。以下示例代码生成了一个自定义的合并器,并返回一个pair类型,first为所有插槽返回值之和,second为所有插槽返回值中的最大值:

#include "boost/signals2.hpp"

template<int N>
class MySlots //模板函数对象类,可以用来生成一系列的插槽
{
public:
int operator()(int x)
{
cout << "slot" << N << " called" << endl;
return x * N;
}
}; template<typename T>
class CCombiner //模板函数对象类
{
public:
typedef pair<T, T> result_type;
template<typename Iterator>
result_type operator()(Iterator begin, Iterator end)const
{
if (begin == end)
return result_type();
vector<T> vec(begin, end);
T sum = accumulate(vec.begin(), vec.end(), );
T max = *max_element(vec.begin(), vec.end()); return result_type(sum, max);
}
}; int main()
{
boost::signals2::signal<int(int), CCombiner<int>> sig; //在这里第二个模板参数我们传入CCombiner<int>类型即可,因为signal的构造函数里会自动生成一个CCombiner<int>实例 boost::signals2::connection con1 = sig.connect(MySlots<>());
boost::signals2::connection con2 = sig.connect(MySlots<>());
boost::signals2::connection con4 = sig.connect(MySlots<>()); auto ret = sig();
int sum = ret.first; //
int max = ret.second; // return ;
}

②、signal<>的第三个模板参数

signal<>的第三个模板参数是“插槽编组”的类型,默认为int类型,signal的connect()函数有一个可以指定插槽所属编组的重载函数,可以通过它们来设置插槽函数的调用顺序:各编组的调用顺序默认由组号从小到大决定,可以通过signal<>的第四个模板参数来改变排序方式。connect()时未被编组的插槽如果位置标志是at_front则在所有插槽调用之前调用,at_back为在所有插槽调用之后调用。

例如以下代码,插槽的调用顺序为:slots5, slots1, slots6, slots3, slots2, slots4

    boost::signals2::signal<void(int)> sig;
boost::signals2::connection con1 = sig.connect(, slots1);
boost::signals2::connection con1 = sig.connect(, slots6);
boost::signals2::connection con2 = sig.connect(, slots2);
boost::signals2::connection con3 = sig.connect(, slots3);
boost::signals2::connection con4 = sig.connect(slots4);
boost::signals2::connection con5 = sig.connect(slots5, boost::signals2::at_front); sig();

③、signal<>的第四个模板参数

signal<>的第四个模板参数用来指定编组的排序规则,默认是升序,因此要求signal<>的第三个模板参数即插槽编组的类型必须支持<或定义了operator<。

4、管理信号与插槽的连接

我们一般使用signal::connect()返回的connection对象来管理信号的连接,比如使用connection::disconnect()来断开连接,使用connection::connected()来检测是否连接,使用connection::blocked()函数来检测插槽是否被阻塞。connection是可拷贝可赋值的,它也重载了 比较操作符,所以可以直接作为序列容器或关联容器的元素。

还可以使用connection的子类scoped_connection对象来管理连接,它的额外作用是当scoped_connection对象离开作用域的时候自动断开连接。

shared_connection_block对象提供了阻塞插槽和信号的连接的方法block(),我们一般将一个connection对象赋给一个shared_connection_block对象,从而使这个shared_connection_block对象拥有对connection的管理权。解除阻塞使用unblock(),当shared_connection_block对象离开作用域的时候会自动调用unblock()来解除阻塞。

#include "boost/signals2.hpp"

template<int N>
class MySlots //模板函数对象类,可以用来生成一系列的插槽
{
public:
int operator()(int x)
{
cout << "slot" << N << " called" << endl;
return x * N;
}
}; int main()
{
boost::signals2::signal<int(int)> sig; boost::signals2::connection con1 = sig.connect(MySlots<>());
boost::signals2::connection con2 = sig.connect(MySlots<>());
boost::signals2::connection con4 = sig.connect(MySlots<>()); con1.disconnect(); //con1断开连接
assert(!con1.connected());
assert(sig.num_slots() == ); {
boost::signals2::scoped_connection con5 = sig.connect(MySlots<>());
assert(sig.num_slots() == );
}
assert(sig.num_slots() == ); //con5离开作用域,自动断开连接 {
boost::signals2::shared_connection_block cb(con4);
cb.block(); //con4被阻塞,将不会被调用
}
assert(!con4.blocked()); //cb离开作用域,con4自动解除阻塞 return ;
}

 5、插槽销毁的处理

如果插槽在与信号建立连接后又被销毁了,那么信号调用将发生未定义的行为。为了防止意外发生,在连接信号的时候我们不直接使用插槽,而是使用signal<>::slot_type()函数包装插槽,并使用成员函数track()来跟踪插槽,这样当插槽销毁的时候会自动断开连接,eg:

#include "boost/smart_ptr.hpp"
#include "boost/signals2.hpp" template<int N>
class MySlots //模板函数对象类,可以用来生成一系列的插槽
{
public:
int operator()(int x)
{
cout << "slot" << N << " called" << endl;
return x * N;
}
}; int main()
{
typedef boost::signals2::signal<int(int)> signal_t;
typedef signal_t::slot_type slot_t;
signal_t sig; boost::shared_ptr<MySlots<>> sp(new MySlots<>);
sig.connect(slot_t(ref(*sp)).track(sp)); sp.reset(); //插槽被销毁,自动关闭连接
assert(sig.num_slots() == );
sig(); //没有插槽会被调用 return ;
}

因为插槽可以是函数指针、函数对象、bind表达式、function对象,所以signal<>::slot_type()也支持函数指针、函数对象、bind表达式、function对象。signal<>::slot_type()的另一个特色是支持bind表达式相同的语法,这样就可以就地绑定函数,避免使用bind表达式的构造成本。eg:

#include "boost/smart_ptr.hpp"
#include "boost/function.hpp"
#include "boost/signals2.hpp" template<int N>
class MySlots //模板函数对象类,可以用来生成一系列的插槽
{
public:
int operator()(int x)
{
cout << "slot" << N << " called" << endl;
return x * N;
}
}; class MySlots2
{
public:
int operator()(int x, int y)
{
cout << x << ", " << y << endl;
return y;
}
typedef int result_type;
}; int main()
{
typedef boost::signals2::signal<int(int)> signal_t;
typedef signal_t::slot_type slot_t;
signal_t sig; boost::shared_ptr<MySlots<>> sp1(new MySlots<>);
boost::function<int(int)> func = ref(*sp1);
sig.connect(slot_t(func).track(sp1)); boost::shared_ptr<MySlots2> sp2(new MySlots2);
sig.connect(slot_t(MySlots2(), , _1).track(sp2)); sp1.reset(); //插槽被销毁,自动关闭连接
assert(sig.num_slots() == );
sig(); //只有MySlots2插槽会被调用 return ;
}

 6、让signal支持拷贝

signal是noncopyable的子类,所以它不能被拷贝或赋值,如果我们的类中含有signal成员变量,那么类的对象也不能被拷贝、赋值。如果确实需要拷贝含有signal数据成员的类的对象的话,可以保存signal的shared_ptr作为成员变量,这样多个类的对象之间可以通过这个shared_ptr来共享signal对象。eg:

#include "boost/smart_ptr.hpp"
#include "boost/signals2.hpp" class CDemoClass
{
public:
typedef boost::signals2::signal<void()> signal_t;
typedef boost::shared_ptr<signal_t> shared_ptr_t;
CDemoClass():m_spSig(new signal_t){} //构造函数
public:
shared_ptr_t m_spSig; //成员变量
}; void print()
{
cout << "print() called" << endl;
} class CTest
{
public:
CTest()
{
cout << << endl;
}
CTest(const CTest& t)
{
cout << << endl;
}
}; int main()
{
CDemoClass dc1;
int n1 = dc1.m_spSig.use_count(); //
CDemoClass dc2(dc1);
int n2 = dc2.m_spSig.use_count(); // dc1.m_spSig->connect(print);
(*(dc2.m_spSig))(); return ;
}

7、插槽的调度

“合并器”也可以用来设置当一个插槽的返回值满足特定条件后就终止其它插槽的调用,eg:

#include "boost/signals2.hpp"

int slots1(int n)
{
int iRet = n * n;
cout << "slot1 called, return value: " << iRet << endl;
return iRet;
} int slots2(int n)
{
int iRet = n;
cout << "slot2 called, value: " << iRet << endl;
return iRet;
} class CCombiner
{
public:
typedef bool result_type;
template<typename InputIterator>
result_type operator()(InputIterator begin, InputIterator end)const
{
while (begin != end)
{
if (*begin > )
return true;
} return false;
}
}; int main()
{
boost::signals2::signal<int(int), CCombiner> sig;
sig.connect(slots1);
sig.connect(slots2); sig();//slots2不会被调用 return ;
}

8、让插槽来管理连接

如果向让插槽来管理连接,那么可以给插槽函数传递当前connection对象来解决。为了完成这个功能,插槽的声明形式需要改变一下,使其能够接受connection对象。同时,在连接插槽的时候是调用signal::connect_extended(),而且必须使用extended_slot_type的类bind语法。eg:

#include "boost/signals2.hpp"

template<int N>
class CSlots
{
public:
typedef boost::signals2::connection connection_t;
int operator()(const connection_t& con, int x)
{
cout << "connection state: " << con.connected() << endl;
return x * N;
}
typedef int result_type;
}; int main()
{ typedef boost::signals2::signal<int(int)> signal_t;
typedef signal_t::extended_slot_type ex_slot_t;
signal_t sig; sig.connect_extended(ex_slot_t(CSlots<>(), _1, _2));
int iRet = *sig(); return ;
}

9、线程安全

signal可以很好的工作于多线程环境,它是线程安全的。同样地,connection和shared_connection_block也是线程安全的,但signal<>::slot_type()函数构造的对象不是线程安全的。

10、signal与function

当signal只连接了一个插槽的时候基本上可以与function替换。但需要注意二者的返回值不同,signal调用函数的时候使用optional对象作为返回值,真正的返回值需要使用解引用操作符*才能取得。function调用函数的时候直接返回被包装函数的返回值。

boost--signal的更多相关文章

  1. 观察者模式与Boost.Signals

      1)  观察者模式定义 略,各种设计模式的书上都有定义. 2)  观察者模式一般实现 观察者模式一般实现,都是“被观察者”保存一个“观察者”的列表,循环这个列表来通知“观察者”.代码,其中使用了b ...

  2. boost事件处理

    尽管这个库的名字乍一看好象有点误导,但实际上并不是如此. Boost.Signals 所实现的模式被命名为 '信号至插槽' (signal to slot).它基于下面概念:当相应的信号被发出时.相关 ...

  3. boost进程间通信经常使用开发一篇全(消息队列,共享内存,信号)

    本文概要: 敏捷开发大家想必知道并且评价甚高,缩短开发周期,提高开发质量.将大project独立为不同的小app开发,整个开发过程,程序可用可測,所以提高了总体的质量.基于这样的开发模式和开发理念,进 ...

  4. 比特币源码分析--C++11和boost库的应用

    比特币源码分析--C++11和boost库的应用     我们先停下探索比特币源码的步伐,来分析一下C++11和boost库在比特币源码中的应用.比特币是一个纯C++编写的项目,用到了C++11和bo ...

  5. boost 学习(1)

    智能指针的学习 中文教程网站 http://zh.highscore.de/cpp/boost/ 不过代码可能 由于BOOST 版本不同需要稍作修改 scoped_ptr 离开作用域则自动调用类析构函 ...

  6. boost库:事件处理

    boost库的signal所实现的模式被命名为信号至插槽,当对应的信号被发出时,相关联的插槽即被执行. #include <boost/signal.hpp> #include <i ...

  7. Win32API界面库 - Project wheels 工程基础部分完成

    离上次发博文过去了好久,先是要忙一个机器人的项目,然后就是部门的事情和考试周复习,然后就到了考试周,趁着复习的间隙,拾起了寒假时候抄的界面库,修掉了从前的bug. bug1 控件显示问题 当初抄这个库 ...

  8. C++0x简讯

    关于C++0x核心进展的一组简讯 刘未鹏 /文 C++的罗浮宫(http://blog.csdn.net/pongba) Concepts无疑是C++0x的杀手级特性之中的一个(也许称它“杀手级”另一 ...

  9. Linux环境编程相关的文章

    Linux环境编程相关的文章 好几年没有接触Linux环境下编程了,好多东西都有点生疏了.趁着现在有空打算把相关的一些技能重拾一下,顺手写一些相关的文章加深印象. 因为不是写书,也受到许多外部因素限制 ...

  10. Linux: 查看软件安装路径

    一.        Which 命令 Shell 的which 命令可以找出相关命令是否已经在搜索路径中. 如: [root@localhost ~]# which gcc /usr/bin/gcc ...

随机推荐

  1. ACM-ICPC 2018 南京赛区网络预赛 L.Magical Girl Haze(分层最短路)

    There are N cities in the country, and M directional roads from u to v(1≤u,v≤n). Every road has a di ...

  2. POJ 3169 Layout(差分约束+链式前向星+SPFA)

    描述 Like everyone else, cows like to stand close to their friends when queuing for feed. FJ has N (2 ...

  3. PAT L2-011 玩转二叉树(二叉树层序遍历)

    给定一棵二叉树的中序遍历和前序遍历,请你先将树做个镜面反转,再输出反转后的层序遍历的序列.所谓镜面反转,是指将所有非叶结点的左右孩子对换.这里假设键值都是互不相等的正整数. 输入格式: 输入第一行给出 ...

  4. ubuntu下tomcat的安装及注册成系统服务

    在ubuntu下tomcat的安装有两种方式,第一种是下载二进制文件,解压安装:第二种则是使用apt-get自动下载.这里不推荐第二种方法安装,因为这种方法安装会像天女散花一样把安装的文件散落在系统的 ...

  5. task 定时设置

    每天凌晨2点  0 0 2 * * ?和每天隔一小时 0 * */1 * * ? 例1:每隔5秒执行一次:*/5 * * * * ? 例2:每隔5分执行一次:0 */5 * * * ? 在26分.29 ...

  6. 编程,计算data段中的第一组数据的3次方,结果保存在后面一组dword单元中

    assume cs:code data segment dw ,,,,,,, dd ,,,,,,, data ends code segment start: mov ax,data mov ds,a ...

  7. HR 常用事务代码

    HR的键值权限查看:  oosb 删除人员 : pu01 查看人员主数据:PA30 对人员进行入职.离职.调岗等基本操作:PA40 查看HR中的公式的意思:PE04 查看HR中的工资项:PE02 创建 ...

  8. android使用Pull解析来自服务器的xml文件时出现错误以及解决方案

    直接上代码,代码中有详细注释: 1 public class CheckUpdateManager { 2 private static final String TAG = "CheckU ...

  9. Porsche PIWIS III with V37.250.020 Piwis 3 Software Update New Feature

    Porsche Piwis tester 3 PT3G VCI with V37.250.020 Piwis 3 Software unlimited license installed on Ful ...

  10. postfix 设置邮件头翻译,本域邮件不进行邮件头翻译,仅发送至外网的进行邮件头翻译?

    postfix 设置邮件头翻译,本域邮件不进行邮件头翻译,仅发送至外网的进行邮件头翻译? 现在设置的 smtp_generic_maps = hash:/etc/postfix/generic sen ...