手记

boost的signal和solt机制使用入门

boost的signal和solt机制使用入门


signal-slot是一个非常方便的接口机制,在Qt和Gtk中广泛使用。boost也实现了一个signal-slot机制。


编译包含signal-slot的代码

使用signal-slot,必须包含头文件 

  1. #include <boost/signal.hpp>  

signal-slot在boost中不是纯头文件,需要一个libboost_signals.so文件,在编译时,需要

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


初见signal-slot

从HelloWorld开始吧

首先定义hellword函数

  1. void helloworld() {  

  2.     std::cout << "Hello, World!(func)" << std::endl;  

  3. }  


然后,定义signal对象

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


在main函数中使用

  1. int main()  

  2. {  

  3.     sig.connect(&helloworld);  

  4.     sig();  

  5. }  

sig()相当与emit。


除了直接的对象外,还可以使用函数对象

  1. struct HelloWorld {  

  2.     void operator() () const  

  3.     {  

  4.         std::cout << "Hello, World!" << std::endl;  

  5.     }  

  6. };  


在main函数中,这样使用

  1. HelloWorld hello;  

  2. sig.connect(hello);  


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

  1. void printMore(const std::string& user)  

  2. {  

  3.     std::cout << user << " say: Hello World!\n";  

  4. }  

在main函数中,这样使用

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

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

打印的结果是

  1. Tom say: Hello World!  

  2. Jerry say: Hello World!  


singal-slot的顺序

默认情况下,signal-slot是按照添加顺序进行的,例如

  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. };  


如果这样写

  1. sig.connect(Hello());  

  2. sig.connect(World());  

输入的结果是

  1. Hello , World  

先调用了Hello,后调用了World

但是,如果这样写

  1. sig.connect(1, World());  

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


结果仍然同上面的一样。

signal connection的管理

disconnection

signal disconnect方法

  1. sig.connect(&helloworld);  

  2. ....  

  3. sig.connect(&helloworld);  


目前发现的只有函数可以这样做,函数对象,bind对象都不可以。


connection对象的disconnect方法

  1.     HelloWorld hello;  

  2.     boost::signals::connection c = sig.connect(hello);  

  3. ....  

  4.   

  5.     c.disconnect();  


block slot

slot可以被暂时阻止,然后在恢复,如

  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

  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的自动跟踪

考虑下面的代码

  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的声明

  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可以添加任意多参数的,比如这个例子

  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

  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. }  

我们得到的结果,将是

  1. The sum is 8  

  2. The product is 15  

  3. The difference is 15  


带返回值的slot

  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的值。


增加返回值处理器

如果这不是你想要的值,你可以增加新的返回值处理器来实现

  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类型对象。这个例子中,它获取返回值中的最大值。


它是这样使用的

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

  2. ......  

  3. .....  


在 signal声明时,作为模板参数给出。


我们还可以收集slot的返回值,这通过定义一个收集器实现

  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. };  


这样使用

  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就不会被触发。例如

  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来完成的

如,下面的例子:

  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类

  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的联系

  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. };  


使用方法:

  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. }  


该代码运行后,可以看到如下的结果

  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 

原文出处

0人推荐
随时随地看视频
慕课网APP