Qt信号与槽机制的基石-MOC详解
引入
上篇讲到了信号与槽就是实现的观察者模式,那具体如何生成映射表就是moc做的事情。
一、moc简介
1. moc的定义
moc 全称是 Meta-Object Compiler,也就是“元对象编译器”,它主要用于处理C++源文件中的非标准C++代码。Qt 程序在交由标准编译器编译之前,先要使用 moc 分析 C++ 源文件。如果它发现在一个头文件中包含了宏 Q_OBJECT,则会生成另外一个 C++ 源文件。这个源文件中包含了 Q_OBJECT 宏的实现代码。这个新的文件名字将会是原文件名前面加上 moc_ 构成。这个新的文件同样将进入编译系统,最终被链接到二进制代码中去。因此我们可以知道,这个新的文件不是“替换”掉旧的文件,而是与原文件一起参与编译。另外,我们还可以看出一点,moc 的执行是在预处理器之前。因为预处理器执行之后,Q_OBJECT 宏就不存在了。
既然每个源文件都需要 moc 去处理,那么我们在什么时候调用了它呢?实际上,如果你使用 qmake 的话,这一步调用会在生成的 makefile 中展现出来。从本质上来说,qmake 不过是一个 makefile 生成器,因此,最终执行还是通过 make 完成的。
2. moc的作用
Moc的主要功能如下:
- 清理代码:Moc会删除源文件中与C++标准不符的代码,例如某些未使用的变量、类型或函数。
- 生成C++类和函数:Moc会将源文件中的预处理器指令和特殊C++语句转换为C++类和函数,这些类和函数可以在其他Qt模块中使用。
- 处理资源文件:Moc可以将源文件中的资源文件(如图像、字符串等)转换为C++类和函数,使得这些资源可以在其他Qt模块中访问。
- 提供扩展:Moc可以处理模板代码,从而使得代码可以根据特定的Qt模块或对象模型扩展。
- 清理和清理:Moc支持两次清理,第一次清理通常用于移除不需要的C++代码,第二次清理用于移除第一次清理后仍然存在的未使用的代码。
二、moc详解
2.1 moc的工作原理
前面我们说过,Qt 不是使用的“标准的” C++ 语言,而是对其进行了一定程度的“扩展”。这里我们从Qt新增加的关键字就可以看出来:signals、slots 或者 emit。所以有人会觉得 Qt 的程序编译速度慢,这主要是因为在 Qt 将源代码交给标准 C++ 编译器,如 gcc 之前,需要事先将这些扩展的语法去除掉。完成这一操作的就是 moc。
Qt库中的moc(Meta-Object Compiler)是一个为特定Qt库生成元对象代码(例如,QObject、QMetaObject、QMetaObject::invokeMethod等)的工具。moc的工作原理主要包括以下几个步骤:
- 扩展原始代码:moc首先读取源代码文件(通常是C++文件),并根据该文件中的元对象代码生成相应的元对象代码。moc将元对象代码添加到源代码文件中,以扩展原始代码。
- 解析元对象:moc接着解析源代码文件中的元对象代码。这些元对象代码包括:
a. Q_OBJECT宏:这是Qt库中最重要的元对象标识,表示该类需要实现元对象系统(如信号和槽机制)。moc为所有定义了Q_OBJECT宏的类生成相应的元对象代码。
b. 信号和槽:moc将读取和解析信号和槽的声明,以生成相应的元对象代码。
c. 其他元对象属性:moc解析并处理其他元对象属性,例如元对象导出表、元对象属性表等。 - 生成元对象代码:moc根据解析到的元对象代码生成相应的元对象代码。这些元对象代码包括:
- QMetaObject:生成的元对象代码包含了所有元对象的属性和方法。
- 元对象导出表:生成的元对象代码包含了类中所有元对象方法的符号,这些符号用于其他库或代码通过Q_OBJECT宏识别和连接这些方法。
- 元对象属性表:生成的元对象代码包含了类中所有元对象属性的符号,这些符号用于其他库或代码读取和设置这些属性。
- 输出元对象代码:moc将生成的元对象代码写回原始代码文件。此时,原始代码文件已经包含了额外的元对象代码,用于实现元对象系统。
2.2 moc生成的文件结构
对于每一个 QObject 类的派生类,qt 都会使用 moc 命令之生成附加的 moc_xxx.cpp 文件。在 moc_xxx.cpp 文件中,包含了 QObject 派生类的附加信息。
我们创建一个qt对象,生成出一个moc文件,然后具体去分析这个文件各个函数的意义:
#ifndef MAINWINDOW_H
#define MAINWINDOW_H#include <QMainWindow>class MainWindow : public QMainWindow
{Q_OBJECT
public:MainWindow(QWidget *parent = nullptr);~MainWindow();
};
#endif // MAINWINDOW_H
/****************************************************************************
** Meta object code from reading C++ file 'mainwindow.h'
**
** Created by: The Qt Meta Object Compiler version 67 (Qt 5.12.10)
**
** WARNING! All changes made in this file will be lost!
*****************************************************************************/#include "mainwindow.h"
#include <QtCore/qbytearray.h>
#include <QtCore/qmetatype.h>
#if !defined(Q_MOC_OUTPUT_REVISION)
#error "The header file 'mainwindow.h' doesn't include <QObject>."
#elif Q_MOC_OUTPUT_REVISION != 67
#error "This file was generated using the moc from 5.12.10. It"
#error "cannot be used with the include files from this version of Qt."
#error "(The moc has changed too much.)"
#endifQT_BEGIN_MOC_NAMESPACE
QT_WARNING_PUSH
QT_WARNING_DISABLE_DEPRECATED
struct qt_meta_stringdata_MainWindow_t {QByteArrayData data[1];char stringdata0[11];
};
#define QT_MOC_LITERAL(idx, ofs, len) \Q_STATIC_BYTE_ARRAY_DATA_HEADER_INITIALIZER_WITH_OFFSET(len, \qptrdiff(offsetof(qt_meta_stringdata_MainWindow_t, stringdata0) + ofs \- idx * sizeof(QByteArrayData)) \)
// #: 这里是它的字符数据对应表
static const qt_meta_stringdata_MainWindow_t qt_meta_stringdata_MainWindow = {{
QT_MOC_LITERAL(0, 0, 10) // "MainWindow"},"MainWindow"
};
#undef QT_MOC_LITERALstatic const uint qt_meta_data_MainWindow[] = {// content:8, // revision0, // classname0, 0, // classinfo0, 0, // methods0, 0, // properties0, 0, // enums/sets0, 0, // constructors0, // flags0, // signalCount0 // eod
};//#:因为这里没有信号和槽函数,所以没有相应静态的元调用
void MainWindow::qt_static_metacall(QObject *_o, QMetaObject::Call _c, int _id, void **_a)
{Q_UNUSED(_o);Q_UNUSED(_id);Q_UNUSED(_c);Q_UNUSED(_a);
}QT_INIT_METAOBJECT const QMetaObject MainWindow::staticMetaObject = { {&QMainWindow::staticMetaObject,qt_meta_stringdata_MainWindow.data,qt_meta_data_MainWindow,qt_static_metacall,nullptr,nullptr
} };const QMetaObject *MainWindow::metaObject() const
{return QObject::d_ptr->metaObject ? QObject::d_ptr->dynamicMetaObject() : &staticMetaObject;
}void *MainWindow::qt_metacast(const char *_clname)
{if (!_clname) return nullptr;if (!strcmp(_clname, qt_meta_stringdata_MainWindow.stringdata0))return static_cast<void*>(this);return QMainWindow::qt_metacast(_clname);
}int MainWindow::qt_metacall(QMetaObject::Call _c, int _id, void **_a)
{_id = QMainWindow::qt_metacall(_c, _id, _a);return _id;
}
QT_WARNING_POP
QT_END_MOC_NAMESPACE
在生成的 moc_sender.cpp 中:
qt_meta_stringdata_Sender 是一个字面量表,也是可称之为符号表。
它的存在是为了能够以 idx 就能获取对应的字符串。比如这里我要找到MainWindow这个字符, idx = 0,对应的字串在则为 (const char *)(qt_meta_stringdata_Sender.stringdata0+0)
此后可以用该对应的QByteArrayData的data()方法取得Hello字符串
2.3 Q_OBJECT展开记录
可以看到,moc_test.cpp 里面为 Test 类增加了很多函数。然而,我们并没有实际写出这些函数,它是怎么加入类的呢?,他是通过Q_OBJECT宏定义出来的~
他带来了
👋• QT_WARNING_PUSH宏
QT_WARNING_PUSH宏是用来将编译器的警告设置推入堆栈的宏。它会暂时关闭编译器的警告,并将当前的警告设置保存在堆栈中。这样,在QT_WARNING_POP宏被调用之后,之前的警告设置会被恢复,从而保证代码的其他部分不受到QT_WARNING_PUSH宏所设置的警告影响。
• 👋Q_OBJECT_NO_OVERRIDE_WARNING宏
Q_OBJECT_NO_OVERRIDE_WARNING宏用于禁止编译器对未重写(override)父类中的虚函数发出的警告。在Qt的信号-槽机制中,通常需要在自定义的类中重写父类的虚函数来实现信号和槽的连接。但是,一些编译器可能会发出警告,提示这些虚函数未被重写。使用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 宏
QT_TR_FUNCTIONS宏是用来定义一组用于国际化(i18n)的翻译函数的宏。这些函数包括tr()、QObject::tr()、QT_TRANSLATE_NOOP()等,用于在Qt应用程序中进行字符串的翻译和本地化。
这个宏的最关键的地方是是声明了一个只读的静态成员变量staticMetaObject,以及3个public的成员函数,通过moc工具就可以生成以下变量和函数的定义
三、实现信号与槽的实现(为啥是moc)
信号的触发
信号 就是普通的类成员函数,信号只要声明(declare),不需要实现(implement),实现由moc(元对象编译器)自动生成。
信号的触发,可以用emit,也可以直接调用函数。
信号的实现,是直接调用了QMetaObject::activate函数。其中0代表miao这个函数的索引号。
QMetaObject::activate函数的实现,在Qt源码的QObject.cpp文件中,略微复杂一些,且不同版本的Qt,实现差异都比较大,
这里总结一下大致的实现:
先找出与当前信号连接的所有对象-槽函数,再逐个处理。
这里处理的方式,分为三种:
if((c->connectionType == Qt::AutoConnection && !receiverInSameThread)|| (c->connectionType == Qt::QueuedConnection)) {// 队列处理
} else if (c->connectionType == Qt::BlockingQueuedConnection) {// 阻塞处理// 如果同线程,打印潜在死锁。
} else {//直接调用槽函数或回调函数
}
receiverInSameThread表示当前线程id和接收信号的对象的所在线程id是否相等。
- 如果信号-槽连接方式为QueuedConnection,不论是否在同一个线程,按队列处理。
- 如果信号-槽连接方式为Auto,且不在同一个线程,也按队列处理。
- 如果信号-槽连接方式为阻塞队列BlockingQueuedConnection,按阻塞处理。
(注意同一个线程就不要按阻塞队列调用了。因为同一个线程,同时只能做一件事,本身就是阻塞的,直接调用就好了,如果走阻塞队列,则多了加锁的过程。如果槽中又发了同样的信号,就会出现死锁:加锁之后还未解锁,又来申请加锁。)
队列处理,就是把槽函数的调用,转化成了QMetaCallEvent事件,通过QCoreApplication::postEvent放进了事件循环。
等到下一次事件分发,相应的线程才会去调用槽函数。
槽和moc生成
slot函数我们自己实现了,moc不会做额外的处理,所以自动生成的moc_Jerry.cpp文件中,只有Q_OBJECT宏的展开
他的具体调用方式就是在qt_static_metacall函数中调用具体的函数(不需要moc做额外处理)
使用moc的注意事项
一个没有定义 Q_OBJECT 宏的类与它最接近的父类是同一类型的。也就是说,如果 A 继承了 QObject 并且定义了 Q_OBJECT,B 继承了 A 但没有定义 Q_OBJECT,C 继承了 B,则 C 的 QMetaObject::className() 函数将返回 A,而不是本身的名字。因此,为了避免这一问题,所有继承了 QObject 的类都应该定义 Q_OBJECT 宏,不管你是不是使用信号槽。
相关文章:

