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