Qt的信号槽机制

1. 什么是元对象编译器和元对象系统?

        在开始讲信号槽之前,我们先了解下Qt的框架的核心组成部分,Qt的元对象编译器(MOC)和元对象系统是Qt框架的核心组成部分,它们使得Qt拥有了信号与槽机制、反射(introspection)和属性系统等强大的特性。下面分别解释这两个概念:

1.1 元对象编译器(MOC)

        元对象编译器是Qt特有的一个预处理器,它不是标准C++的一部分。MOC会处理使用了Qt的特殊宏(如`Q_OBJECT`、`signals`、`slots`等)的C++头文件。它的主要任务是为使用了这些宏的类生成额外的C++源代码文件。这个生成的源文件包含了元信息以及信号和槽机制所需的实现代码。

MOC生成的代码包括但不限于:

- 类的元信息(类名、父类、信号和槽的名称等)。
- 用于实现信号和槽机制的函数,包括信号发射的存根和用于槽调用的代码。
- 用于动态属性系统的代码。
- 实现Qt的反射机制的代码,允许在运行时查询对象的类型信息和成员函数。

1.2 元对象系统

        元对象系统是Qt运行时环境的一部分,它使用MOC生成的代码来提供动态特性,如:

- 信号与槽:一个高级的事件订阅和通知机制,用于对象间的通信。
- 对象反射:允许在运行时查询对象的类型信息(如它的类名、它继承的基类、它拥有的信号和槽)。
- 属性系统:允许在运行时查询和修改对象的属性。
- 动态方法调用:允许在运行时调用对象的方法。

        每个通过`Q_OBJECT`宏声明的类,都能在运行时通过其元对象(`QMetaObject`)来访问这些特性。`QMetaObject`实例包含了关于其对应类的所有信息,包括信号、槽、属性等。这种机制使得Qt的对象可以在运行时进行更多动态操作。

        简而言之,元对象编译器为元对象系统生成必需的胶水代码,而元对象系统则利用这些代码,在运行时提供动态的、反射式的特性。这两个元素共同构成了Qt框架中对象间通信和动态类型管理的基础。

2. 信号槽机制

        2.1 元对象编译器(MOC):


               MOC扫描通过`Q_OBJECT`宏标记的类,并为这些类生成附加的C++代码。这个代码包括信号和槽的定义,以及类的元信息(如类名、信号/槽列表、属性等)。   假设有如下类定义:

 class MyClass : public QObject {Q_OBJECTpublic:MyClass(QObject *parent = nullptr) : QObject(parent)   signals:void mySignal(int);public slots:void mySlot(int);};

        MOC将为此类生成一个名为`moc_myclass.cpp`的源文件,其中包含了用于信号和槽的实现细节。这个文件通常包括:
   - 信号的存根(stub)函数
   - 类的元信息(元对象代码)
   - 用于调用槽的静态函数

        2.1.1 信号的存根(stub)函数是什么?       

                在Qt的信号和槽机制中,信号的存根(stub)函数是MOC生成的一段代码,它充当信号的实现。在Qt中,信号函数本身是不包含用户定义的实现的;你只需要在类的头文件中声明它们。当你在代码中发射(emit)一个信号时,实际上是调用了这个存根函数。

                存根函数的主要职责是通知Qt元对象系统有信号发生,并传递任何相关的参数。然后,元对象系统负责调用和这个信号相关联的所有槽函数。

这里是一个信号存根函数的简化示例(伪代码):

// MyClass 类中的信号声明部分
class MyClass : public QObject {Q_OBJECTpublic:...signals:void mySignal(int value);...
};// MOC 生成的存根函数
void MyClass::mySignal(int value) {// 内部生成的代码,用来激活信号QMetaObject::activate(this, &MyClass::staticMetaObject, signalIndex, &value);
}

        在上面的代码中,`mySignal` 信号的存根函数被MOC生成,并且包含了调用 `QMetaObject::activate` 函数的代码。这个 `activate` 函数是元对象系统的一部分,它负责查找所有连接到 `mySignal` 信号的槽,并依次调用它们。

        请注意,开发者不需要编写信号的存根函数;它们是由MOC自动根据类的头文件生成的。开发者只需要声明信号,并在必要的时候使用 `emit` 关键字来发射它们。例如:

