学习QT之信号槽机制详解


一、Qt信号槽机制

概念:信号槽是Qt框架引以为豪的机制之一。所谓信号槽,实际就是观察者模式。当某个事件发生之后,比如:按钮检测到自己被点击了一下,它就会发出一个信号(signal)。这种发出是没有目的的,类似广播。如果有对象对这个信号感兴趣,他就会使用连接(connect)函数,意思是,将想要处理的信号和自己的一个函数(称为槽(slot))绑定来处理这个信号。也就是说,放信号发出是,被连接的槽函数会自动被回调。

槽的本质是类的成员函数,其参数可以是任意类型的。和普通C++成员函数几乎没有区别,它可以是虚函数;也可以被重载;可以是共有的、保护的、私有的、也可以被其它C++成员函数调用。唯一的区别是:槽可以和信号连接在一起,每当和槽连接的信号被发射的时候,就会调用这个槽。

自定义信号槽注意事项

  1. 发送者和接受者都需要是QObject的子类(当然,槽函数是全局函数,Lambda表达式等无需接收者的时候除外);
  2. 使用signals标记信号函数,信号是一个函数声明,返回void,不需要实现函数代码;
  3. 槽函数是普通的成员函数,作为成员函数,会受到public、private、protected的影响;
  4. 使用emit在恰当的位置发送信号;
  5. 使用QObject::connect()函数连接信号和槽;
  6. 任何成员函数、static函数、全局函数和Lambda表达式都可以作为槽函数。

信号槽的多种用法

  1. 一个信号可以和多个槽相连;如果是这种情况,这些槽会一个接一个的被调用,但是它们的调用顺序是不确定的。
  2. 多个信号可以连接到一个槽;只要任意一个信号发出,这个槽就会被调用。
  3. 一个信号可以连接到另一个信号;当第一个信号发出时,第二个信号被发出。除此之外,这种信号-信号的形式和信号-槽的形式没有什么区别。
  4. 槽可以被取消连接;这种情况并不经常出现,因为当一个对象delete之后,Qt自动取消所有连接到这个对象上面的槽。
  5. 使用Lambda表达式;在使用Qt5的时候,能够支持Qt5的编译器都是支持Lambda表达式的。

二、QT信号槽机制的优缺点

(1)问题:为什么Qt使用信号与槽机制而不是传统的回调函数机制进行对象间的通信呢?

回调函数的本质是“你想让 别人的代码执行你的代码,而别人的代码你又不能动”这种需求下产生的。回调函数是函数指针的一种用法,如果多个类都关注某个类的状态变化,此时需要维护一个列表,以存放多个回调函数的地址。对于每一个被关注的类,都需要做类似的工作,因此这种做法效率低,不灵活。

(2)解决办法

Qt使用信号槽机制来解决这个问题,程序只需要指定一个类含哪些信号函数,哪些槽函数,Qt会处理信号函数和槽函数之间的绑定。当信号函数被调用时,Qt会找到并执行与其绑定的槽函数。允许一个信号函数和多个槽函数绑定,Qt会依次找到并执行与一个信号函数绑定的所有槽函数,这种处理方式更灵活。

(3)优点

Qt信号与槽机制降低了Qt对象的耦合度。

激发信号的Qt对象无须知道是哪个对象的哪个槽函数需要接收它发出的信号,它只需要做的是在适当的时间发送适当的信号就可以了,而不需要知道也不关心它的信号有没有被接收到,更不需要知道哪个对象的哪个槽接收了信号。

同样,对象的槽也不知道是哪些信号关联了自己,而一旦关联信号和槽,Qt就保证了适合的槽得到了调用。即使关联的对象在运行时被删除。应用程序也不会奔溃。

(4)缺点

信号槽机制,同回调函数相比,信号和槽机制运行速度有些慢。通过传递一个信号来调用槽函数将会比直接调用非虚函数运行速度慢10倍。原因如下:- 需要定位接收信号的对象;- 安全地遍历所有的关联(一个信号关联多个槽的情况);- 编组/解组传递的参数;- 多线程的时候,信号可能需要排队等待。

三、多线程情况下,Qt中的信号槽分别在什么线程中执行,如何控制?

通过connect()函数的第五个参数connectType来控制。

connect()用于连接Qt的信号和槽,在Qt编程过程中不可或缺。它其实有第五个参数,只是一般使用默认值,在满足某些特殊需求的时候可能需要手动设置。

Qt::AutoConnection:默认值,使用这个值则连接类型会在信号发送是决定。如果接收者和发送者在同一个线程,则自动使用Qt::DirectConnection类型。如果接收者和发送者不在同一个线程,则自动使用Qt::QueuedConnection类型。

Qt::DirectConnection:槽函数会在信号发送的时候直接被调用,槽函数运行与信号发送者所在线程。效果看上去就像是直接信号发送位置调用了槽函数。这个在多线程环境下比较危险,可能会造成崩溃。

Qt::QueuedConnection:槽函数在控制回到接受者所在线程的事件循环时被调用,槽函数运行于信号接收者所在线程。发送信号之后,槽函数不会立刻被调用,等到接收者的当前函数执行完,进入事件循环之后,槽函数才会被调用。多线程环境下一般用这个。

Qt::BlockingQueuedConnection:槽函数的调用时机与Qt::QueuedConnection一致,不过发送完信号后发送者所在线程会阻塞,直到槽函数运行完。接收者和发送者绝对不能在一个线程,否则程序会死锁。在多线程间需要同步的场合可能需要这个。

Qt::UniqueConnection:这个flag可以通过按位或(|)与以上四个结合在一起使用。当这个flag设置时,当某个信号和槽已经连接是,在进行复杂的了解就会失败。也就是避免了重复连接。