Qt信号与槽机制的基石-MOC详解
引入 上篇讲到了信号与槽就是实现的观察者模式,那具体如何生成映射表就是moc做的事情。 一、moc简介 1. moc的定义 moc 全称是 Meta-Object Compiler,也就是“元对象编译器”,它主要用于处理C源文件中的非标准C代码。Qt 程序在交由标准编…...
关于单体架构缓存刷新实现方案
背景 如果各位看官是分布式项目应该都采用分布式缓存了,例如redis等,分布式缓存不在本次讨论范围哈;我个人建议是,如果是用户量比较大,建议采用分布式缓存机制,后期可以很容易前后到分布式服务或微服务。 …...

洞悉安全现状,建设网络安全防护新体系
一、“网络攻防演练行动“介绍 国家在2016年发布《网络安全法》,出台网络安全攻防演练相关规定:关键信息基础设施的运营者应“制定网络安全事件应急预案,并定期进行演练”。同年“实战化网络攻防演练行动”成为惯例。由公安部牵头࿰…...

spring中怎么通过静态工厂和动态工厂获取对象以及怎么通过 FactoryBean 获取对象
😀前言 本章是spring基于XML 配置bean系类中第4篇讲解spring中怎么通过静态工厂和动态工厂获取对象以及怎么通过 FactoryBean 获取对象 🏠个人主页:尘觉主页 🧑个人简介:大家好,我是尘觉,希望…...
三元组表实现矩阵相加(数据结构)
代码: 含注释,供参考 #include <stdio.h> #include <stdlib.h>typedef struct {int row,col,value;//分别为行数,列数,数值 } Triple; typedef struct {int len;//非零数值的个数Triple data[200]; } TSMatrix;void…...