emit mySignal(123);

        这行代码在运行时实际上调用的就是MOC为 `mySignal` 信号生成的存根函数。

        2.1.2 activate 函数运行原理

            在Qt中,信号和槽之间的连接是通过 `QObject::connect` 函数建立的。这个函数告诉Qt元对象系统,当特定的信号被发射时,应该调用哪个槽函数。连接可以在运行时动态建立,也就是说,在程序的执行过程中,可以根据需要将任何信号连接到任何槽上。

        下面是一个信号和槽连接的例子:

QObject::connect(sender, &SenderClass::signalName, receiver, &ReceiverClass::slotName);

        在这个例子中,我们假设 `sender` 是一个指向 `SenderClass` 实例的指针,而 `SenderClass` 中有一个名为 `signalName` 的信号。`receiver` 是指向 `ReceiverClass` 实例的指针,`ReceiverClass` 中有一个名为 `slotName` 的槽函数。

        当 `connect` 函数被调用时,Qt元对象系统会记录下信号和槽之间的连接。这个信息被用于在信号发射时,查找和调用所有连接到该信号的槽函数。

`QMetaObject::activate` 函数内部的工作原理如下:

1. 当信号发射时(即,当存根函数被调用时),`activate` 函数被执行。
2. `activate` 函数查询内部的连接列表,这个列表记录了所有连接到该信号的槽。
3. 对于每一个连接,`activate` 函数会调用相应的槽函数。如果槽函数接受参数,`activate` 会传递信号的参数给槽。

        这个过程是由Qt的元对象系统在背后自动管理的,开发者不需要编写代码来处理这些低级细节。您只需要知道如何使用 `connect` 函数来建立连接,以及如何使用 `emit` 关键字来发射信号。

        Qt的信号和槽机制非常强大,因为它允许对象之间进行松耦合的通信。槽函数不需要知道是哪个信号触发了它们,也不需要知道信号来自哪个对象。同样,对象可以发射信号而不需要知道谁将接收它们。这种机制极大地提高了代码的可重用性和可维护性。

        2.1.3 activate函数怎么知道哪个信号发送了?

                `activate`函数是Qt元对象系统的一部分,用于在运行时处理信号的发射和槽的调用。它知道是哪个信号被发射的,因为每次信号发射时,存根函数都会传递特定的信息给`activate`函数。

这个过程中涉及到几个关键的步骤和元素:

1. 信号发射(Emission):
   - 当你调用一个信号(如`emit mySignal(value);`),实际上你调用的是由MOC为该信号生成的存根函数。

2. 信号索引(Signal Index):
   - 每个信号在其类的元对象中都有一个唯一的索引值。这个索引是在编译时由MOC根据信号在类中的声明顺序计算得出的。
   - 当存根函数被调用时,它使用这个索引作为参数之一调用`QMetaObject::activate`函数。

3. 参数传递(Parameter Passing):
   - 除了信号索引,存根函数还将信号的参数作为参数传递给`activate`函数。这样,`activate`函数就可以将这些参数传递给目标槽函数。

4. 槽函数调用(Slot Invocation):
   - `QMetaObject::activate`函数使用信号索引来查找所有连接到该信号的槽,并使用信号的参数来调用它们。连接信息存储在内部的数据结构中,这些数据结构在运行时通过`QObject::connect`函数填充。

下面是`QMetaObject::activate`函数调用的伪代码流程:

