很多开发者在听到术语'事件处理'时就会想到GUI:点击一下某个按钮,相关联的功能就会被执行。 点击本身就是事件,而功能就是相对应的事件处理器。这一模式的使用当然不仅限于GUI。 一般情况下,任意对象都可以调用基于特定事件的专门函数。
Boost.Signals 库提供了一个名为 boost::signal
的类,定义于 boost/signal.hpp
. 实际上,这个头文件是唯一一个需要知道的,因为它会自动包含其它相关的头文件。
#include
#include
void func1()
{
std::cout << "Hello" << std::flush;
}
void func2()
{
std::cout << ", world!" << std::endl;
}
int main()
{
boost::signal s;
s.connect(func1);
s.connect(func2);
s();
}
boost::signal 实际上被实现为一个模板函数,具有被用作为事件处理器的函数的签名,该签名也是它的模板参数。 在这个例子中,只有签名为 void ()
的函数可以被成功关联至信号 s。函数 func1()
及func2()被通过 connect()
方法关联至信号 s。 由于 func1()
及func2()符合所要求的 void ()
签名,所以该关联成功建立。因此当信号 s 被触发时,func1()及
func2()会按照connect的顺序被调用。
另外,执行的顺序也可通过 connect()
方法的另一个重载版本来明确指定,该重载版本要求以一个 int
类型的值作为额外的参数。例如:
#include
#include
int func1()
{
std::cout << "Hello" << std::flush;
return 1;
}
int func2()
{
std::cout << ", world!" << std::endl;
return 2;
}
int main()
{
boost::signal s;
s.connect(1, func2);
s.connect(0, func1);
s();
}
除了 connect()
以外,boost::signal
还提供了几个方法:
要释放某个函数与给定信号的关联,可以用 disconnect()
方法。
num_slots() 返回已关联函数的数量。如果没有函数被关联,则 num_slots()
返回0。
disconnect_all_slots() 方法释放所有已有的关联。
empty()方法判断当前信号是否已关联函数。
另外,关于函数返回值,缺省情况下,所有被关联函数中,实际上只有最后一个调用的函数的返回值被真正返回。
若想对所有返回值进行操作处理,可以把一个称为合成器(combiner)的东西作为第二个参数传递给 boost::signal
。例如:
#include
#include
#include
#include
int func1()
{
return 1;
}
int func2()
{
return 2;
}
template
struct min_element
{
typedef T result_type;
template
T operator()(InputIterator first, InputIterator last) const
{
return T(first, last);
}
};
int main()
{
boost::signal > > s;
s.connect(func1);
s.connect(func2);
std::vector v = s();
std::cout << *std::min_element(v.begin(), v.end()) << std::endl;
}
合成器其实就是一个重载了 operator()()
操作符的类。这个操作符会被自动调用,传入两个迭代器,指向某个特定信号的所有返回值。另外这段代码在实际编译是,是有警告的,因为boost::signal 要求合成器的operator()返回名为 result_type
的类型。 而在标准 C++ 算法中缺少这个类型。
实际上 boost::signal变量在调用
connect()
和 disconnect()
方法时会返回一个类型为 boost::signals::connection 的值。而我们则可以通过该返回值对boost::signal进行管理。例如:
#include
#include
void func()
{
std::cout << "Hello, world!" << std::endl;
}
int main()
{
boost::signal s;
boost::signals::connection c = s.connect(func);
c.block();
s();
c.unblock();
s();
c.disconnect();
}
如上代码所示:boost::signal 的 disconnect()
方法需要传入一个函数指针,而直接调用 boost::signals::connection
对象上的 disconnect()
方法则略去该参数。另外以上程序只会执行一次 func()
。 虽然信号 s 被触发了两次,但是在第一次触发时 func()
不会被调用,因为连接 c 实际上已经被 block()
调用所阻塞。 由于在第二次触发之前调用了 unblock()
,所以之后 func()
被正确地执行。
除了 boost::signals::connection
以外,还有一个名为 boost::signals::scoped_connection
的类,它会在析构时自动释放连接。
最后,信号也是可以关联一个类的成员函数的,例如:
#include
#include
#include
#include
class world
{
public:
void hello() const
{
std::cout << "Hello, world!" << std::endl;
}
};
int main()
{
boost::signal s;
{
std::auto_ptr w(new world());
s.connect(boost::bind(&world::hello, w.get()));
}
std::cout << s.num_slots() << std::endl;
s();
}
其实以上代码是存在问题的,程序中使用 Boost.Bind 将一个对象的方法关联至一个信号。 在信号触发之前,这个对象就被销毁了,这会产生问题。 我们不传递实际的对象 w,而只传递一个指针给 boost::bind()
。 在 s()
被实际调用的时候,该指针所引向的对象已不再存在。
可以如下修改这个程序,使得一旦对象 w 被销毁,连接就会自动释放。如下所示:
#include
#include
#include
#include
class world :
public boost::signals::trackable
{
public:
void hello() const
{
std::cout << "Hello, world!" << std::endl;
}
};
int main()
{
boost::signal s;
{
std::auto_ptr w(new world());
s.connect(boost::bind(&world::hello, w.get()));
}
std::cout << s.num_slots() << std::endl;
s();
}
仅需的修改是让 world
类继承自 boost::signals::trackable
。 当使用对象的指针而不是对象的副本来关联函数至信号时,boost::signals::trackable
可以显著简化连接的管理。