【Qt】信号槽的三种连接语法

实现观察者模式,可以使用函数回调,但注册回调函数有一定局限,安全性也没有保证。所以一定程度上可以说 Qt 信号槽是对回调机制进行了封装。

Qt 的信号槽能够连接(connect) 和编译通过,需要满足两个条件

  1. 信号的参数个数大于等于槽函数
  2. 信号槽的参数个数相同的部分,参数类型必须一一对应

创建信号槽连接,有以下三种方式

1. 使用 ui 界面控件

第一种,使用 ui 界面上的控件,通过右键 -> 转到槽,则会在对应界面的 cpp 生成类似以下函数名称的代码

void MainWindow::on_pushButton_clicked()
{
  
}

on_WidgetName_SignalName

on + 控件名称 + 信号名称

此种方式生成槽函数,编译时不进行检查,在运行时连接,通过 Qt 自身 moc (meta object compiler) 系统的反射机制来连接两个函数。

所以在控件名称修改时,在运行时会提示连接失败。

另一个缺陷是这种连接方式也不方便维护,连接(connect)和解除连接(disconnect)都不在可控范围内。

2. Qt4 的连接语法

第二种方式,使用 Qt4 语法的连接,也就是使用宏扩展, 本质上还是利用字符串的反射机制,示例:

connect(sender, SIGNAL(sigfunc()), receiver, SLOT((slotfunc()));

connect 的四个参数为:发送者对象指针,SIGNAL(发送者信号函数),接收者对象指针,SLOT(接收者槽函数)

如果查看 SIGNAL, SLOT 这两个宏的实现,就可以注意到这两个宏是将函数名称转换为字符串

#ifndef QT_NO_META_MACROS
#ifndef QT_NO_DEBUG
#define QLOCATION "\0" __FILE__ ":" QT_STRINGIFY(__LINE__)
#ifndef QT_NO_KEYWORDS
#	define METHOD(0)  qFlagLocation("0"#a QLOCATION)
#endif
# define SLOT(a) qFlagLocation("1"#a QLOCATION)
# define SIGNAL(a) qFlagLocation("2"#a QLOCATION)
#else 
# ifndef QT_NO_KEYWORDS
#  define METHOD(a)  "0"#a
# endif
# define SLOT(a) "1"#a
# define SIGNAL(a) "2"#a
#endif

slot 槽函数以 “1” 打头,signal 信号函数以 “2” 打头。QT_STRINGIFY 宏的定义也是单井号的使用

#define QT_STRINGIFY2(x) #x
#define QT_STRINGIFY(x) QT_STRINGIFY2(x)

但相较第一种方式,此种方式编译阶段做了字符串形式的参数一致性检查。

缺点是无法确认类中是否包含此函数,可以在两个宏中放入两个参数匹配但根本不存在的函数,一样能在编译期间顺利通过编译检查,却在运行时提示连接失败。

3. Qt5 的连接语法

第三种,Qt5 中提供了函数指针形式的 connect 语法,示例:

connect(sender, &Sender::signal, receiver, &Receiver::slot);

类名加函数取地址,确保了编译器检查信号与槽函数是否匹配,可以减少运行时出现连接失败的情况。

还有一种 lambda 表达式的变体,也是使用函数指针的方式来连接

connect(sender, &Sender::signal, [](){
  //... implement of slot
});

但 Qt5 语法中如果出现信号或槽函数重载,或两者都有重载的情况下,connect 不会智能匹配,重载情况下直接使用,编译会报错,错误信息如下:

no matching member function for call to 'connect'

调用 connect 没有匹配的成员函数

可以使用 Qt 的 QOverload 来处理,假如有以下信号和槽函数

signals:
  void sigfunc(int);
  void sigfunc(QString);
//...
public slots:
  void slotfunc(int);
  void slotfunc(QString);

连接时可以使用QOverload<T>::of ,示例:

connect(sender, QOverload<int>::of(&Sender::sigfunc), 
	receiver, QOverload<int>::of(&Receiver::slotfunc));

connect(sender, QOverload<QString>::of(&Sender::sigfunc), 
	receiver, QOverload<QString>::of(&Receiver::slotfunc));

来分别连接信号和槽函数的重载 (int, QString)

08-12 23:48