// 假设这是由MOC生成的存根函数的调用
void MyClass::mySignal(int value) {// ...省略其他代码...QMetaObject::activate(this, &MyClass::staticMetaObject, signalIndex, &value);
}// activate函数的概念实现
void QMetaObject::activate(QObject *sender, QMetaObject *m, int local_signal_index, void **argv) {// 确定全局信号索引int signal_index = m->methodOffset() + local_signal_index;// 查找对应于信号的所有连接,并调用相应的槽foreach (const Connection &c, connectionsForSignal(signal_index)) {if (c.receiver) {c.slot_method(sender, argv); // argv 包含了所有传递给信号的参数}}
}

        在这个示例中,`activate`函数接收到了信号索引和信号参数。这个索引用于在信号和槽的连接表中查找应该被调用的槽函数。然后,`activate`函数根据这些信息调用所有连接的槽函数,并将信号参数传递给它们。

        这样,`activate`函数就可以知道是哪个信号被发射,并且能够将该信号路由到所有已连接的槽函数。这是Qt信号和槽机制的核心,允许对象之间进行灵活和动态的通信。

        2. 2 信号和槽的存储:


           MOC生成的元信息包含信号和槽的名称和参数类型。这个信息存储在每个对象实例的元对象中。

        2.3. 信号和槽的连接:


           连接信号和槽时使用的`QObject::connect()`函数大致上是这样的:

QObject::connect(&sender, &MyClass::mySignal, &receiver, &MyClass::mySlot);

         在内部,`QObject::connect()`会创建一个连接数据结构,它包含了关于信号和槽的信息,以及它们所属的对象。

2.4. 信号的发射:

        当你调用`emit mySignal(10);`时,MOC为`mySignal`生成的代码会被执行。这段代码会遍历所有与该信号连接的槽,并调用它们。这通常是通过调用`QMetaObject::activate()`来完成的。

  // 伪代码void MyClass::mySignal(int value) {// MOC生成的信号函数QMetaObject::activate(this, &MyClass::staticMetaObject, signalIndex, &value);}

2.5. 槽函数的调用:

        槽函数的调用是通过`QMetaObject::activate()`在运行时通过元对象系统完成的。当一个信号被激活时,Qt会查找与之相连接的所有槽,并逐个调用它们。

   // 伪代码void QMetaObject::activate(QObject *sender, QMetaObject *m, int local_signal_index, void **argv) {// 遍历连接列表for (每个连接到该信号的接收器) {// 调用槽函数slot = connection->slot;(receiver->*slot)(/* 参数转换和传递 */);}}

        如果信号和槽在不同的线程,Qt会安排一个事件(QEvent)并将其发送到接收者所在的线程的事件队列中,事件处理函数将在目标线程中调用槽函数。

        信号和槽机制的核心是QObject和QMetaObject。QObject提供了基础的通信能力,而QMetaObject负责存储类的元信息和提供动态类型检查和方法调用等能力。通过MOC生成的代码和这些类的合作,Qt可以在运行时动态地连接对象,传递参数,并且安全地调用方法。这就是Qt信号和槽机制强大灵活性的来源。

3. connect的第五个参数

        在Qt中,`QObject::connect` 方法有一个重载版本,它接受第五个参数,这个参数是一个枚举 `Qt::ConnectionType`,它指定了信号和槽之间连接的类型。`Qt::ConnectionType` 枚举的值决定了信号是直接发送到槽,还是通过事件队列来进行异步调用。这个参数是可选的,如果不提供,默认是 `Qt::AutoConnection`。

`Qt::ConnectionType` 枚举的几个可能的值包括:

- `Qt::AutoConnection` (默认): Qt会根据接收者是否位于发射者的线程中自动选择是使用 `Qt::DirectConnection` 还是 `Qt::QueuedConnection`。如果接收者和发射者在同一个线程,它会使用 `Qt::DirectConnection`,否则会使用 `Qt::QueuedConnection`。

- `Qt::DirectConnection`: 槽函数会在信号发射的那一刻立即被调用,无论接收者和发射者是否在同一个线程。这意味着槽函数是在信号发射者的线程上下文中执行的。

- `Qt::QueuedConnection`: 发射信号的事件会被放入接收者所在的线程的事件队列中。接收者的槽函数将会在接收者所在的线程的事件循环中稍后被调用。这种连接类型在跨线程通信时特别有用。

- `Qt::BlockingQueuedConnection`: 类似于 `Qt::QueuedConnection`,但是发射信号的线程会等到接收者线程中的槽函数执行完毕后再继续执行。这种类型的连接必须在不同线程之间使用,否则会导致死锁。

