boost的signal和solt機制使用入門
signal-slot是一個非常方便的接口機制,在Qt和Gtk中廣泛使用。boost也實現了一個signal-slot機制。
編譯包含signal-slot的代碼
使用signal-slot,必須包含頭文件
[cpp] view plain copy
- #include <boost/signal.hpp>
signal-slot在boost中不是純頭文件,需要一個libboost_signals.so文件,在編譯時,需要
[plain] view plain copy
- g++ -o signal2 signal2.cpp -l boost_signals
初見signal-slot
從HelloWorld開始吧
首先定義hellword函數
[cpp] view plain copy
- void helloworld() {
- std::cout << "Hello, World!(func)" << std::endl;
- }
然後,定義signal對象
[cpp] view plain copy
- boost::signal<void ()>sig;
在main函數中使用
[cpp] view plain copy
- int main()
- {
- sig.connect(&helloworld);
- sig();
- }
sig()相當與emit。
除了直接的對象外,還可以使用函數對象
[cpp] view plain copy
- struct HelloWorld {
- void operator() () const
- {
- std::cout << "Hello, World!" << std::endl;
- }
- };
在main函數中,這樣使用
[cpp] view plain copy
- HelloWorld hello;
- sig.connect(hello);
還可以使用bind,(請#include <boost/bind.hpp>)
[cpp] view plain copy
- void printMore(const std::string& user)
- {
- std::cout << user << " say: Hello World!\n";
- }
在main函數中,這樣使用
[cpp] view plain copy
- sig.connect(boost::bind(printMore, "Tom"));
- sig.connect(boost::bind(printMore, "Jerry"));
打印的結果是
[plain] view plain copy
- Tom say: Hello World!
- Jerry say: Hello World!
singal-slot的順序
默認情況下,signal-slot是按照添加順序進行的,例如
[cpp] view plain copy
- struct Hello {
- void operator() () const
- {
- std::cout << "Hello ";
- }
- };
- struct World {
- void operator() () const
- {
- std::cout << ", World" << std::endl;
- }
- };
如果這樣寫
[cpp] view plain copy
- sig.connect(Hello());
- sig.connect(World());
輸入的結果是
[plain] view plain copy
- Hello , World
先調用了Hello,後調用了World
但是,如果這樣寫
[cpp] view plain copy
- sig.connect(1, World());
- sig.connect(0, Hello());
結果仍然同上面的一樣。
signal connection的管理
disconnection
signal disconnect方法
[cpp] view plain copy
- sig.connect(&helloworld);
- ....
- sig.connect(&helloworld);
目前發現的只有函數可以這樣做,函數對象,bind對象都不可以。
connection對象的disconnect方法
[cpp] view plain copy
- HelloWorld hello;
- boost::signals::connection c = sig.connect(hello);
- ....
- c.disconnect();
block slot
slot可以被暫時阻止,然後在恢復,如
[cpp] view plain copy
- HelloWorld hello;
- boost::signals::connection c = sig.connect(hello);
- .....
- c.block();
- sig();
- ....
- c.unblock();
- sig();
block和unblock都是boost::signals::connection對象的方法,需要首先得到這個connection。
在作用域範圍內的slot
[cpp] view plain copy
- {
- 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的自動跟蹤
考慮下面的代碼
[cpp] view plain copy
- 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的聲明
[cpp] view plain copy
- 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可以添加任意多參數的,比如這個例子
[cpp] view plain copy
- 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
[cpp] view plain copy
- int main()
- {
- boost::signal<void (float, float) > sig;
- sig.connect(&print_sum);
- sig.connect(&print_product);
- sig.connect(&print_difference);
- sig(5, 3);
- }
我們得到的結果,將是
[plain] view plain copy
- The sum is 8
- The product is 15
- The difference is 15
帶返回值的slot
[cpp] view plain copy
- 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的值。
增加返回值處理器
如果這不是你想要的值,你可以增加新的返回值處理器來實現
[cpp] view plain copy
- 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類型對象。這個例子中,它獲取返回值中的最大值。
它是這樣使用的
[cpp] view plain copy
- boost::signal<float (float x, float y), maximum<float> > sig;
- ......
- .....
在 signal聲明時,作爲模板參數給出。
我們還可以收集slot的返回值,這通過定義一個收集器實現
[cpp] view plain copy
- template<typename Container>
- struct aggregate_values
- {
- typedef Container result_type;
- template<typename InputIterator>
- Container operator()(InputIterator first, InputIterator last) const
- {
- return Container(first, last);
- }
- };
這樣使用
[cpp] view plain copy
- 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就不會被觸發。例如
[cpp] view plain copy
- 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來完成的
如,下面的例子:
[cpp] view plain copy
- 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的類型,且可下面的使用
[cpp] view plain copy
- 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類
[cpp] view plain copy
- 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的聯繫
[cpp] view plain copy
- 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
[cpp] view plain copy
- 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;
- }
- };
使用方法:
[cpp] view plain copy
- 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");
- }
該代碼運行後,可以看到如下的結果
[plain] view plain copy
- ================= document view test ===============
- TextView:Hello world!
- HexView: 48 65 6c 6c 6f 20 77 6f 72 6c 64 21 a
- TextView:Hello world!
- Good!
- HexView: 48 65 6c 6c 6f 20 77 6f 72 6c 64 21 a 47 6f 6f 64 21 a
- TextView: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