本小節將使用signals2開發一個完整的觀察者模式示例程序,用來演示信號/插槽的用法。這個程序將模擬一個平常生活場景:客人按門鈴,門鈴響,護士開門,嬰兒哭鬧。ios
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:c#
#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(要否則別的地方找不到這個串)。dom
signals2中的信號/插槽機制原理上相似於c#語言的event/deletegate機制。函數
但c#的deletegate的功能要比signals2弱,它要求精確的類型匹配,也沒有合併器的概念,只能返回一個結果。spa
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; }
首先討論了result_of庫。它很小但功能很強大,使用了模板元編程技術,能夠幫助肯定一個調用表達式的返回類型,相似typeof庫,主要用於泛型編程。
ref也是一個很小的庫。它最初是tuple庫的一部分,後來因爲其重要性二被移出,成爲了單獨的庫,並且也被收入了TR1標準草案。它可以包裝對象的引用,變成一個能夠被拷貝、賦值的普通對象,所以減小了昂貴的複製代價,標準庫算法、tuple、bind、function等許多庫均可以從ref庫受益。但ref庫實現有個較大的缺陷,不支持operator()重載(函數調用),經過更改源文件,作出了一個示範性質的實現,它能夠配合標準庫算法和其餘庫組件正常工做。
bind是一個功能強大的函數綁按期。它能夠綁定任何可調用對象,搭配標準算法能夠得到靈活操做容器內元素的強大功能。但bind過於強大也是個弱點。程序員學會bind的用法後每每會傾向於老是用bind解法,而忘記代碼的清晰易讀纔是最重要的。
function庫是函數指針的泛化,能夠存儲任意可調用的對象,所以function庫常常配合bind使用,它能夠存儲bind表達式的結果,以備以後調用。
最後是signals2庫,它綜合運用了前四個組件,使用了信號/插槽機制,是觀察者設計模式的一個具體應用,也是一個功能強大的回調框架。使用signals2庫能夠簡化對象間的通訊關係,下降它們的耦合性,只須要在程序開始時把它們鏈接起來,以後的一切都會自動處理。