boost的signal和solt机制使用入门
signal-slot是一个非常方便的接口机制,在Qt和Gtk中广泛使用。boost也实现了一个signal-slot机制。
编译包含signal-slot的代码
使用signal-slot,必须包含头文件
#include <boost/signal.hpp>
signal-slot在boost中不是纯头文件,需要一个libboost_signals.so文件,在编译时,需要
g++ -o signal2 signal2.cpp -l boost_signals
初见signal-slot
从HelloWorld开始吧
首先定义hellword函数
void helloworld() {
std::cout << "Hello, World!(func)" << std::endl;
}
然后,定义signal对象
boost::signal<void ()>sig;
在main函数中使用
int main()
{
sig.connect(&helloworld);
sig();
}
sig()相当与emit。
除了直接的对象外,还可以使用函数对象
struct HelloWorld {
void operator() () const
{
std::cout << "Hello, World!" << std::endl;
}
};
在main函数中,这样使用
HelloWorld hello;
sig.connect(hello);
还可以使用bind,(请#include <boost/bind.hpp>)
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!
singal-slot的顺序
默认情况下,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 connection的管理
disconnection
signal disconnect方法
sig.connect(&helloworld);
....
sig.connect(&helloworld);
目前发现的只有函数可以这样做,函数对象,bind对象都不可以。
connection对象的disconnect方法
HelloWorld hello;
boost::signals::connection c = sig.connect(hello);
....
c.disconnect();
block slot
slot可以被暂时阻止,然后在恢复,如
HelloWorld hello;
boost::signals::connection c = sig.connect(hello);
.....
c.block();
sig();
....
c.unblock();
sig();
block和unblock都是boost::signals::connection对象的方法,需要首先得到这个connection。
在作用域范围内的slot
{
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的自动跟踪
考虑下面的代码
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 slot
带参数的signal
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;
}
定义和使用signal
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
带返回值的slot
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的值。
增加返回值处理器
如果这不是你想要的值,你可以增加新的返回值处理器来实现
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类型对象。这个例子中,它获取返回值中的最大值。
它是这样使用的
boost::signal<float (float x, float y), maximum<float> > sig;
......
.....
在 signal声明时,作为模板参数给出。
我们还可以收集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;
slot执行的结果,将被放在vector<float>对象中,并可以被访问。
这个返回值收集器在工作的时候,如果first和last没有被访问到,那么,slot就不会被触发。例如
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来完成的
如,下面的例子:
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类
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的联系
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;
}
};
使用方法:
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