signal-slot是一个非常方便的接口机制,在Qt和Gtk中广泛使用。boost也实现了一个signal-slot机制。
使用signal-slot,必须包含头文件
#include
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;
}
然后,定义signal对象
boost::signal
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
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);
目前发现的只有函数可以这样做,函数对象,bind对象都不可以。
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();
block和unblock都是boost::signals::connection对象的方法,需要首先得到这个connection。
{
boost::signals::scoped_connection c = sig.connect(ShortLived());
sig(); // will call ShortLived function object
}
sig(); // ShortLived function object no longer connected to sig
ShortLive只在作用域内起作用,如果离开了作用域,就不能起作用了。
考虑下面的代码
boost::signal
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<
}
};
派生自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
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
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
struct maximum
{
typedef T result_type;
template
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
> sig; ......
.....
在 signal声明时,作为模板参数给出。
我们还可以收集slot的返回值,这通过定义一个收集器实现
template
struct aggregate_values
{
typedef Container result_type;
template
Container operator()(InputIterator first, InputIterator last) const
{
return Container(first, last);
}
};
这样使用
boost::signal
> > sig2; sig2.connect("ient);
sig2.connect(&product);
sig2.connect(&sum);
sig2.connect(&difference);
std::vector
results = sig2(5,3); std::copy(results.begin(), results.end(),
std::ostream_iterator
(std::cout, " ")); std::cout<
slot执行的结果,将被放在vector
这个返回值收集器在工作的时候,如果first和last没有被访问到,那么,slot就不会被触发。例如
template
struct FirstResult
{
template
T operator()(InputIterator first, InputIterator last) {
return *first;
}
};
这个收集器事实上,仅仅让signal触发了第一个slot,其余的slot均没有被触发。因此,这个slot也可以作为我们过滤slot的方法。
slot和signal的声明不会在一个地方(如果那样,就没有必要提供signal-slot机制了),这是,我们需要传递 slot对象,具体做法,是通过signal::slot_type来完成的
如,下面的例子:
class Button
{
typedef boost::signal
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 testn";
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
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
(*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
原文出处



