一、为什么需要信号与槽机制
在GUI用户界面中,当用户操作一个窗口部件时,需要其他窗口部件的响应或者能够激活其他的操作。在程序开发中,经常使用回调(callback)机制来实现。
所谓回调,就是事先将一个回调函数(callback function)指针传递给某一个处理过程,当这个处理过程得到执行时,回调预先定义好的回调函数以期实现激活其他处理过程的目的。
不同于回调函数机制,Ot提供了信号和槽机制。信号是一个特定的标识:一个槽就是一个函数,与一般的函数不同,槽函数既能够和信号关联,也能够像普通函数一样直接调用。
当某个事件出现时,通过发送信号,可以将与之相关联的槽函数激活,即执行槽函数代码。
在程序中,使用QObject:connect0函数来将某个信号和某个槽进行关联,而信号和槽之间的真正关联是由Qt的信号和槽机制来实现的。
二、信号与槽的关联形式
信号和槽的关联关系可以有几种模式:
- 一个信号和一个槽关联;
- 一个信号和多个槽关联:
- 多个信号和一个槽关联。
- 一个信号和多个槽关联的情况下,当发出该信号的时候,与该信号关联的各个槽以任意的先后顺序立即执行(即槽函数的执行顺序是随机的,与槽关联的顺序没有关系)。
信号和槽机制是完全独立于GUI事件循环的。
在 Qt 框架中,GUI 事件循环是应用程序的核心,它负责处理所有 GUI 相关的事件,如用户输入、窗口消息、定时器事件等。Qt 使用一个中心事件循环(event loop),它持续运行并等待事件的发生,然后根据事件的类型将事件分发到相应的处理函数。
事件循环的工作原理
事件获取:事件循环从操作系统获取事件。
事件过滤:事件通过 QObject::eventFilter() 方法进行过滤,允许开发者拦截事件。
事件分发:事件被分发到相应的接收者(widgets 或其他对象),通常是通过信号和槽机制。
事件处理:具体的事件处理函数(如槽函数)被调用,执行相应的操作。Qt 事件循环处理多种类型的事件,包括:
鼠标和键盘事件:如 QMouseEvent 和 QKeyEvent。
窗口事件:如 QResizeEvent 和 QCloseEvent。
定时器事件:由 QTimer 产生。
自定义事件:开发者可以创建和使用自定义事件。
此外,信号还可以和信号进行关联。当两个信号关联时,第一个信号的发出将会激活t发送第二个信号。
信号和槽通过
QObject::connect(const QObject*sender,const char*signal,
const QObject*receiver, const char * method,
Qt::ConnectionType type=Qt::AutoCompatConnection)
函数关联,参数type 定义了信号和槽的关联方式,决定一个信号是立即传递到槽还是排队等待以后传递。
Qt 使用枚举类型 Qt::ConnectionType 定义信号和槽的关联方式,有三种:
- Qt:DirectConnection,信号发送后立即传递给相关联的槽,只有槽函数执行完毕返回后,发送信号“emit<信号>”之后的代码才被执行。
- Qt::QueuedConnection,信号发送后排队,直到事件循环(eventloop)有能力将它传递给槽:而不管槽函数有没有执行,发送信号“emit<信号>”之后的代码都会立即得到执行
- Qt::AutoConnection:如果信号和槽在同一个线程,信号发出后,函数将立即执行,等同于 Qt::DirectConnection:如果信号和槽不在同一个线程,信号将排队,等待事件循环的处理,效果等同于 Ot::QueuedConnection
对于 Qt::QueuedConnection方式,Qt元对象系统(meta-objectsystem)必须知道信号/槽的参数类型,否则的话编译器会报错“QObject::connect: Cannot queue arguments oftype “Type””
要想使用类型 Type 必须首先通过 qRegisterMetType()函数在元对象系统中注册(简单数据类型和 Qt 定义的数据类型读者无需注册,可以直接使用)。
宏SIGNALO)和SLOTO)返回其参数的C风格的字符串(constchar*),因此下面关联信号和槽的两个语句是等同的:
connect(pushButton,SIGNAL(clicked()),this,SLoT (doPushButton()));
connect(pushButton,*clicked()",this,"doPushButton()");
三、信号与槽机制的优点与缺点
Qt信号和槽机制的优点是:
- 类型安全的。需要关联的信号和槽的签名必须是等同的,即信号的参数类型和参数个数与接受该信号的槽的参数类型和参数个数相同:不过,一个槽的参数个数是可以少于信号的参数个数的,但缺少的参数必须是信号参数的最后一个或几个参数。如果信号和槽的签名不符编译器就会报错。
- 松散耦合的。Qt 信号和槽机制减弱了 Qt 对象的耦合度。激发信号的 Qt 对象无须知道是哪个对象的哪个槽需要接收它发出的信号,它只需要做的是在适当的时间发送适当的信号就可以了,而不需要知道也不必关心它的信号有没有被接收到,更不需要知道是哪个对象的哪个槽接收到了信号:同样地,对象的槽也不知道是哪些信号关联到了自己。而一旦关联信号和槽,Qt就保证了适合的槽得到调用。即使关联的对象在运行时被删除,应用程序也不会出现崩溃。
一个类要想支持信号和槽,必须从QObject或QObject 的子类继承。注意,Qt信号和槽机制不支持对模板的使用。
关于信号/槽机制的效率问题
信号和槽机制增强了对象间通信的灵活性,然而这也损失了一些性能。与回调函数相比较,信号和槽机制有些慢。通常,通过传递一个信号来调用槽函数将会比直接调用非虚函数慢10倍。原因主要有:
- 需要定位接收信号的对象;
- 安全地遍历所有的关联(比如,一个信号关联到多个槽的情况);
- 编组(marshal)/解组(unmarshal)传递的参数;
- 多线程的时候,信号可能需要排队等待。
然而,与创建堆对象的 new 操作以及删除堆对象的 delete操作相比较,信号和槽的代价只是它
们很少的一部分。信号和槽机制导致的这点性能损耗,对实时应用程序是可以忽略的。与信号和槽提供的灵活性和简便性相比,这点性能的损失也是值得的。
四、信号和槽的自动关联
除了能够在程序中手动关联信号和槽之外,Qt的元对象提供了信号和槽的自动关联。
对于Qt窗口部件已经提供的信号,如果能够按下面的规则命名槽函数,那么Qt就能够自动进行关联:
void on_<窗口部件名>_<信号名>(<信号参数>);