- `Qt::UniqueConnection`: 这个参数可以和其他类型组合使用(通过按位或操作)。它确保不会为同一信号和槽创建重复的连接。如果尝试重复连接,`connect` 函数将不会建立连接并返回 `false`。

下面是一个带有第五个参数的 `connect` 函数的例子:

QObject::connect(sender, &SenderClass::signalName,receiver, &ReceiverClass::slotName, Qt::QueuedConnection);

        在这个例子中,当 `signalName` 被发射时,`slotName` 槽函数将通过事件队列异步调用。这是跨线程工作时的一个典型用例。        

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/737764.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

网络安全防御保护 Day7

1.因为FW1和FW2已处于双机热备状态,所以只需要对主设备进行配置即可。进入FW1的配置界面,选择“网络”界面,点击“IPsec”,进行IPsec通道的基本配置,这里选择的是“电信”链路。 2.完成上述配置后,进行待加…

揭秘防爆气象仪器:超声波监测原理如何守护安全?

TH-WFB5随着科技的不断进步,防爆气象仪器已成为现代工业生产中不可或缺的重要设备。这些仪器采用先进的超声波监测原理,能够实时、准确地监测环境中的各种气象参数,为企业的安全生产提供有力保障。 一、超声波监测原理简介 超声波是指频率高于…

架构师面试100问?

面试架构师时,需要考察广泛的知识领域,包括技术、架构设计、团队管理、沟通能力等方面。以下是一些可能的面试问题,涵盖了多个方面问题: 介绍一下你的技术背景和经验。你在之前的项目中扮演过哪些角色?你对微服务架构…

从焦虑到成功:一个软件测试工程师的逆袭之路

日常大家聊天时经常提及一个关键词——大环境不好,由此带来了很多行为的变化,有的人迷茫,有的人躺平。本文给大家介绍发生在我身上和身边的真实案例,希望能带给你一些输入。 案例一:曾经的我也极度焦虑 我是2008年参加…

探索Java多线程开发

在Java编程中,多线程是一种强大的工具,它允许程序员编写能够并行执行多个任务的程序。这不仅可以提高程序的执行效率,还能更好地利用计算机的多核处理器。Java提供了内置的支持来简化多线程编程的复杂性,使得开发者能够更加专注于…

CentOS7配置静态IP

文章目录 CentOS7配置静态IP一、前言1.场景2.环境 二、正文1)确定网络接口名称2)配置固定IP和DNS3)重启网络服务4)验证配置 CentOS7配置静态IP 一、前言 1.场景 在 CentOS7上设置静态 IP 和 DNS。 2.环境 CentOS Linux 版本&…

BUU [网鼎杯 2020 半决赛]AliceWebsite

BUU [网鼎杯 2020 半决赛]AliceWebsite 开题&#xff1a; hint附件是源码。在index.php中有一个毫无过滤的本地文件包含 <?php $action (isset($_GET[action]) ? $_GET[action] : home.php); if (file_exists($action)) {include $action; } else {echo "File not…

Unity接入海量RTSP直播流,多线程渲染

Unity 播放海量RTSP视频&#xff0c;多线程播放&#xff0c;长时间运行稳定 Unity 播放海量RTSP视频&#xff0c;多线程渲染 使用的libvlc库&#xff0c;目前支持windows、Linux平台 25路视频同时播放&#xff0c;测试持续运行1晚上&#xff0c;运行稳定&#xff0c;不掉帧&am…

安卓studio安装(从安装到配置到helloworld)

安卓studio安装 2024.3.11官网的版本&#xff08;有些翻墙步骤下载东西也解决了&#xff09; 这次写的略有草率&#xff0c;后面会更新布局的&#xff0c;因为截图量太大了&#xff0c;有需要的小伙伴可以试着接受一下哈哈哈哈 !(https://gitee.com/jiuzheyangbawjf/img/raw/ma…

二叉搜索树题目:前序遍历构造二叉搜索树

