signals2基于Boost的另一个库signals,实现了线程安全的观察者模式。在signals2库中,观察者模式被称为信号/插槽(signals and slots),他是一种函数回调机制,一个信号关联了多个插槽,当信号发出时,所有关联它的插槽都会被调用。
许多成熟的软件系统都用到了这种信号/插槽机制(另一个常用的名称是事件处理机制:event/event handler),它可以很好地解耦一组互相协作的类,有的语言设置直接内建了对它的支持(如c#),signals2以库的形式为c++增加了这个重要的功能。
1、操作函数
signal最重要的操作函数是插槽管理connect()函数,它吧插槽连接到信号上,相当于为信号(事件)增加了一个处理的handler。
插槽可以是任意的可调用对象,包括函数指针、函数对象、以及它们的bind表达式和function对象,signal内部使用function作为容器来保存这些可调用对象。连接时可以指定组号也可以不指定组号,当信号发生时将依据组号的排序准则依次调用插槽函数。
如果连接成功connect()将返回一个connection,表示了信号与插槽之间的连接关系,它是一个轻量级的对象,可以处理两者间的连接,如断开、重连接、或者测试连接状态。
成员函数disconnect()可以断开插槽与信号的连接,它有两种形式:传递组号将断开该组的所有插槽,传递一个插槽对象将仅断开该插槽。函数disconnect_all_slots()可以一次性断开信号的所有插槽连接。
2、插槽的连接于使用
signal就像一个增强的function对象,它可以容纳(使用connect())多个符合模板参数中函数签名类型的函数(插槽),形成一个插槽链表,然后在信号发生时一起调用:
#include "stdafx.h"
#include "boost/utility/result_of.hpp"
#include "boost/typeof/typeof.hpp"
#include "boost/assign.hpp"
#include "boost/ref.hpp"
#include "boost/bind.hpp"
#include "boost/function.hpp"
#include "boost/signals2.hpp"
#include "iostream"
using namespace std; void slots1()
{
cout << "slots1 call" << endl;
} void slots2()
{
cout << "slots2 call" << endl;
} struct Hello
{
void operator()() const
{
std::cout << "Hello";
}
}; int _tmain(int argc, _TCHAR* argv[])
{
boost::signals2::signal<void()> sig;
sig.connect(&slots1);
sig.connect(&slots2); sig(); boost::signals2::signal<void ()> sig1; sig1.connect(Hello());
sig1(); return 0;
}
注:编译这个程序的时候,确保已经在stdafx.h中加入#define _SCL_SECURE_NO_WARNINGS或者,在C/C++----预处理器----预处理器定义中加上了_SCL_SECURE_NO_WARNINGS,否则会引发错误(或不能正确输出),下同。
在连接插槽时省了了connect()的第二个参数connect_position,它的缺省值是at_back,表示插槽将插入到信号插槽链表的尾部,因此slot2将在slot1之后被调用。
下面是使用和组号的情况:
#include "stdafx.h"
#include "boost/utility/result_of.hpp"
#include "boost/typeof/typeof.hpp"
#include "boost/assign.hpp"
#include "boost/ref.hpp"
#include "boost/bind.hpp"
#include "boost/function.hpp"
#include "boost/signals2.hpp"
#include "iostream"
using namespace std; template<int N>
struct Slot
{
void operator()()
{
cout << "Slot current N value is : " << N << endl;
}
}; int _tmain(int argc, _TCHAR* argv[])
{
boost::signals2::signal<void()> sig; sig.connect(Slot<1>(), boost::signals2::at_back); // 最后一个被调用
sig.connect(Slot<99>(), boost::signals2::at_front); // 第一个被调用 sig.connect(5, Slot<55>(), boost::signals2::at_back); // 组号5的最后一个
sig.connect(5, Slot<57>(), boost::signals2::at_front);// 组号5的第一个 sig.connect(10, Slot<100>());// 组号10该组只有一个 sig(); return 0;
}
3、信号的返回值
signal如function一样,不仅可以把输入参数转发给所以插槽,也可以传回插槽的返回值。默认情况下signal使用合并器optional_last_value<R>,它将使用optional对象返回最后被调用的插槽的返回值。
#include "stdafx.h"
#include "boost/utility/result_of.hpp"
#include "boost/typeof/typeof.hpp"
#include "boost/assign.hpp"
#include "boost/ref.hpp"
#include "boost/bind.hpp"
#include "boost/function.hpp"
#include "boost/signals2.hpp"
#include "iostream"
using namespace std; template<int N>
struct Slot
{
int operator()(int x)
{
cout << "Slot current N * x value is : " << endl; return (N * x);
}
}; int _tmain(int argc, _TCHAR* argv[])
{
boost::signals2::signal<int(int)> sig; sig.connect(Slot<10>());
sig.connect(Slot<100>());
cout << *sig(2) << endl;; return 0;
}
signal的operator()调用这时需要传入一个整数参数,这个参数会被signal存储一个拷贝,然后转发给各个插槽。最后signal将返回插槽链表末尾slots<100>()的计算结果,它是一个optional对象,必须用接引用操作符*来获得值(但你可以发现Slotcurrent N * x value is是输出了两次的)。
4、合并器
大多数时候,插槽的返回值都是有意义的,需要以某种方式处理多个插槽的返回值。
signal允许用户自定义合并器来处理插槽的返回值,把多个插槽的返回值合并为一个结果返回给用户。
#include "stdafx.h"
#include "boost/utility/result_of.hpp"
#include "boost/typeof/typeof.hpp"
#include "boost/assign.hpp"
#include "boost/ref.hpp"
#include "boost/bind.hpp"
#include "boost/function.hpp"
#include "boost/signals2.hpp"
#include "numeric"
#include "iostream"
using namespace std; template<int N>
struct Slot
{
int operator()(int x)
{
cout << "Slot current N * x value is : " << endl; return (N * x);
}
}; template<typename T>
class combiner
{
public:
typedef pair<T, T> result_type;
combiner(T t = T()) : v(t)
{ } template<typename InputIterator>
result_type operator()(InputIterator begin, InputIterator end) const
{
if (begin == end)
{
return result_type();
} vector<T> vec(begin, end);
T sum = accumulate(vec.begin(), vec.end(), v);
T max = *max_element(vec.begin(), vec.end()); return result_type(sum, max);
} private:
T v;
}; int _tmain(int argc, _TCHAR* argv[])
{
boost::signals2::signal<int(int), combiner<int> > sig; sig.connect(Slot<10>());
sig.connect(Slot<20>());
sig.connect(Slot<3>()); BOOST_AUTO(x, sig(2));
cout << x.first << ", " << x.second << endl; return 0;
}
combiner类的调用操作符operator()的返回值类型可以是任意类型,完全由用户指定,不一定必须是optional或者是插槽的返回值类型。operator()的模板参数InputIterator是插槽链表的返回值迭代器,可以使用它来遍历所有插槽的返回值,进行所需的处理。
当信号被调用时,signal会自动把引用操作转换为插槽调用,将调用给定的合并器的operator()逐个处理插槽的返回值,并最终返回合并器operator()的结果。
如果我们不适用signal的缺省构造函数,而是在构造signal时传入一个合并器的实例,那么signal将使用逐个合并器(的拷贝)处理返回值。例如,下面的代码使用了一个有初值的合并器对象,累加值从100开始:
signal<int(int),combiner<int> > sig(combiner<int>(100));
5、管理信号的连接
信号与插槽的链接并不要求是永久的,当信号调用玩插槽后,有可能需要把插槽从信号中断开,再连接到其他的信号上去。
signal可以用成员函数disconnect()断开一个或一组插槽,或者使用disconnect_all_slots()断开所有插槽连接,函数empty()和num_slots()用来检测信号当前插槽的连接状态。
要断开一个插槽,插槽必须能够进行登记等价比较,对于函数对象来说就是重载一个等价语义的operator==。下面对slots<N>增加一个等价比较:
#include "stdafx.h"
#include "boost/utility/result_of.hpp"
#include "boost/typeof/typeof.hpp"
#include "boost/assign.hpp"
#include "boost/ref.hpp"
#include "boost/bind.hpp"
#include "boost/function.hpp"
#include "boost/signals2.hpp"
#include "numeric"
#include "iostream"
using namespace std; template<int N>
struct Slot
{
int operator()(int x)
{
cout << "Slot current N * x value is : " << endl; return (N * x);
}
}; template<int N>
bool operator== (const Slot<N>& a, const Slot<N>& b)
{
return true;
} template<typename T>
class combiner
{
public:
typedef pair<T, T> result_type;
combiner(T t = T()) : v(t)
{ } template<typename InputIterator>
result_type operator()(InputIterator begin, InputIterator end) const
{
if (begin == end)
{
return result_type();
} vector<T> vec(begin, end);
T sum = accumulate(vec.begin(), vec.end(), v);
T max = *max_element(vec.begin(), vec.end()); return result_type(sum, max);
} private:
T v;
}; int _tmain(int argc, _TCHAR* argv[])
{
boost::signals2::signal<int(int)> sig;
// assert(sig.empty()); sig.connect(0, Slot<10>());
sig.connect(Slot<20>());
sig.connect(1, Slot<30>()); assert(sig.num_slots() == 3);
sig.disconnect(0); // assert(sig.num_slots() == 1);
sig.disconnect(Slot<30>()); // assert(sig.empty()); sig(2); return 0;
}
6、更灵活的管理信号连接
signals2库提供另外一种较为灵活的连接管理方式:使用connection对象。
每当signal使用connect()连接插槽时,他就会返回一个connection对象。connection对象像是信号与插槽连接关系的一个句柄(handle),可以管理链接:
#include "stdafx.h"
#include "boost/utility/result_of.hpp"
#include "boost/typeof/typeof.hpp"
#include "boost/assign.hpp"
#include "boost/ref.hpp"
#include "boost/bind.hpp"
#include "boost/function.hpp"
#include "boost/signals2.hpp"
#include "numeric"
#include "iostream"
using namespace std; template<int N>
struct Slot
{
int operator()(int x)
{
cout << "Slot current N * x value is : " << endl; return (N * x);
}
}; template<int N>
bool operator== (const Slot<N>& a, const Slot<N>& b)
{
return true;
} template<typename T>
class combiner
{
public:
typedef pair<T, T> result_type;
combiner(T t = T()) : v(t)
{ } template<typename InputIterator>
result_type operator()(InputIterator begin, InputIterator end) const
{
if (begin == end)
{
return result_type();
} vector<T> vec(begin, end);
T sum = accumulate(vec.begin(), vec.end(), v);
T max = *max_element(vec.begin(), vec.end()); return result_type(sum, max);
} private:
T v;
}; int _tmain(int argc, _TCHAR* argv[])
{
boost::signals2::signal<int(int)> sig;
boost::signals2::connection c1 = sig.connect(0, Slot<10>());
boost::signals2::connection c2 = sig.connect(0, Slot<10>());
boost::signals2::connection c3 = sig.connect(0, Slot<10>()); c1.disconnect();
assert(sig.num_slots() == 2);
assert(!c1.connected());
assert(c2.connected()); return 0;
}
另外一种连接管理对象是scoped_connection,它是connection的种类,提供类似scoped_ptr的RAII功能:插槽与信号的连接仅在作用域内生效,当离开作用域时连接就会自动断开。当需要临时连接信号时scoped_connection会非常有用:
#include "stdafx.h"
#include "boost/utility/result_of.hpp"
#include "boost/typeof/typeof.hpp"
#include "boost/assign.hpp"
#include "boost/ref.hpp"
#include "boost/bind.hpp"
#include "boost/function.hpp"
#include "boost/signals2.hpp"
#include "numeric"
#include "iostream"
using namespace std; template<int N>
struct Slot
{
int operator()(int x)
{
cout << "Slot current N * x value is : " << endl; return (N * x);
}
}; template<int N>
bool operator== (const Slot<N>& a, const Slot<N>& b)
{
return true;
} template<typename T>
class combiner
{
public:
typedef pair<T, T> result_type;
combiner(T t = T()) : v(t)
{ } template<typename InputIterator>
result_type operator()(InputIterator begin, InputIterator end) const
{
if (begin == end)
{
return result_type();
} vector<T> vec(begin, end);
T sum = accumulate(vec.begin(), vec.end(), v);
T max = *max_element(vec.begin(), vec.end()); return result_type(sum, max);
} private:
T v;
}; int _tmain(int argc, _TCHAR* argv[])
{
boost::signals2::signal<int(int)> sig; sig.connect(0, Slot<10>());
assert(sig.num_slots() == 1);
{
boost::signals2::scoped_connection sc = sig.connect(0, Slot<20>());
assert(sig.num_slots() == 2);
}
assert(sig.num_slots() == 1); return 0;
}
插槽与信号的连接一旦断开就不能再连接起来,connection不提供reconnet()这样的函数。但可以暂时地阻塞插槽与信号的连接,当信号发生时被阻塞的插槽将不会被调用,connection对象的blocked()函数可以检查插槽是否被阻塞。但被阻塞的插槽并没有断开与信号的链接,在需要的时候可以随时解除阻塞。
connection对象自身没有阻塞的功能,他需要一个辅助类shared_connection_block,它将阻塞connection对象,知道它被析构或者显式调用unblock()函数:
#include "stdafx.h"
#include "boost/utility/result_of.hpp"
#include "boost/typeof/typeof.hpp"
#include "boost/assign.hpp"
#include "boost/ref.hpp"
#include "boost/bind.hpp"
#include "boost/function.hpp"
#include "boost/signals2.hpp"
#include "numeric"
#include "iostream"
using namespace std; template<int N>
struct Slot
{
void operator()(int x)
{
cout << "Slot current N is : " << N << endl;
}
}; template<int N>
bool operator== (const Slot<N>& a, const Slot<N>& b)
{
return true;
} template<typename T>
class combiner
{
public:
typedef pair<T, T> result_type;
combiner(T t = T()) : v(t)
{ } template<typename InputIterator>
result_type operator()(InputIterator begin, InputIterator end) const
{
if (begin == end)
{
return result_type();
} vector<T> vec(begin, end);
T sum = accumulate(vec.begin(), vec.end(), v);
T max = *max_element(vec.begin(), vec.end()); return result_type(sum, max);
} private:
T v;
}; int _tmain(int argc, _TCHAR* argv[])
{
boost::signals2::signal<void(int)> sig; boost::signals2::connection c1 = sig.connect(0, Slot<10>());
boost::signals2::connection c2 = sig.connect(0, Slot<20>()); assert(sig.num_slots() == 2);
sig(2); cout << "begin blocking..." << endl;
{
boost::signals2::shared_connection_block block(c1);
assert(sig.num_slots() == 2);
assert(c1.blocked());
sig(2);
} cout << "end blocking.." << endl;
assert(!c1.blocked());
sig(2); return 0;
}
boost------signals2的使用1(Boost程序库完全开发指南)读书笔记的更多相关文章
-
boost------signals2的使用2(Boost程序库完全开发指南)读书笔记
1.应用于观察者模式 本小节将使用signals2开发一个完整的观察者模式示例程序,用来演示信号/插槽的用法.这个程序将模拟一个日常生活场景:客人按门铃,门铃响,护士开门,婴儿哭闹. Ring.h: ...
-
boost------function的使用(Boost程序库完全开发指南)读书笔记
function是一个函数对象的“容器”,概念上像是c/c++中函数指针类型的泛化,是一种“智能函数指针”.它以对象的形式封装了原始的函数指针或函数对象,能够容纳任意符合函数签名的可调用对象. 因此, ...
-
boost------asio库的使用1(Boost程序库完全开发指南)读书笔记
asio库基于操作系统提供的异步机制,采用前摄器设计模式(Proactor)实现了可移植的异步(或者同步)IO操作,而且并不要求多线程和锁定,有效地避免了多线程编程带来的诸多有害副作用. 目前asio ...
-
boost------asio库的使用2(Boost程序库完全开发指南)读书笔记
网络通信 asio库支持TCP.UDP.ICMP通信协议,它在名字空间boost::asio::ip里提供了大量的网络通信方面的函数和类,很好地封装了原始的Berkeley Socket Api,展现 ...
-
boost------bind的使用(Boost程序库完全开发指南)读书笔记
bind是c++98标准库中函数适配器bind1st/bind2nd的泛化和增强,可以适配任意的可调用类型,包括函数指针.函数引用.成员函数指针和函数对象. 1.工作原理 bind并不是一个单独的类或 ...
-
[转] boost------ref的使用(Boost程序库完全开发指南)读书笔记
http://blog.csdn.net/zengraoli/article/details/9663057 STL和Boost中的算法和函数大量使用了函数对象作为判断式或谓词参数,而这些参数都是传值 ...
-
boost------ref的使用(Boost程序库完全开发指南)读书笔记
STL和Boost中的算法和函数大量使用了函数对象作为判断式或谓词参数,而这些参数都是传值语义,算法或函数在内部保修函数对象的拷贝并使用,例如: #include "stdafx.h&quo ...
-
Boost程序库完全开发指南——深入C++“准”标准库(第3版)
内容简介 · · · · · · Boost 是一个功能强大.构造精巧.跨平台.开源并且完全免费的C++程序库,有着“C++‘准’标准库”的美誉. Boost 由C++标准委员会部分成员所设立的Bo ...
-
《Boost程序库完全开发指南》读书笔记-日期时间
●timer库 #include <boost\timer.hpp> #include <boost\progress.hpp> 1.timer类 // timer类的示例. ...
随机推荐
-
Spring.net 间接调用被AOP拦截的方法失效(无法进入aop的拦截方法)
.下面的tx要定义 <objects xmlns="http://www.springframework.net" xmlns:db="http://www.spr ...
-
EF实体框架之CodeFirst三
前两篇博客学习了数据库映射和表映射,今天学习下数据库初始化.种子数据.EF执行sql以及执行存储过程这几个知识. 一.数据库初始化策略 数据库初始化有4种策略 策略一:数据库不存在时重新创建数据库 D ...
-
DedeCMS 5.7 后门漏洞
简要描述: DedeCMS V5.7 SP1正式版 UTF-8 GBK版本疑似被植入一句话后门 前几日下载并不存在此代码 详细说明: shopcar.class.php被植入一句话@eval(file ...
-
【USACO 2.3.2】奶牛家谱
[题目描述] 农民约翰准备购买一群新奶牛.在这个新的奶牛群中,每一个母亲奶牛都生两小奶牛.这些奶牛间的关系可以用二叉树来表示.这些二叉树总共有N个节点(3 <= N < 200).这些二叉 ...
-
javascript函数作用域链之词法作用域
在开发语言中常见的作用域规则有 块级作用域和词法作用域 作用域 顾名思义就是起作用的区域 定义一变量后 ,可以在此范围作用的区域 一.块级作用域就是用一个块结构分割变量的访问区域 块即{ } 代 ...
-
js 序列化
Python 序列化 字符串 = json.dumps(对象) 对象转字符串 对象 = json.loads(字符串) 字符串转对象 Javascript 字符串 = JSON.stringif ...
-
高手养成计划基础篇-Linux第二季
高手养成计划基础篇-Linux第二季 本文来源:i春秋社区-分享你的技术,为安全加点温度 前言 前面我们学习了文件处理命令和文件搜索命令,简单的了解了一下Linux,但是仅仅了解这样还不行,遇 ...
-
maven web项目生成WebContent或WebRoot目录
本文为博主原创,转载请注明出处: 新建maven web工程时,自动生成的文件结构目录如下: 这个是maven web自动生成的目录结构,我想让其生成如java web工程的WebRoot 或WebC ...
-
vue 兼容IE报错解决方案
IE 页面空白 报错信息 此时页面一片空白 报错原因 Babel 默认只转换新的 JavaScript 语法(如箭头函数),而不转换新的 API ,比如 Iterator.Generator.Set. ...
-
〖Linux〗noip免费域名申请,及更新域名的API
1. 登录 http://www.noip.com2. 选择 Hosts/Redirects -- Add A Host3. 填写 期望的域名即可(如下图) 4. 更新域名的API: wget -q ...