参考资料:
- Qt帮助文档中的《Signals & Slots》
 
作用
用于在对象之间建立通信。
当发生某个事件时,信号会被发出,槽函数会被调用,以响应某个信号。
信号和槽机制是类型安全的:信号的签名必须与槽的签名匹配(实际上,一个槽的签名可能比它接收到的信号的签名要短,它可以忽略信号传递过来的额外的参数)。
信号和槽是松耦合的:发出信号的类既不知道也不关心哪个槽会接收信号。
所有从QObject或其子类(例如,QWidget)继承的类都可以包含信号和槽。
槽可以用来接收信号,但也是普通的成员函数。
信号与槽之间的连接可以是多对一的,也可以是一对多的。
信号与信号之间也可以建立连接。
信号
信号是公共访问函数,且必须没有返回值,可以从任何地方发出,但我们建议只从定义信号及其子类的类发出信号。
当发出信号时,连接到它的槽通常立即执行,就像普通的函数调用一样,当这种情况发生时,信号和槽机制完全独立于任何GUI事件循环,一旦所有槽都返回,emit语句之后的代码就会执行。
当使用队列连接时,情况略有不同,在这种情况下,emit关键字后面的代码将立即继续执行,而槽将稍后执行。
如果多个槽连接到一个信号,则在信号发出时,这些槽将按照它们连接的顺序依次执行。
信号是由moc自动生成的,不能在.cpp文件中实现。它们不能有返回类型(即必须使用void)。
语法:
1 2
   | signals:     void 信号名称(参数);
 
  | 
 
例如:
1 2
   | signals:     void xxSig(int i, double d);
 
  | 
 
槽
当一个连接到槽的信号发出时,就会调用这个槽。
槽是普通的C++函数,可以正常调用,与其他函数不同的是,信号可以与它们建立连接。
与回调相比,信号和槽稍微慢一些,原因在于需要定位连接对象、安全地遍历所有连接以及处理参数所需的开销。
注意,在与基于Qt的应用程序一起编译时,定义称为信号或槽的变量的其他库可能会导致编译器警告和错误。要解决这个问题,需要使用#undef屏蔽掉有问题的预处理器符号。
所有包含信号或槽的类必须在声明的顶部提到Q_OBJECT。这些类还**必须(直接或间接)**派生自QObject。
槽函数需要有实现。
可以通过一个disconnect()中断所有连接。如果使用了Qt::UniqueConnection,则只有在不重复的情况下才会进行连接;如果已经有一个重复的信号(相同对象上相同槽的相同信号),连接将会失败,connect()会返回false。
语法:
1 2
   | slots:     [public|protected|private] void 槽名称(参数);
 
  | 
 
例如:
1 2
   | public slots:     void xxSigHandler(int i);
 
  | 
 
信号与槽的连接
使用connect()函数进行连接:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39
   | static QMetaObject::Connection connect(     const QObject *sender,      const char *signal,      const QObject *receiver,      const char *member,      Qt::ConnectionType = Qt::AutoConnection );
  static QMetaObject::Connection connect(     const QObject *sender,      const QMetaMethod &signal,      const QObject *receiver,      const QMetaMethod &method,      Qt::ConnectionType type = Qt::AutoConnection );
  inline QMetaObject::Connection connect(     const QObject *sender,      const char *signal,      const char *member,      Qt::ConnectionType type = Qt::AutoConnection ) const;
  template <typename Func1, typename Func2> static inline QMetaObject::Connection connect(     const typename QtPrivate::FunctionPointer<Func1>::Object *sender,      Func1 signal,      const typename QtPrivate::FunctionPointer<Func2>::Object *receiver,      Func2 slot,      Qt::ConnectionType type = Qt::AutoConnection );
  template <typename Func1, typename Func2> static inline typename std::enable_if<int(QtPrivate::FunctionPointer<Func2>::ArgumentCount) >= 0, QMetaObject::Connection>::type  connect(     const typename QtPrivate::FunctionPointer<Func1>::Object *sender,      Func1 signal,      Func2 slot );
 
  | 
 
信号槽连接的几种方式
可以通过以下3种方式进行连接:
SIGNAL和SLOT宏(字符串形式) 
- 函数指针
 
C++11的lambda表达式 
SIGNAL和SLOT宏
这两个宏,会把传入的内容转换为字符串,它们的定义如下:
1 2
   | #define SIGNAL(arg) #arg #define SLOT(arg) #arg
 
  | 
 
