Boost.Signals2
介绍
Boost.Signals2
库是一个管理信号和槽函数系统的实现。信号代表具有多个目标的回调,在相似系统中也称为发布者或事件。信号连接到一组槽函数,这些槽函数是回调接收器(也称为事件目标或订阅者),在“发出”信号时调用它们。
管理信号和槽函数,因为信号和槽函数(或更恰当地说,是作为槽函数的一部分出现的对象)可以跟踪连接,并且能够在其中一个被销毁时自动断开信号/插槽的连接。 这使用户可以进行信号/槽函数连接,而无需花费很大的精力来管理所有相关对象的生命周期。
当信号连接到多个槽函数时,存在关于槽函数的返回值与信号的返回值之间关系的问题。 Boost.Signals2
允许用户指定组合多个返回值的方式。
Signals2
本文档描述了原始Boost.Signals库的线程安全变体。 为了支持线程安全,对接口进行了一些更改,主要是在自动连接管理方面。 此实现由Frank Mori Hess编写。 也要感谢Timmo Stange,Peter Dimov和Tony Van Eerd的想法和反馈,也要感谢Douglas Gregor的Boost的原始版本。
教程
如何阅读本教程
本教程不适合线性阅读。 它的顶层结构将库中的不同概念大致分开(例如,处理调用多个槽函数,将值传递到槽函数和从槽函数传递值),在这些概念中,首先介绍基本概念,然后在以后描述该库的更复杂的用法 。 每个部分均标记为Beginner
,Intermediate
或Advanced
,以帮助指导读者。 初学者部分包括所有库用户都应该知道的信息; 在只阅读了Beginner部分之后,您可以充分利用Signals2库。 中级部分建立在初学者部分的基础上,该库的使用稍微复杂一些。 最后,“高级”部分详细介绍了Signals2库的非常高级的用法,这些用法通常需要扎实的入门和中级主题知识; 大多数用户不需要阅读“高级”部分。
Hello,World!(Beginner)
下面的例子使用信号和槽函数来输出"Hello,World!"。首先,我们创建一个信号sig,一个不带参数的信号,并且有一个void返回值。接下来,我们使用connect方法将hello函数连接到信号。
最后,使用像函数一样的信号sig来调用槽,它依次调用`HelloWorld::operator()来打印"Hello,World!"。
struct HelloWorld {
void operator()() const {
std::cout << "Hello, World!" << std::endl;
}
};
// Signal with no arguments and a void return value
boost::signals2::signal<void ()> sig;
// Connect a HelloWorld slot
HelloWorld hello;
sig.connect(hello);
// Call all of the slots
sig();
调用多个槽函数
连接多个槽函数(Beginner)
从一个信号中调用一个单独的槽函数并不是很有趣,所以我们可以通过将打印“Hello,World!”的工作分成两个完全独立的槽函数来让Hello,World程序变得更有趣。第一个槽函数会打印“Hello”,可能是这样的:
struct Hello {
void operator()() const {
std::cout << "Hello";
}
};
第二个槽函数将打印“,World!”和一个换行符,来完成这个程序。第二个槽函数可能是这样的:
struct World {
void operator()() const {
std::cout << ", World!" << std::endl;
}
};
就像在前面的例子中一样,我们可以创建一个没有参数的信号sig,并且有一个void返回值。这一次,我们将hello和world槽函数连接到相同的信号,当我们调用信号时,两个槽都将被调用。
boost::signals2::signal<void ()> sig;
# 这里Hello()后面带括号是构造了临时对象,不是调用operator()
sig.connect(Hello());
sig.connect(World());
sig();
默认情况下,槽函数被push到槽函数list的后面,因此这个程序的输出将如预期的那样:
Hello, World!
顺序调用槽函数(Itermediate)
槽函数可以自由地产生副作用,这意味着有些槽函数必须在其他槽函数之前被调用,即使它们没有按顺序连接。Boost.Signals2
库允许将插槽放置在以某种方式排序的组中。对于我们的Hello,World程序,我们想要“Hello”在“,World!”之前打印出来。因此,我们将“Hello”放入一个必须在“,World!”的组之前执行的组中。为了做到这一点,我们可以在指定该组的connect调用的开头提供一个额外的参数。group值默认为int,并且由从小到大排序。下面是我们如何构建Hello,World:
boost::signals2::signal<void ()> sig;
sig.connect(1, World()); // connect with group 1
sig.connect(0, Hello()); // connect with group 0
调用该信号将正确地打印“Hello,World!”因为Hello对象在组0中,在第1组中Word对象所在的位置前面。实际上,group
参数是可选的。我们在第一个Hello,World例子中省略了它,因为当所有的槽都是独立的时候没有必要。那么,如果我们把调用连接起来使用组参数和那些没有的调用会发生什么呢?“unnamed”的槽函数(即那些在没有指定group
的情况下被连接的人可以被放置在槽函数list的前面或后面(通过boost::signals2::at_front
或 boost::signals2::at_back
作为最后一个连接的参数),并且默认为list的末尾。当一个组被指定时,最后的at_front
或at_back
参数描述了在组排序中放置位置的位置。与at_front连接的未分组槽函数将始终位于所有分组插槽之前。 与at_back
连接的未分组槽函数将始终在所有分组槽函数之后。
如果我们在这个例子中添加一个新的槽函数:
struct GoodMorning {
void operator()() const {
std::cout << "... and good morning!" << std::endl;
}
};
// by default slots are connected at the end of the slot list
sig.connect(GoodMorning());
// slots are invoked this order:
// 1) ungrouped slots connected with boost::signals2::at_front
// 2) grouped slots according to ordering of their groups
// 3) ungrouped slots connected with boost::signals2::at_back
sig();
…我们会得到我们想要的结果:
Hello, World!
... and good morning!
向槽函数传递值
槽函数的参数(Beginner)
信号可以将参数传递到他们调用的每个槽函数。例如,传递鼠标移动事件的信号可能想要传递新的鼠标坐标,以及鼠标按钮是否被按下。
例如,我们将创建一个将两个float参数传递到其槽函数的信号。 然后,我们将创建一些插槽,在这些值上打印各种算术运算的结果。
void print_args(float x, float y) {
std::cout << "The arguments are " << x << " and " << y << std::endl;
}
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;
}
void print_quotient(float x, float y) {
std::cout << "The quotient is " << x / y << std::endl;
}
boost::signals2::signal<void (float, float)> sig;
sig.connect(&print_args);
sig.connect(&print_sum);
sig.connect(&print_product);
sig.connect(&print_difference);
sig.connect(&print_quotient);
sig(5., 3.);
这个程序将打印出以下内容:
The arguments are 5 and 3
The sum is 8
The product is 15
The difference is 2
The quotient is 1.66667
因此,像函数一样被调用时给sig的任何值都将传递到每个槽函数。 创建信号时,我们必须预先声明这些值的类型。 类型boost::signals2::signal <void(float,float)>
表示信号具有void
返回值,并传入两个float
值。 因此,连接到sig
的任何插槽都必须能够传入两个float
值。
信号返回值(Advanced)
正如槽函数可以接收参数一样,它们也可以返回值。 然后可以通过combiner
将这些值返回给信号的调用者。 combiner
是一种机制,可以获取调用槽函数的结果(可能没有结果或一百个结果;直到程序运行时我们才知道),并将它们合并为一个结果以返回给调用者。 单个结果通常是槽函数调用结果的简单函数:最近一次槽函数调用的结果,任何槽函数返回的最大值或所有结果的容器都是有可能的。
我们可以略微修改前面的算术运算示例,以便所有槽函数都返回计算乘积,商,和或差的结果。 然后,信号本身可以根据这些要打印的结果返回一个值:
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; }
boost::signals2::signal<float (float, float)> sig;