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