信号和槽
信号和槽是 Qt 带来的非常便利的对象间通信机制,实质上是使用观察者模式包装的回调系统。与之类似的还有 Boost::Signals、libsigc++ 等。
延续自 Qt4 的 SIGNAL 和 SLOT 这里不提,这里只讲 Qt5 中类型安全的语法。
信号和槽的优缺点:[1]
松散耦合
QT的信号可以对应多个槽(但他们的调用顺序随机),也可以多个槽映射一个信号
QT的信号槽的建立和解除绑定十分自由
信号槽相比回调函数大约有 10 倍的性能损失
信号槽的参数限定很多,例如不能带模板类参数,不能出现宏定义等等
信号和槽是类型安全的(Qt 5 语法)
上面的内容很容易理解,之所以信号和槽不能带模板类参数是因为信号连接的对象必须是已经实体化的。但是函数模板可以在 connect 中进行实体化,就没什么限制了
信号和槽的性能损失主要来自:[2]
需要定位接收信号的对象;
安全地遍历所有的关联(如一个信号关联多个槽的情况);
编组/解组传递的参数;
多线程的时候,信号可能需要排队等待。
声明信号和槽
信号的声明必须要在类的 signal 节中,而且必须使用 moc 对源文件进行处理。槽函数任意。
连接信号和槽
信号和槽的连接需要保证槽函数需要的参数少于信号提供的参数
当信号有重载形式时,有两种方式:
connect(&client
, qOverload<QLocalSocket::LocalSocketError>(&QLocalSocket::error)
, [=]() {});
connect(&client
, static_cast<void(QLocalSocket::*)(QLocalSocket::LocalSocketError)>(&QLocalSocket::error)
, [=]() {});
多线程中的信号和槽
connect 最后一个参数 ConnectionType 有以下几种选择:
枚举名 | 值 | 说明 |
AutoConnection | 0 | 如果接受者与发射信号的线程位于同一个线程,使用 DirectConnection,否则使用 QueuedConnection |
DirectConnection | 1 | 适用于单线程,槽函数将会在发射信号的线程中被执行 |
QueuedConnection | 2 | 当时间循环到接受者线程时执行槽函数,槽函数将会在接受者的线程中被执行 |
BlockingQueuedConnection | 3 | 与 QueuedConnection 类似,但是信号线程会被阻塞至槽函数返回 |
UniqueConnection | 0x80 | 这是一个与上面相配合的标志位,当连接已经存在时,返回 false |
SingleShotCOnnection | 0.100 | 同样是标志位,在槽函数执行一次后自动断开信号和槽 |
多线程中的信号和槽
在多线程中使用信号和槽的时候需要注意的信号发送过快的问题。信号发送过快会导致多余的信号被放到队列中,等槽函数处理完后再拿出来。这就导致一些其它工作受阻,比如槽函数中触发了一些事件,这些事件就可能不会被正确处理。
在 digikamflowplugin 中,由于信号发送过快会导致刚进入时界面卡顿,之后 Widget 的高度也会计算错(或者是 paintEvent 没按预期执行?)
解决的办法是槽函数执行时阻塞信号发送,将 connect 的最后一个参数设置为 Qt::BlockingQueuedConnection ,但是这个函数只适用于槽函数和信号位于不同的线程,否则会导致死锁