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库可以简化对象间的通信关系,降低它们的耦合性,只需要在程序开始时把它们连接起来,之后的一切都会自动处理。