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 
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值