在我们实验室的手持设备的开发中,用到了大量的QT核心特性——信号与槽,那么,什么是传说中的信号与槽呢?
一、信号和槽,究竟是什么
简单地说,这个机制就是消息机制的牛X版本。如果学过一点windows/MFC编程,或者是一些操作系统级别编程的同学,都会知道什么是消息。一般的,如果我们发送了一个消息,那么我们会希望一个函数来接收这个消息。在一般的系统中,这个函数通常称之为”回调函数“。简单点说就是如何在一个类的一个函数中触发另一个类的另一个函数调用,而且还要把相关的参数传递过去.
信号与槽机制也是类似。信号就是我们所说的”消息“,而”槽“就是我们所说的回调函数。所以,就是消息机制的牛X版本,一个类发送一个信号,另外一个类接收这个信号。接收信号的函数,就称为“槽”。
二、信号与槽机制,和传统的消息-回调函数机制,有什么区别吗
答案是肯定的。(废话,要是没有区别,搞这个东东干什么?)
官方的说法如下:
Callbacks have two fundamental flaws: Firstly, they are not type-safe. We can never be certain that the processing function will call the callback with the correct arguments. Secondly, the callback is strongly coupled to the processing function since the processing function must know which callback to call.
简单翻译并且解释一下,就是:
第一,回调函数传递的是函数指针,不会进行传递的参数类型检查(比如回调函数想来处理一个int型的数据,但是编程人员不小心传递了一个char型的数据,就有可能导致悲剧...)。而信号与槽机制,会检查传递的参数类型。如果不匹配,就拒绝传递参数。
第二,回调方式对于程序之间“高内聚,低耦合”的思想是一种背离——如果你的发送消息的程序需要调用一个回调函数,那么这个回调函数和发送消息之间的程序之间就产生了大量的耦合——发送消息的函数必须要知道这个回调函数是什么,而且必须存在(当然可以为空函数,类似于艾师兄的新版的WSN程序中那样做的一样——比如mac层来了一个包要发送到应用层,应用层不是直接处理,而是将这个包传递给一个回调函数,让这个回调函数来处理。在最开始的时候,这个回调函数为空,随着应用的改变而改变)。
但是,signal/slot机制不同。信号和槽是qt的标准接口,我们只需要在使用的时候声明信号和槽就可以,信号不必知道槽的存在(也即,对于一个类来说,并不知道是哪个类来处理其发出的信号)。而槽也不必要知道信号的存在(对于一个处理函数来说,不必关心程序的数据来自于哪里)。这样可以更大程度的降低程序之间的耦合性。看完这篇文章,对于这一点就能够理解的更加深刻了。
同时,该机制消耗的资源较少,官方说法如下:
On an i586-500, you can emit around 2,000,000 signals per second connected to one receiver, or around 1,200,000 per second connected to two receivers. The simplicity and flexibility of the signals and slots mechanism is well worth the overhead, which your users won't even notice.
并且,如同回调机制一样,信号与槽也可以一对多,多对一,多对多:
三、初级应用
说了半天,这个牛X的东东怎样用?
首先我们来看一个最简单的C++的类:
如果采用信号与槽机制:
我们注意到,首先继承了QObject类;然后写一个Q_OBJECT关键字(所有的包含信号和槽的类,都需要在类的开始写上这个关键字,为了后期的moc处理)。
然后声明signal是什么——里面的int newValue就是信号传递的名字;slots就是处理函数,int value就是被传递进的参数了。
下面这个就是setValue函数(也就是槽)的实现了,很简单吧。当然,这里为了后面的叙述方便,在这个槽中,还发射了一个信号:
所以我们看到,发射信号也很简单,就是使用一个emit关键字就好了。
那么,怎样来将信号和槽连接起来呢?也很简单,使用connect就可以了。
很简单,将a的signal 和 b的slot机制连接起来了。但是注意,没有将b的signal和a的slot连接起来!于是,如果执行
a
.
setValue(
12
),那个根据刚才的setValue的函数,a的value会变成12,同时发出一个信号。由于b的slot接收到了这个信号,那么b的value会变成12。但是,当执行第二句话,b.setValue(48)的时候,由于b的signal没有连接到任何slot上面,那么b的value变成48,但是a的value却没有变化。这一点是在例会的时候没有讲清楚的。
怎么样,简单吧!
四、高级应用
在具体的应用过程中,遇到这样一个问题:如果我们需要传递一个自定义的数据类型(不是int,float等标准的数据类型。比如在我们的手持设备中,需要传递一个MAC层的帧结构),那么怎么办?
其实也很简单,使用qRegisterMetaType就可以了。代码如下:
//注册MAC_RX_FRAME到qt的meta type(元类型)里面,这样才可以在不同的线程之间传递
qRegisterMetaType<MAC_RX_FRAME>("MAC_RX_FRAME");
实际上这样就MAC_RX_FRAME类型注册到了qt的MetaType中了,就可以通过信号与槽机制来进行传递了~
可以参考nokia的官方文档:
http://qt-project.org/doc/qt-4.8/signalsandslots.html