本文根据boost的教程文档整理。
signal-slot是一个非常方便的接口机制,在Qt和Gtk中广泛使用。boost也实现了一个signal-slot机制。
使用signal-slot,必须包含头文件
#include <boost/signal.hpp>
signal-slot在boost中不是纯头文件,需要一个libboost_signals.so文件,在编译时,需要
g++ -o signal2 signal2.cpp -l boost_signals
从HelloWorld开始吧
首先定义hellword函数
void helloworld() { std::cout << "Hello, World!(func)" << std::endl; }
boost::signal<void ()>sig;
int main() { sig.connect(&helloworld); sig(); }sig()相当与emit。
除了直接的对象外,还可以使用函数对象
struct HelloWorld { void operator() () const { std::cout << "Hello, World!" << std::endl; } };
HelloWorld hello; sig.connect(hello);
void printMore(const std::string& user) { std::cout << user << " say: Hello World!\n"; }在main函数中,这样使用
sig.connect(boost::bind(printMore, "Tom")); sig.connect(boost::bind(printMore, "Jerry"));打印的结果是
Tom say: Hello World! Jerry say: Hello World!
默认情况下,signal-slot是按照添加顺序进行的,例如
struct Hello { void operator() () const { std::cout << "Hello "; } }; struct World { void operator() () const { std::cout << ", World" << std::endl; } };
sig.connect(Hello()); sig.connect(World());输入的结果是
Hello , World先调用了Hello,后调用了World
但是,如果这样写
sig.connect(1, World()); sig.connect(0, Hello());
signal disconnect方法
sig.connect(&helloworld); .... sig.connect(&helloworld);
connection对象的disconnect方法
HelloWorld hello; boost::signals::connection c = sig.connect(hello); .... c.disconnect();
slot可以被暂时阻止,然后在恢复,如
HelloWorld hello; boost::signals::connection c = sig.connect(hello); ..... c.block(); sig(); .... c.unblock(); sig();
{ boost::signals::scoped_connection c = sig.connect(ShortLived()); sig(); // will call ShortLived function object } sig(); // ShortLived function object no longer connected to sig
考虑下面的代码
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的声明
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可以添加任意多参数的,比如这个例子
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; }
int main() { boost::signal<void (float, float) > sig; sig.connect(&print_sum); sig.connect(&print_product); sig.connect(&print_difference); sig(5, 3); }我们得到的结果,将是
The sum is 8 The product is 15 The difference is 15
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; }
如果这不是你想要的值,你可以增加新的返回值处理器来实现
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; } };
它是这样使用的
boost::signal<float (float x, float y), maximum<float> > sig; ...... .....
我们还可以收集slot的返回值,这通过定义一个收集器实现
template<typename Container> struct aggregate_values { typedef Container result_type; template<typename InputIterator> Container operator()(InputIterator first, InputIterator last) const { return Container(first, last); } };
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;
这个返回值收集器在工作的时候,如果first和last没有被访问到,那么,slot就不会被触发。例如
template<typename T> struct FirstResult { template<class InputIterator> T operator()(InputIterator first, InputIterator last) { return *first; } };
slot和signal的声明不会在一个地方(如果那样,就没有必要提供signal-slot机制了),这是,我们需要传递 slot对象,具体做法,是通过signal::slot_type来完成的
如,下面的例子:
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的类型,且可下面的使用
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类
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; };
View对象建立起和Document的联系
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; };
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; } };
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"); }
================= 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