boost的signal和solt機制使用入門

boost的signal和solt機制使用入門

 

signal-slot是一個非常方便的接口機制,在Qt和Gtk中廣泛使用。boost也實現了一個signal-slot機制。

 

編譯包含signal-slot的代碼

使用signal-slot,必須包含頭文件

[cpp] view plain copy

  1. #include <boost/signal.hpp>  

signal-slot在boost中不是純頭文件,需要一個libboost_signals.so文件,在編譯時,需要

[plain] view plain copy

  1. g++ -o signal2 signal2.cpp -l boost_signals  

 

初見signal-slot

從HelloWorld開始吧

首先定義hellword函數

[cpp] view plain copy

  1. void helloworld() {  
  2.     std::cout << "Hello, World!(func)" << std::endl;  
  3. }  


然後,定義signal對象

[cpp] view plain copy

  1. boost::signal<void ()>sig;  


在main函數中使用

[cpp] view plain copy

  1. int main()  
  2. {  
  3.     sig.connect(&helloworld);  
  4.     sig();  
  5. }  

sig()相當與emit。

 

除了直接的對象外,還可以使用函數對象

[cpp] view plain copy

  1. struct HelloWorld {  
  2.     void operator() () const  
  3.     {  
  4.         std::cout << "Hello, World!" << std::endl;  
  5.     }  
  6. };  


在main函數中,這樣使用

[cpp] view plain copy

  1. HelloWorld hello;  
  2. sig.connect(hello);  