文章目录 题目标题和出处难度题目描述要求示例数据范围 解法一思路和算法代码复杂度分析 解法二思路和算法代码复杂度分析 解法三思路和算法代码复杂度分析 解法四思路和算法代码复杂度分析 题目 标题和出处 标题&#xff1a;前序遍历构造二叉搜索树 出处&#xff1a;1008. …

android JNI float *转MutableList

data class Test(var data:MutableList<Float> )JNIEXPORT void JNICALL Java_NativeUtils_assignFloatArrayToHealth(JNIEnv *env, jclass clazz, jobject obj, jfloatArray cData) {jclass objClass env->GetObjectClass(obj);// 获取 Test类中的 data 属性jfieldI…

【解决】Sublime Text找不到Package Control选项,且输入install也不显示Install Package(其中一种情况)

【问题描述】 Sublime Text 找不到 Package Control 选项&#xff0c;且输入 install 也不显示 Install Package 【解决方法】&#xff08;其中一种情况&#xff09; 1、工具栏 Preferences -> Settings&#xff0c;点开查看设置文档 2、检查 "ignored_packages&q…

递归在解决链表问题中的应用

递归在解决链表问题时通常很有用&#xff0c;尤其是对于以下类型的问题&#xff1a; 遍历问题&#xff1a;递归可以很自然地用来遍历链表&#xff0c;比如打印链表的值、计算链表的长度等。 搜索问题&#xff1a;当需要搜索链表中的某个特定节点时&#xff0c;递归可以简化代码…

提示并输入一个字符串,统计该字符中大写、小写字母个数、数字个数、空格个数以及其他字符个数要求使用C++风格字符串完成

#include <iostream> #include <array> using namespace std;int main() {cout<<"请输入一个字符串"<<endl;//array<string,100> str;string str;getline(cin,str);int daxie0,xiaoxie0,num0,space0,other0;int lenstr.size();;for(in…

使用sunny-Ngrok免费实现内网穿透

1、注册用户 网址&#xff1a;https://ngrok.cc/login/register 2、实名认证 注册成功之后&#xff0c;登录系统&#xff0c;进行实名认证&#xff0c;认证费两元。认证通过后才能开通隧道。 3、开通隧道 选择免费的隧道 4、开通成功后查看开通隧道 5、启动隧道 下载…

日常002:双系统时间不一致问题

日常002&#xff1a;双系统时间不一致问题 推荐解决方法&#xff1a;Windows管理员执行如下命令&#xff0c;将硬件时钟设置为UTC时间 reg add "HKEY_LOCAL_MACHINE\System\CurrentControlSet\Control\TimeZoneInformation" /v RealTimeIsUniversal /d 1 /t REG_DWO…

计算机网络—以太网接口和链路配置

目录 1.拓扑图 2.以太网交换机基础配置 3.配置手动模式的链路聚合 4.配置静态 LACP 模式的链路聚合 5.配置文件 1.拓扑图 2.以太网交换机基础配置 华为交换机接口默认开启了自协商功能&#xff0c;需要手动配置S1与 S2上G0/0/9和G0/0/10接口的速率。 首先修改交换机的设…

力扣--动态规划97.交错字符串

思路分析&#xff1a; 动态规划数组定义&#xff1a; dp[i][j] 表示&#xff1a;使用字符串 s1 的前 i 个字符和字符串 s2 的前 j 个字符&#xff0c;能否构成字符串 s3 的前 i j 个字符的交错组合。 初始化&#xff1a; dp[0][0] 初始化为 1&#xff0c;表示空串是 s1 和 s2 …

QWebEngineView与js交互

1.先用QWebChannel建立交互通道 QWebChannel* channel new QWebChannel(m_view); m_view->page()->setWebChannel(channel); 2.在通道上注册交互对象变量。 MFWebChannelBridge* pBridge new MFWebChannelBridge(channel); channel->registerObjec…

Docker学习——容器

容器是 Docker 的另一个核心概念&#xff0c;简单来说&#xff0c;容器是镜像的一个运行实例。所不同的是&#xff0c;镜像是静态的只读文件&#xff0c;而容器带有运行时需要的可写文件层&#xff0c;同时&#xff0c;容器中的应用进程处于运行状态。 创建容器 1新建容器 可以…