1. 概述
Qt 的信号与槽机制是 GUI 编程中的重要工具,用于实现对象间的通信。它允许在事件发生时,信号发射者与槽接收者建立动态连接,从而实现松耦合的设计。该机制依赖于 Qt 的元对象系统和 Meta-Object Compiler (MOC) 生成的元对象代码。
2. 元对象系统 (Meta-Object System)
2.1 概述
元对象系统是 Qt 实现信号与槽机制的基础。通过扩展 C++ 编译器,元对象系统支持运行时类型信息 (RTTI)、属性系统、动态信号与槽等功能。Q_OBJECT
宏使得类能利用这些功能,并允许 MOC 生成相应的元信息,包括信号、槽和属性。
2.2 MOC 工作原理
MOC 的任务是在编译时扫描含有 Q_OBJECT 宏的类,生成包含信号、槽、属性等元信息的代码文件。它帮助每个包含 Q_OBJECT 宏的类创建其自己的元对象表。这些表包含类的信号、槽及其他元信息,但MOC 并不负责信号与槽的连接和调度。MOC 的任务是在编译时扫描含有 Q_OBJECT 宏的类,生成包含信号、槽、属性等元信息的代码文件。它帮助每个包含 Q_OBJECT 宏的类创建其自己的元对象表。这些表包含类的信号、槽及其他元信息,但MOC 并不负责信号与槽的连接和调度。
3. 信号与槽机制的运行流程
3.1 信号与槽的定义
在 Qt 中,信号与槽通过声明和定义相互关联的函数来实现。信号用于发射事件,而槽用于响应信号并处理事件。信号本质上是没有实现体的占位符函数,由 MOC 为其生成元信息。
3.2 信号与槽的连接
在运行时,Qt 的元对象系统(由 QMetaObject
和 QObject
提供)才真正负责信号与槽的连接、断开和激活。每个对象的信号与槽是通过QObject::connect()
来建立连接的,这些连接被存储在对象的连接表中。
3.3 信号的发射与槽的执行
当信号被发射时,Qt 内部触发一个事件,查找与该信号关联的槽函数,然后依次调用这些槽。信号发射通常通过使用 emit
关键字完成。信号发射时,QMetaObject::activate()
会被调用,遍历连接表,激活相应的槽函数。
#define Q_OBJECT \
public: \QT_WARNING_PUSH \Q_OBJECT_NO_OVERRIDE_WARNING \static const QMetaObject staticMetaObject; \virtual const QMetaObject *metaObject() const; \virtual void *qt_metacast(const char *); \virtual int qt_metacall(QMetaObject::Call, int, void **); \QT_TR_FUNCTIONS \
private: \Q_OBJECT_NO_ATTRIBUTES_WARNING \Q_DECL_HIDDEN_STATIC_METACALL static void qt_static_metacall(QObject *, QMetaObject::Call, int, void **); \QT_WARNING_POP \struct QPrivateSignal { explicit QPrivateSignal() = default; }; \QT_ANNOTATE_CLASS(qt_qobject, "")
QMetaObject
是 Qt 框架中的一个类,它用于存储与某个类相关的所有元数据信息。
4. 事件循环与事件分发
4.1 事件循环 (QEventLoop
)
Qt 的事件循环是应用程序的核心部分。事件循环负责监视系统事件(如用户输入、定时器等)和应用程序的信号。当信号被发射时,事件循环将信号封装为事件,并负责调度这些事件以执行相应的槽函数。
4.2 信号发射与事件队列
当信号发射时,如果使用队列连接或跨线程连接,信号会被封装为事件并加入事件队列。事件循环会在适当的时机提取这些事件并执行相应的槽函数,以确保程序的响应性和稳定性。
5. 线程间的信号与槽机制
5.1 跨线程连接
Qt 提供了线程安全的信号与槽机制。当信号与槽位于不同线程时,信号会被封装成事件并传递到目标线程的事件队列中。通过这种方式,Qt 可以安全地在不同线程间传递信号和处理槽函数,而无需显式的线程同步操作。
5.2 事件队列的线程安全
Qt 的事件队列使用加锁机制来确保线程间通信的安全性。通过这种机制,信号事件可以安全地插入到目标线程的事件队列中,并且不会直接在非线程安全的环境中调用槽函数,从而避免了数据竞争和同步问题。
6. 底层实现细节与源码解析
6.1 信号与槽的连接
当 connect()
函数被调用时,Qt 会执行以下步骤完成信号与槽的连接:
- 检查信号和槽的类型是否匹配。
- 查找信号和槽的元数据信息,如信号 ID 和槽函数地址。
- 将信号和槽之间的连接信息存储在连接表中。
6.2 信号的发射与激活
当信号被发射时,Qt 调用 QMetaObject::activate()
函数,该函数负责查找连接表中的槽函数并依次调用。对于跨线程信号,激活函数会将信号封装为事件并插入接收者线程的事件队列中。