還可以使用bind,(請#include <boost/bind.hpp>)

[cpp] view plain copy

  1. void printMore(const std::string& user)  
  2. {  
  3.     std::cout << user << " say: Hello World!\n";  
  4. }  

在main函數中,這樣使用

[cpp] view plain copy

  1. sig.connect(boost::bind(printMore, "Tom"));  
  2. sig.connect(boost::bind(printMore, "Jerry"));  

打印的結果是

[plain] view plain copy

  1. Tom say: Hello World!  
  2. Jerry say: Hello World!  

 

singal-slot的順序

默認情況下,signal-slot是按照添加順序進行的,例如

[cpp] view plain copy

  1. struct Hello {  
  2.     void operator() () const  
  3.     {  
  4.         std::cout << "Hello ";  
  5.     }  
  6. };  
  7. struct World {  
  8.     void operator() () const  
  9.     {  
  10.         std::cout << ", World" << std::endl;  
  11.     }  
  12. };  


如果這樣寫

[cpp] view plain copy

  1. sig.connect(Hello());  
  2. sig.connect(World());  

輸入的結果是

[plain] view plain copy

  1. Hello , World  

先調用了Hello,後調用了World

但是,如果這樣寫

[cpp] view plain copy

  1. sig.connect(1, World());  
  2. sig.connect(0, Hello());  


結果仍然同上面的一樣。

signal connection的管理

disconnection

signal disconnect方法

[cpp] view plain copy

  1. sig.connect(&helloworld);  
  2. ....  
  3. sig.connect(&helloworld);  


目前發現的只有函數可以這樣做,函數對象,bind對象都不可以。

 

connection對象的disconnect方法

[cpp] view plain copy

  1.     HelloWorld hello;  
  2.     boost::signals::connection c = sig.connect(hello);  
  3. ....  
  4.   
  5.     c.disconnect();  

 

block slot

slot可以被暫時阻止,然後在恢復,如

[cpp] view plain copy

  1.     HelloWorld hello;  
  2.     boost::signals::connection c = sig.connect(hello);  
  3. .....  
  4.     c.block();  
  5.     sig();  
  6. ....  
  7.     c.unblock();  
  8.     sig();  


block和unblock都是boost::signals::connection對象的方法,需要首先得到這個connection。

在作用域範圍內的slot

[cpp] view plain copy

  1. {  
  2.   boost::signals::scoped_connection c = sig.connect(ShortLived());  
  3.   sig(); // will call ShortLived function object  
  4. }  
  5. sig(); // ShortLived function object no longer connected to sig  


ShortLive只在作用域內起作用,如果離開了作用域,就不能起作用了。

slot的自動跟蹤

考慮下面的代碼

[cpp] view plain copy

  1. boost::signal<void (const std::string&)> deliverMsg;  
  2. void autoconnect()  
  3. {  
  4.     MessageArea * msgarea = new MessageArea();  
  5.   
  6.     deliverMsg.connect(boost::bind(&MessageArea::displayMessage, msgarea, _1));  
  7.   
  8.     deliverMsg("hello world!");  
  9.           
  10.     delete msgarea;  
  11.     //Oops, msgarea is deleted!  
  12.     deliverMsg("again!");  
  13.   
  14. }  

最後一個deliverMsg被調用時,msgarea已經被刪除了,通常情況下,這會引起崩潰。爲了避免這個問題,boost引入一個trackable對象。

請看MessageArea的聲明

[cpp] view plain copy

  1. struct MessageArea : public boost::signals::trackable  
  2. {  
  3. public:  
  4.     void displayMessage(const std::string& msg) {  
  5.         std::cout<<"** the message is: " << msg<<std::endl;  
  6.     }  
  7. };  

派生自boost::signals::trackable,就可以解決這個自動關閉的問題了!

autoconnect函數只會調用一次displayMessage。在delete msgarea發生後,deliverMsg對應的slot就被刪除了。

帶參數和返回值的signal slot

 

帶參數的signal

signal可以添加任意多參數的,比如這個例子

[cpp] view plain copy

  1. void print_sum(float x, float y)  
  2. {  
  3.     std::cout << "The sum is " << x + y << std::endl;  
  4. }  
  5.   
  6. void print_product(float x, float y)  
  7. {  
  8.     std::cout << "The product is " << x * y << std::endl;  
  9. }  
  10.   
  11.   
  12. void print_difference(float x, float y)  
  13. {  
  14.     std::cout << "The difference is " << x * y << std::endl;  
  15. }  


定義和使用signal

[cpp] view plain copy

  1. int main()  
  2. {  
  3.     boost::signal<void (float, float) > sig;  
  4.   
  5.     sig.connect(&print_sum);  
  6.     sig.connect(&print_product);  
  7.     sig.connect(&print_difference);  
  8.   
  9.     sig(5, 3);  
  10. }  

我們得到的結果,將是

[plain] view plain copy

  1. The sum is 8  
  2. The product is 15  
  3. The difference is 15  

 

帶返回值的slot

[cpp] view plain copy

  1. float product(float x, float y) { return x*y; }  
  2. float quotient(float x, float y) { return x/y; }  
  3. float sum(float x, float y) { return x+y; }  
  4. float difference(float x, float y) { return x-y; }  
  5.   
  6.   
  7. int main(void)  
  8. {  
  9.     boost::signal<float (float x, float y)> sig;  
  10.   
  11.     sig.connect(&product);  
  12.     sig.connect("ient);  
  13.     sig.connect(&sum);  
  14.     sig.connect(&difference);  
  15.   
  16.     std::cout << sig(5, 3) << std::endl;  
  17. }  


最後的結果是"2",這是最後一個slot difference的結果。signal默認返回最後一個slot的值。

 

增加返回值處理器

如果這不是你想要的值,你可以增加新的返回值處理器來實現

[cpp] view plain copy

  1. template<typename T>  
  2. struct maximum  
  3. {  
  4.     typedef T result_type;  
  5.     template<typename InputIterator>  
  6.     T operator()(InputIterator first, InputIterator last) const  
  7.     {  
  8.         if(first == last)  
  9.             return T();  
  10.   
  11.         T max_value = *first ++;  
  12.         while(first != last) {  
  13.             if(max_value < *first)  
  14.                 max_value = *first;  
  15.             ++first;  
  16.         }  
  17.         return max_value;  
  18.     }  
  19. };  


maximum是一個函數對象,它必須接收兩個參數 InputIterator first和last,返回T類型對象。這個例子中,它獲取返回值中的最大值。

 

它是這樣使用的

[cpp] view plain copy

  1.     boost::signal<float (float x, float y), maximum<float> > sig;  
  2. ......  
  3. .....  


在 signal聲明時,作爲模板參數給出。

 

我們還可以收集slot的返回值,這通過定義一個收集器實現

[cpp] view plain copy

  1. template<typename Container>  
  2. struct aggregate_values  
  3. {  
  4.     typedef Container result_type;  
  5.   
  6.     template<typename InputIterator>  
  7.     Container operator()(InputIterator first, InputIterator last) const  
  8.     {  
  9.         return Container(first, last);  
  10.     }  
  11. };  


這樣使用

[cpp] view plain copy

  1. boost::signal<float (float x, float y), aggregate_values<std::vector<float> > > sig2;  
  2.   
  3. sig2.connect("ient);  
  4. sig2.connect(&product);  
  5. sig2.connect(&sum);  
  6. sig2.connect(&difference);  
  7.   
  8. std::vector<float> results = sig2(5,3);  
  9. std::copy(results.begin(), results.end(),  
  10.         std::ostream_iterator<float>(std::cout, " "));  
  11. std::cout<<std::endl;  


slot執行的結果,將被放在vector<float>對象中,並可以被訪問。

 

這個返回值收集器在工作的時候,如果first和last沒有被訪問到,那麼,slot就不會被觸發。例如

[cpp] view plain copy

  1. template<typename T>  
  2. struct FirstResult  
  3. {  
  4.    template<class InputIterator>  
  5.    T operator()(InputIterator first, InputIterator last) {  
  6.         return *first;  
  7.    }  
  8. };  


這個收集器事實上,僅僅讓signal觸發了第一個slot,其餘的slot均沒有被觸發。因此,這個slot也可以作爲我們過濾slot的方法。

 

slot_type傳遞slot

slot和signal的聲明不會在一個地方(如果那樣,就沒有必要提供signal-slot機制了),這是,我們需要傳遞 slot對象,具體做法,是通過signal::slot_type來完成的

如,下面的例子:

[cpp] view plain copy

  1. class Button  
  2. {  
  3.     typedef boost::signal<void (int x, int y)> OnClick;  
  4.   
  5. public:  
  6.     void addOnClick(const OnClick::slot_type& slot);  
  7.   
  8.     void press(int x, int y) {  
  9.         onClick(x, y);  
  10.     }  
  11.   
  12. private:  
  13.     OnClick onClick;  
  14. };  
  15.   
  16. void Button::addOnClick(const OnClick::slot_type& slot)  
  17. {  
  18.    onClick.connect(slot);  
  19. }  

OnClick::slot_type定義了slot的類型,且可下面的使用

[cpp] view plain copy

  1. void printCoordinates(long x, long y)  
  2. {  
  3.     std::cout<<"Button Clicked @(" << x << "," << y <<")\n";  
  4. }  
  5.   
  6. void button_click_test()  
  7. {  
  8.     Button button;  
  9.   
  10.     button.addOnClick(&printCoordinates);  
  11.   
  12.     std::cout<<"===== button onclick test\n";  
  13.   
  14.     button.press(200,300);  
  15.     button.press(20,30);  
  16.     button.press(19,3);  
  17. }  

button.addOnClick可以直接接收任何能夠被signal.connect接受的參數。

來個綜合的例子:Document-View

定義Document類

[cpp] view plain copy

  1. class Document  
  2. {  
  3. public:  
  4.     typedef boost::signal<void (bool)> signal_t;  
  5.     typedef boost::signals::connection connect_t;  
  6.   
  7. public:  
  8.     Document(){ }  
  9.     connect_t connect(signal_t::slot_function_type subscriber)  
  10.     {  
  11.         return m_sig.connect(subscriber);  
  12.     }  
  13.   
  14.     void disconnect(connect_t subscriber)  
  15.     {  
  16.         subscriber.disconnect();  
  17.     }  
  18.   
  19.     void append(const char* s)  
  20.     {  
  21.         m_text += s;  
  22.         m_sig(true);  
  23.     }  
  24.   
  25.     const std::string& getText() const { return m_text; }  
  26.   
  27. private:  
  28.     signal_t m_sig;  
  29.     std::string m_text;  
  30. };  


注意到m_sig定義了一個信號對象。

View對象建立起和Document的聯繫

[cpp] view plain copy

  1. class View  
  2. {  
  3. public:  
  4.     View(Document& m)  
  5.         :m_doc(m)  
  6.     {  
  7.         m_conn = m_doc.connect(boost::bind(&View::refresh, this, _1));  
  8.     }  
  9.   
  10.     virtual ~View()  
  11.     {  
  12.         m_doc.disconnect(m_conn);  
  13.     }  
  14.   
  15.     virtual void refresh(bool bExtended) const = 0;  
  16.   
  17. protected:  
  18.     Document& m_doc;  
  19. private:  
  20.     Document::connect_t m_conn;  
  21. };  


兩個派生類TextView和HexView

[cpp] view plain copy

  1. class TextView : public View  
  2. {  
  3. public:  
  4.     TextView(Document& doc) : View(doc) { }  
  5.   
  6.     virtual void refresh(bool bExtended) const {  
  7.         std::cout << "TextView:" << m_doc.getText() << std::endl;  
  8.     }  
  9. };  
  10.   
  11. class HexView : public View  
  12. {  
  13. public:  
  14.     HexView(Document& doc) : View(doc) { }  
  15.   
  16.     virtual void refresh(bool bExtended) const {  
  17.         std::cout << "HexView: ";  
  18.         const std::string& s = m_doc.getText();  
  19.   
  20.         for(std::string::const_iterator it = s.begin();  
  21.             it != s.end(); ++it)  
  22.             std::cout << ' ' << std::hex << static_cast<int>(*it);  
  23.         std::cout << std::endl;  
  24.     }  
  25. };  


使用方法:

[cpp] view plain copy

  1. void document_view_test()  
  2. {  
  3.     Document doc;  
  4.     TextView v1(doc);  
  5.     HexView  v2(doc);  
  6.   
  7.     std::cout<<"================= document view test ===============\n";  
  8.   
  9.     doc.append("Hello world!\n");  
  10.     doc.append("Good!\n");  
  11.     doc.append("Happy!\n");  
  12. }  


該代碼運行後,可以看到如下的結果

[plain] view plain copy

  1. ================= document view test ===============  
  2. TextView:Hello world!  
  3.   
  4. HexView:  48 65 6c 6c 6f 20 77 6f 72 6c 64 21 a  
  5. TextView:Hello world!  
  6. Good!  
  7.   
  8. HexView:  48 65 6c 6c 6f 20 77 6f 72 6c 64 21 a 47 6f 6f 64 21 a  
  9. TextView:Hello world!  
  10. Good!  
  11. Happy!  
  12.   
  13. 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 
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章