ChinaJoy 2023微星雷鸟17游戏本震撼发布:搭载AMD锐龙9 7945HX首发8499元
ChinaJoy 2023展会中微星笔记本再次给大家带来惊喜,发布了搭载AMD移动端16大核的旗舰游戏本:雷鸟17,更重要的这样一款旗舰性能的游戏本,首发价8499元堪称当今游戏本市场中的“性价比爆款”! 本着和玩家一同制霸游戏战场…...

各种运算符
算术运算符 1.双目运算符 */%:从左到右优先级依次降低 一些注意事项: 1若a/b都为整型那么结果也为整型,如果ab其中有一个为实型,结果则为实型 求余运算符注意事项: 1运算对象必须为整数 2运算结果的整数跟左边数字的…...

yolov3-tiny原理解析及代码分析
前言 从去年十一月份开始学习yolo神经网络用于目标识别的硬件实现,到现在已经六个月了。一个硬件工程师,C/C基础都差劲的很,对照着darknet作者的源码和网上东拼西凑的原理讲解,一点一点地摸索。刚开始进度很慢,每天都…...
深入了解Redis-实战篇-短信登录
深入了解Redis-实战篇-短信登录 一、故事背景二、知识点主要构成2.1、短信登录2.1.1、生成随机短信验证码引入maven依赖生成验证码 2.1.2、实现登录校验拦截器2.1.3、基于Redis实现短信登录2.1.3.1、发送验证码时存入Redis2.1.3.2、登录时校验验证码 2.1.4、解决状态登录刷新的…...

