本文根據boost的教程文檔整理。
signal-slot是一個非常方便的介面機制,在Qt和Gtk中廣泛使用。boost也實現了一個signal-slot機制。
編譯包含signal-slot的代碼
使用signal-slot,必須包含標頭檔
#include <boost/signal.hpp>
signal-slot在boost中不是純標頭檔,需要一個libboost_signals.so檔案,在編譯時間,需要
g++ -o signal2 signal2.cpp -l boost_signals
初見signal-slot
從HelloWorld開始吧
首先定義hellword函數
void helloworld() { std::cout << "Hello, World!(func)" << std::endl;}
然後,定義signal對象
boost::signal<void ()>sig;
在main函數中使用
int main(){ sig.connect(&helloworld); sig();}
sig()相當與emit。
除了直接的對象外,還可以使用函數對象
struct HelloWorld { void operator() () const { std::cout << "Hello, World!" << std::endl; }};
在main函數中,這樣使用
HelloWorld hello; sig.connect(hello);
還可以使用bind,(請#include <boost/bind.hpp>)
void printMore(const std::string& user){ std::cout << user << " say: Hello World!\n";}
在main函數中,這樣使用
sig.connect(boost::bind(printMore, "Tom")); sig.connect(boost::bind(printMore, "Jerry"));
列印的結果是
Tom say: Hello World!Jerry say: Hello World!
singal-slot的順序
預設情況下,signal-slot是按照添加順序進行的,例如
struct Hello { void operator() () const { std::cout << "Hello "; }};struct World { void operator() () const { std::cout << ", World" << std::endl; }};
如果這樣寫
sig.connect(Hello()); sig.connect(World());
輸入的結果是
Hello , World
先調用了Hello,後調用了World
但是,如果這樣寫
sig.connect(1, World()); sig.connect(0, Hello());
結果仍然同上面的一樣。
signal connection的管理disconnection
signal disconnect方法
sig.connect(&helloworld);....sig.connect(&helloworld);
目前發現的只有函數可以這樣做,函數對象,bind對象都不可以。
connection對象的disconnect方法
HelloWorld hello; boost::signals::connection c = sig.connect(hello);.... c.disconnect();
block slot
slot可以被暫時阻止,然後在恢複,如
HelloWorld hello; boost::signals::connection c = sig.connect(hello);..... c.block(); sig();.... c.unblock(); sig();
block和unblock都是boost::signals::connection對象的方法,需要首先得到這個connection。
在範圍範圍內的slot
{ boost::signals::scoped_connection c = sig.connect(ShortLived()); sig(); // will call ShortLived function object}sig(); // ShortLived function object no longer connected to sig
ShortLive只在範圍內起作用,如果離開了範圍,就不能起作用了。
slot的自動跟蹤
考慮下面的代碼
boost::signal<void (const std::string&)> deliverMsg;void autoconnect(){ MessageArea * msgarea = new MessageArea(); deliverMsg.connect(boost::bind(&MessageArea::displayMessage, msgarea, _1)); deliverMsg("hello world!"); delete msgarea; //Oops, msgarea is deleted! deliverMsg("again!");}
最後一個deliverMsg被調用時,msgarea已經被刪除了,通常情況下,這會引起崩潰。為了避免這個問題,boost引入一個trackable對象。
請看MessageArea的聲明
struct MessageArea : public boost::signals::trackable{public: void displayMessage(const std::string& msg) { std::cout<<"** the message is: " << msg<<std::endl; }};
派生自boost::signals::trackable,就可以解決這個自動關閉的問題了!
autoconnect函數只會調用一次displayMessage。在delete msgarea發生後,deliverMsg對應的slot就被刪除了。
帶參數和傳回值的signal slot帶參數的signal
signal可以添加任意多參數的,比如這個例子
void print_sum(float x, float y){ std::cout << "The sum is " << x + y << std::endl;}void print_product(float x, float y){ std::cout << "The product is " << x * y << std::endl;}void print_difference(float x, float y){ std::cout << "The difference is " << x * y << std::endl;}
定義和使用signal
int main(){ boost::signal<void (float, float) > sig; sig.connect(&print_sum); sig.connect(&print_product); sig.connect(&print_difference); sig(5, 3);}
我們得到的結果,將是
The sum is 8The product is 15The difference is 15
帶傳回值的slot
float product(float x, float y) { return x*y; }float quotient(float x, float y) { return x/y; }float sum(float x, float y) { return x+y; }float difference(float x, float y) { return x-y; }int main(void){ boost::signal<float (float x, float y)> sig; sig.connect(&product); sig.connect("ient); sig.connect(&sum); sig.connect(&difference); std::cout << sig(5, 3) << std::endl;}
最後的結果是"2",這是最後一個slot difference的結果。signal預設返回最後一個slot的值。
增加傳回值處理器
如果這不是你想要的值,你可以增加新的傳回值處理器來實現
template<typename T>struct maximum{ typedef T result_type; template<typename InputIterator> T operator()(InputIterator first, InputIterator last) const { if(first == last) return T(); T max_value = *first ++; while(first != last) { if(max_value < *first) max_value = *first; ++first; } return max_value; }};
maximum是一個函數對象,它必須接收兩個參數 InputIterator first和last,返回T類型對象。這個例子中,它擷取傳回值中的最大值。
它是這樣使用的
boost::signal<float (float x, float y), maximum<float> > sig;...........
在 signal聲明時,作為模板參數給出。
我們還可以收集slot的傳回值,這通過定義一個收集器實現
template<typename Container>struct aggregate_values{ typedef Container result_type; template<typename InputIterator> Container operator()(InputIterator first, InputIterator last) const { return Container(first, last); }};
這樣使用
boost::signal<float (float x, float y), aggregate_values<std::vector<float> > > sig2; sig2.connect("ient); sig2.connect(&product); sig2.connect(&sum); sig2.connect(&difference); std::vector<float> results = sig2(5,3); std::copy(results.begin(), results.end(), std::ostream_iterator<float>(std::cout, " ")); std::cout<<std::endl;
slot執行的結果,將被放在vector<float>對象中,並可以被訪問。
這個傳回值收集器在工作的時候,如果first和last沒有被訪問到,那麼,slot就不會被觸發。例如
template<typename T>struct FirstResult{ template<class InputIterator> T operator()(InputIterator first, InputIterator last) { return *first; }};
這個收集器事實上,僅僅讓signal觸發了第一個slot,其餘的slot均沒有被觸發。因此,這個slot也可以作為我們過濾slot的方法。
slot_type傳遞slot
slot和signal的聲明不會在一個地方(如果那樣,就沒有必要提供signal-slot機制了),這是,我們需要傳遞 slot對象,具體做法,是通過signal::slot_type來完成的
如,下面的例子:
class Button{ typedef boost::signal<void (int x, int y)> OnClick;public: void addOnClick(const OnClick::slot_type& slot); void press(int x, int y) { onClick(x, y); }private: OnClick onClick;};void Button::addOnClick(const OnClick::slot_type& slot){ onClick.connect(slot);}
OnClick::slot_type定義了slot的類型,且可下面的使用
void printCoordinates(long x, long y){ std::cout<<"Button Clicked @(" << x << "," << y <<")\n";}void button_click_test(){ Button button; button.addOnClick(&printCoordinates); std::cout<<"===== button onclick test\n"; button.press(200,300); button.press(20,30); button.press(19,3);}
button.addOnClick可以直接接收任何能夠被signal.connect接受的參數。
來個綜合的例子:Document-View
定義Document類
class Document{public: typedef boost::signal<void (bool)> signal_t; typedef boost::signals::connection connect_t;public: Document(){ } connect_t connect(signal_t::slot_function_type subscriber) { return m_sig.connect(subscriber); } void disconnect(connect_t subscriber) { subscriber.disconnect(); } void append(const char* s) { m_text += s; m_sig(true); } const std::string& getText() const { return m_text; }private: signal_t m_sig; std::string m_text;};
注意到m_sig定義了一個訊號對象。
View對象建立起和Document的聯絡
class View{public: View(Document& m) :m_doc(m) { m_conn = m_doc.connect(boost::bind(&View::refresh, this, _1)); } virtual ~View() { m_doc.disconnect(m_conn); } virtual void refresh(bool bExtended) const = 0;protected: Document& m_doc;private: Document::connect_t m_conn;};
兩個衍生類別TextView和HexView
class TextView : public View{public: TextView(Document& doc) : View(doc) { } virtual void refresh(bool bExtended) const { std::cout << "TextView:" << m_doc.getText() << std::endl; }};class HexView : public View{public: HexView(Document& doc) : View(doc) { } virtual void refresh(bool bExtended) const { std::cout << "HexView: "; const std::string& s = m_doc.getText(); for(std::string::const_iterator it = s.begin(); it != s.end(); ++it) std::cout << ' ' << std::hex << static_cast<int>(*it); std::cout << std::endl; }};
使用方法:
void document_view_test(){ Document doc; TextView v1(doc); HexView v2(doc); std::cout<<"================= document view test ===============\n"; doc.append("Hello world!\n"); doc.append("Good!\n"); doc.append("Happy!\n");}
該代碼運行後,可以看到如下的結果
================= document view test ===============TextView:Hello world!HexView: 48 65 6c 6c 6f 20 77 6f 72 6c 64 21 aTextView:Hello world!Good!HexView: 48 65 6c 6c 6f 20 77 6f 72 6c 64 21 a 47 6f 6f 64 21 aTextView:Hello world!Good!Happy!HexView: 48 65 6c 6c 6f 20 77 6f 72 6c 64 21 a 47 6f 6f 64 21 a 48 61 70 70 79 21 a