传入SIGNAL宏的函数签名的参数个数不能比传入SLOT宏的函数签名的参数个数少。
也就是说,信号的参数个数需要大于或等于槽的参数个数,且信号中的前N个参数必须要与对应的槽参数的类型相同(N为槽函数中参数的个数),否则在Qt Creator中会出现:
QObject::connect: Incompatible sender/receiver arguments
传入SLOT宏的可以是成员函数、静态函数、lambda表达式。
1 2 3
   | connect(sender, SIGNAL(destroyed(QObject*)), this, SLOT(objectDestroyed(Qbject*))); connect(sender, SIGNAL(destroyed(QObject*)), this, SLOT(objectDestroyed())); connect(sender, SIGNAL(destroyed()), this, SLOT(objectDestroyed()));
 
  | 
 
函数指针
此时,槽函数必须是一个普通成员函数或静态函数。
1
   | connect(sender, &QObject::destroyed, this, &MyObject::objectDestroyed);
 
  | 
 
信号或槽有重载时,如果直接传入函数指针,则无法判断到底要使用哪个信号、哪个槽,此时,需要通过QOverload或显式创建指针变量进行处理:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
   | signals:     void brake(int force);     void brake(int force, int a); public slots:     void stopRunning(double force);
 
  connect(&car, qOverload<int>(&Car::brake), &car, &Car::stopRunning); connect(&car, qOverload<int, int>(&Car::brake), &car, &Car::stopRunning);
  void (Car::*ptr1)(int) = &Car::brake; void (Car::*ptr2)(int, int) = &Car::brake; connect(&car, ptr1, &car, &Car::stopRunning); connect(&car, ptr2, &car, &Car::stopRunning);
 
  | 
 
使用函数指针进行信号与槽的连接,有以下优势:
- 编译器可以检查信号参数与槽参数是否兼容(编译期检测)
 
- 如有必要,编译器会对参数进行转换(比如,double转int,int转double)
 
C++11的lambda表达式
1 2 3
   | connect(sender, &QObject::destroyed, this, [=](){     this->m_objects.remove(sender); });
 
  | 
 
Qt::ConnectionType
| 序号 | 
类型 | 
描述 | 
| 1 | 
Qt::AutoConnection | 
默认。如果接收者与发送的信号在同一个线程中,则使用Qt::DirectConnection;否则,则使用Qt::QueuedConnection。具体使用哪个,是在信号发送时才确定的。 | 
| 2 | 
Qt::DirectConnection | 
信号发送时,槽函数立刻执行。槽函数在信号发送线程中执行。 | 
| 3 | 
Qt::QueuedConnection | 
槽函数在控制权交还给接收线程的运行循环时执行。槽函数在接收线程中执行。 | 
| 4 | 
Qt::BlockingQueuedConnection | 
与Qt::QueuedConnection一样,但信号线程会一直阻塞,直到槽函数返回,因此信号线程与接收线程不能是同一个,否则会发生死锁。 | 
| 5 | 
Qt::UniqueConnection | 
可以与上述几种类型结合使用(使用按位或“|”),如果连接已经存在,那么新的连接将会失败。Qt 4.6引入。 | 
| 6 | 
Qt::SingleShotConnection | 
可以与上述几种类型结合使用(使用按位或“|”),槽函数只会执行一次,当信号发送时,连接会自动断开。Qt 6.0引入。 | 
在信号、槽中使用自定义类型的参数
如果调用connect函数时,Qt::ConnectionType设置的是Qt::QueuedConnection,那么,自定义类型必须是Qt元对象系统所知道的类型,否则在连接时会报错。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
   | #ifndef MYOBJECT_H #define MYOBJECT_H
  #include <QObject> #include "MyCustomType.h"
  class MyObject : public QObject {     Q_OBJECT public:     explicit MyObject(QObject *parent = nullptr);
  private:
  signals:     void customTypeSig(MyCustomType val);
  public slots:     void handlerCustomType(MyCustomType val); };
  #endif 
 
  | 
 
注意,最后一个参数是Qt::QueuedConnection:
1
   | connect(&obj, &MyObject::customTypeSig, &obj, &MyObject::handlerCustomType, Qt::QueuedConnection);
 
  | 
 
错误信息:
1 2
   | QObject::connect: Cannot queue arguments of type 'MyCustomType' (Make sure 'MyCustomType' is registered using qRegisterMetaType().)
 
  | 
 
需要在连接前,加入:
1
   | Q_DECLARE_METATYPE(MyCustomType)
 
  |