Mysql的锁
加锁的目的 对数据加锁是为了解决事务的隔离性问题,让事务之前相互不影响,每个事务进行操作的时候都必须先加上一把锁,防止其他事务同时操作数据。 事务的属性 (ACID) 原子性 一致性 隔离性 持久性 事务的隔离级别 锁…...

【EI/SCOPUS征稿】2023年算法、图像处理与机器视觉国际学术会议(AIPMV2023)
2023年算法、图像处理与机器视觉国际学术会议(AIPMV2023) 2023 International Conference on Algorithm, Image Processing and Machine Vision(AIPMV2023) 2023年算法、图像处理与机器视觉国际学术会议(AIPMV2023&am…...

Go语言性能优化建议与pprof性能调优详解——结合博客项目实战
文章目录 性能优化建议Benchmark的使用slice优化预分配内存大内存未释放 map优化字符串处理优化结构体优化atomic包小结 pprof性能调优采集性能数据服务型应用go tool pprof命令项目调优分析修改main.go安装go-wrk命令行交互界面图形化火焰图 性能优化建议 简介: …...
K阶斐波那契数列(数据结构)
代码: 注意k阶斐波那契序列定义:第k和k1项为1,前k - 1项为0,从k项之后每一项都是前k项的和 例如:k2时,斐波那契序列为:0,1,1,2,3,5,8,13... k3时,斐波那契序列为:0,0,…...

【JavaEE】博客系统前后端交互
目录 一、准备工作 二、数据库的表设计 三、封装JDBC数据库操作 1、创建数据表对应的实体类 2、封装增删改查操作 四、前后端交互逻辑的实现 1、博客列表页 1.1、展示博客列表 1.2、博客详情页 1.3、登录页面 1.4、强制要求用户登录,检查用户的登录状态 …...

Redis 简介
文章目录 Redis 简介 Redis 简介 Redis(Remote Dictionary Server),远程词典服务器,基于 C/S 架构,是一个基于内存的键值型 NoSQL 数据库,开源,遵守 BSD 协议,Redis 由 C语言 实现。…...

CS162 13-17 虚拟内存
起源 为啥我们需要虚拟内存-----------需求是啥? 可以给程序提供一个统一的视图,比如多个程序运行同一个代码段的话,同一个kernel,就可以直接共享 cpu眼里的虚拟内存 无限内存的假象 设计迭代过程 为啥这样设计? 一…...

接口自动化测试-Jmeter+ant+jenkins实战持续集成(详细)
目录:导读 前言一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结(尾部小惊喜) 前言 1、下载安装配置J…...

最长连续序列——力扣128
文章目录 题目描述法一 哈希表 题目描述 法一 哈希表 用一个哈希表存储数组中的数,这样查看一个数是否存在即能优化至 O(1) 的时间复杂度 每次在哈希表中检查是否存在 x−1 即能判断是否需要跳过 int longestConsecutive(vector<int>& nums){unordered_s…...

uniapp app端 echarts 设置tooltip的formatter不生效问题以及解决办法
需求一: y轴数据处理不同数据增加不同单位 需求二: 自定义图表悬浮显示的内容 需求一:实现方式 在yAxis里面添加formatter yAxis: [{//y轴显示value的设置axisLabel: {show: true,formatter (value, index) > {var valueif (value > 1…...

Spring入门-技术简介、IOC技术、Bean、DI
前言 Spring是一个开源的项目,并不是单单的一个技术,发展至今已形成一种开发生态圈。也就是说我们可以完全使用Spring技术完成整个项目的构建、设计与开发。Spring是一个基于IOC和AOP的架构多层j2ee系统的架构。 SpringFramework:Spring框架…...

深度学习在微纳光子学中的应用
深度学习在微纳光子学中的主要应用方向 深度学习与微纳光子学的结合主要集中在以下几个方向: 逆向设计 通过神经网络快速预测微纳结构的光学响应,替代传统耗时的数值模拟方法。例如设计超表面、光子晶体等结构。 特征提取与优化 从复杂的光学数据中自…...
java_网络服务相关_gateway_nacos_feign区别联系
1. spring-cloud-starter-gateway 作用:作为微服务架构的网关,统一入口,处理所有外部请求。 核心能力: 路由转发(基于路径、服务名等)过滤器(鉴权、限流、日志、Header 处理)支持负…...

基于距离变化能量开销动态调整的WSN低功耗拓扑控制开销算法matlab仿真
目录 1.程序功能描述 2.测试软件版本以及运行结果展示 3.核心程序 4.算法仿真参数 5.算法理论概述 6.参考文献 7.完整程序 1.程序功能描述 通过动态调整节点通信的能量开销,平衡网络负载,延长WSN生命周期。具体通过建立基于距离的能量消耗模型&am…...
2024年赣州旅游投资集团社会招聘笔试真
2024年赣州旅游投资集团社会招聘笔试真 题 ( 满 分 1 0 0 分 时 间 1 2 0 分 钟 ) 一、单选题(每题只有一个正确答案,答错、不答或多答均不得分) 1.纪要的特点不包括()。 A.概括重点 B.指导传达 C. 客观纪实 D.有言必录 【答案】: D 2.1864年,()预言了电磁波的存在,并指出…...

Docker 本地安装 mysql 数据库
Docker: Accelerated Container Application Development 下载对应操作系统版本的 docker ;并安装。 基础操作不再赘述。 打开 macOS 终端,开始 docker 安装mysql之旅 第一步 docker search mysql 》〉docker search mysql NAME DE…...

Razor编程中@Html的方法使用大全
文章目录 1. 基础HTML辅助方法1.1 Html.ActionLink()1.2 Html.RouteLink()1.3 Html.Display() / Html.DisplayFor()1.4 Html.Editor() / Html.EditorFor()1.5 Html.Label() / Html.LabelFor()1.6 Html.TextBox() / Html.TextBoxFor() 2. 表单相关辅助方法2.1 Html.BeginForm() …...
2025.6.9总结(利与弊)
凡事都有两面性。在大厂上班也不例外。今天找开发定位问题,从一个接口人不断溯源到另一个 接口人。有时候,不知道是谁的责任填。将工作内容分的很细,每个人负责其中的一小块。我清楚的意识到,自己就是个可以随时替换的螺丝钉&…...

python可视化:俄乌战争时间线关键节点与深层原因
俄乌战争时间线可视化分析:关键节点与深层原因 俄乌战争是21世纪欧洲最具影响力的地缘政治冲突之一,自2022年2月爆发以来已持续超过3年。 本文将通过Python可视化工具,系统分析这场战争的时间线、关键节点及其背后的深层原因,全面…...

SQLSERVER-DB操作记录
在SQL Server中,将查询结果放入一张新表可以通过几种方法实现。 方法1:使用SELECT INTO语句 SELECT INTO 语句可以直接将查询结果作为一个新表创建出来。这个新表的结构(包括列名和数据类型)将与查询结果匹配。 SELECT * INTO 新…...

uni-app学习笔记二十七--设置底部菜单TabBar的样式
官方文档地址:uni.setTabBarItem(OBJECT) | uni-app官网 uni.setTabBarItem(OBJECT) 动态设置 tabBar 某一项的内容,通常写在项目的App.vue的onLaunch方法中,用于项目启动时立即执行 重要参数: indexnumber是tabBar 的哪一项&…...