1、应用于观察者模式
本小节将使用signals2开发一个完整的观察者模式示例程序,用来演示信号/插槽的用法。这个程序将模拟一个日常生活场景:客人按门铃,门铃响,护士开门,婴儿哭闹。
Ring.h:
#ifndef __RING_H__
#define __RING_H__
#include "iostream"
using namespace std;
#include "boost/signals2.hpp"
class Ring
{
public:
typedef boost::signals2::signal<void()> signal_t;
typedef signal_t::slot_type slot_t;
boost::signals2::connection connect(const slot_t& s)
{
return alarm.connect(s);
}
void Press()
{
cout << "Ring alarm..." << endl;
alarm();
}
private:
signal_t alarm;
};
#endif // !__RING_H__
Nurse.h:
#ifndef __NURSE_H__
#define __NURSE_H__
#include "boost/random.hpp"
extern char const nurse1[] = "Mary";
extern char const nurse2[] = "Kate";
typedef boost::variate_generator<boost::rand48, boost::uniform_smallint<> > bool_rand;
bool_rand g_rand(boost::rand48(time(0)), boost::uniform_smallint<>(0, 100));
template<char const* name>
class Nurse
{
public:
Nurse() : rand_(g_rand) { }
void Action()
{
cout << name;
if (rand_() > 30)
{
cout << " wake up and open door." << endl;
}
else
{
cout << " is sleeping..." << endl;
}
}
private:
bool_rand& rand_;
};
#endif // !__NURSE_H__
Baby.h:
#ifndef __BABY_H__
#define __BABY_H__
extern char const baby1[] = "Tom";
extern char const baby2[] = "Jerry";
template<char const* name>
class Baby
{
public:
Baby() : rand(g_rand) { }
void Action()
{
cout << "Baby " << name;
if (rand() > 50)
{
cout << " wake up and crying loudly..." << endl;
}
else
{
cout << " is sleeping sweetly..." << endl;
}
}
private:
bool_rand& rand;
};
#endif // !__BABY_H__
Guest.h:
#ifndef __GUEST_H__
#define __GUEST_H__
#include "Ring.h"
class Guest
{
public:
void Press(Ring& r)
{
cout << "A guest press the ring." << endl;
r.Press();
}
};
#endif // !__GUEST_H__
main:
#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;
#include "Ring.h"
#include "nurse.h"
#include "Baby.h"
#include "Guest.h"
int _tmain(int argc, _TCHAR* argv[])
{
// 声明门铃、护士、婴儿、客人等类的实例
Ring r;
Nurse<nurse1> n1;
Nurse<nurse2> n2;
Baby<baby1> b1;
Baby<baby2> b2;
Guest g;
// 把护士、婴儿、门铃连接起来
r.connect(boost::bind(&Nurse<nurse1>::Action, n1));
r.connect(boost::bind(&Nurse<nurse2>::Action, n2));
r.connect(boost::bind(&Baby<baby1>::Action, b1));
r.connect(boost::bind(&Baby<baby2>::Action, b2));
// 客人按动门铃,触发一系列的事件
g.Press(r);
return 0;
}
在程序中采用随机数来让护士和婴儿的行为具有不确定性。随机数的产生使用random库,为了方便使用把随机数发生器定义为全局变量:
typedef boost::variate_generator<boost::rand48, boost::uniform_smallint<> > bool_rand;
bool_rand g_rand(boost::rand48(time(0)), boost::uniform_smallint<>(0, 100));
然后我们实现护士类nurse,他有一个action()函数,根据随机数决定是惊醒开门还是继续睡觉。注意:他的模板参数,使用了charconst*作为护士的名字,因此实例化时字符串必须声明成extern(要不然别的地方找不到这个串)。
2、与C#的区别
signals2中的信号/插槽机制原理上类似于c#语言的event/deletegate机制。
但c#的deletegate的功能要比signals2弱,它要求精确的类型匹配,也没有合并器的概念,只能返回一个结果。
deletegate使用operator+=来链接event与deletegate,signals2则使用connect()函数。这是因为signals2在设计时认为operator+=并没有带来太多的好处,反而会导致连续使用+=链接、-=等其他语义问题。
不过我们可以稍微重载一下+=号来实现这种方式:
#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 Signature>
class SigEx
{
public:
typedef boost::signals2::signal<Signature> signal_type;
typedef typename signal_type::slot_type slot_type;
boost::signals2::connection connect(const slot_type& s)
{
return sig.connect(s);
}
boost::signals2::connection operator+=(const slot_type& s)
{
return connect(s);
}
typename signal_type::result_type operator()(typename signal_type::template arg<0>::type a0)
{
return sig(a0);
}
private:
signal_type sig;
};
int _tmain(int argc, _TCHAR* argv[])
{
SigEx<void(int)> sig;
sig += Slot<10>();
sig += Slot<10>();
sig(2);
return 0;
}
对前几张blog的总结
首先讨论了result_of库。它很小但功能很强大,使用了模板元编程技术,可以帮助确定一个调用表达式的返回类型,类似typeof库,主要用于泛型编程。
ref也是一个很小的库。它最初是tuple库的一部分,后来由于其重要性二被移出,成为了单独的库,而且也被收入了TR1标准草案。它能够包装对象的引用,变成一个可以被拷贝、赋值的普通对象,因此减少了昂贵的复制代价,标准库算法、tuple、bind、function等许多库都可以从ref库受益。但ref库实现有个较大的缺陷,不支持operator()重载(函数调用),通过更改源文件,做出了一个示范性质的实现,它可以配合标准库算法和其他库组件正常工作。
bind是一个功能强大的函数绑定期。它可以绑定任何可调用对象,搭配标准算法可以获得灵活操作容器内元素的强大功能。但bind过于强大也是个弱点。程序员学会bind的用法后往往会倾向于总是用bind解法,而忘记代码的清晰易读才是最重要的。
function库是函数指针的泛化,可以存储任意可调用的对象,因此function库经常配合bind使用,它可以存储bind表达式的结果,以备之后调用。
最后是signals2库,它综合运用了前四个组件,使用了信号/插槽机制,是观察者设计模式的一个具体应用,也是一个功能强大的回调框架。使用signals2库可以简化对象间的通信关系,降低它们的耦合性,只需要在程序开始时把它们连接起来,之后的一切都会自动处理。