Qt信号与槽机制的基石-MOC详解

news2024/11/25 20:21:30

引入

上篇讲到了信号与槽就是实现的观察者模式,那具体如何生成映射表就是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的主要功能如下:

  1. 清理代码:Moc会删除源文件中与C++标准不符的代码,例如某些未使用的变量、类型或函数。
  2. 生成C++类和函数:Moc会将源文件中的预处理器指令和特殊C++语句转换为C++类和函数,这些类和函数可以在其他Qt模块中使用。
  3. 处理资源文件:Moc可以将源文件中的资源文件(如图像、字符串等)转换为C++类和函数,使得这些资源可以在其他Qt模块中访问。
  4. 提供扩展:Moc可以处理模板代码,从而使得代码可以根据特定的Qt模块或对象模型扩展。
  5. 清理和清理: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的工作原理主要包括以下几个步骤:

  1. 扩展原始代码:moc首先读取源代码文件(通常是C++文件),并根据该文件中的元对象代码生成相应的元对象代码。moc将元对象代码添加到源代码文件中,以扩展原始代码。
  2. 解析元对象:moc接着解析源代码文件中的元对象代码。这些元对象代码包括:
    a. Q_OBJECT宏:这是Qt库中最重要的元对象标识,表示该类需要实现元对象系统(如信号和槽机制)。moc为所有定义了Q_OBJECT宏的类生成相应的元对象代码。
    b. 信号和槽:moc将读取和解析信号和槽的声明,以生成相应的元对象代码。
    c. 其他元对象属性:moc解析并处理其他元对象属性,例如元对象导出表、元对象属性表等。
  3. 生成元对象代码:moc根据解析到的元对象代码生成相应的元对象代码。这些元对象代码包括:
    • QMetaObject:生成的元对象代码包含了所有元对象的属性和方法。
    • 元对象导出表:生成的元对象代码包含了类中所有元对象方法的符号,这些符号用于其他库或代码通过Q_OBJECT宏识别和连接这些方法。
    • 元对象属性表:生成的元对象代码包含了类中所有元对象属性的符号,这些符号用于其他库或代码读取和设置这些属性。
  4. 输出元对象代码: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.)"
#endif

QT_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_LITERAL

static const uint qt_meta_data_MainWindow[] = {

 // content:
       8,       // revision
       0,       // classname
       0,    0, // classinfo
       0,    0, // methods
       0,    0, // properties
       0,    0, // enums/sets
       0,    0, // constructors
       0,       // flags
       0,       // signalCount

       0        // 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 宏,不管你是不是使用信号槽。

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

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

相关文章

使用go与智能合约交互之使用abigen生成合约go文件进行调用

前两篇文章&#xff0c;我们讲解了go如何通过函数选择器、abi调用的方式与智能合约交互&#xff0c;那么有没有一种更加便捷的方式&#xff08;就好像调用预先定义好的方法一样&#xff09;与智能合约交互呢&#xff1f;答案是有的&#xff0c;本章我们就来学习一下如何使用abi…

Stable Diffusion - 真人照片的高清修复 (StableSR + GFPGAN) 最佳实践

欢迎关注我的CSDN&#xff1a;https://spike.blog.csdn.net/ 本文地址&#xff1a;https://spike.blog.csdn.net/article/details/132032216 GFPGAN (Generative Facial Prior GAN) 算法&#xff0c;用于实现真实世界的盲脸恢复的算法&#xff0c;利用预训练的面部 GAN&#xf…

Linux和TSN网络

时间敏感网络是传统以太网的扩展&#xff0c;实现实时能力和确定性通信。终端通常是基于Linux OS 的&#xff0c;它的实时能力是通过PREEMPT-RT 补丁实现的。而设备的实时性网络是通过时间敏感性网络技术实现。到目前为止&#xff0c;大多数Linux OS 的TSN 都是通过特殊的解决方…

《cuda c编程权威指南》04 - 使用块和线程索引映射矩阵索引

目录 1. 解决的问题 2. 分析 3. 方法 4. 代码示例 1. 解决的问题 利用块和线程索引&#xff0c;从全局内存中访问指定的数据。 2. 分析 通常情况下&#xff0c;矩阵是用行优先的方法在全局内存中线性存储的。如下。 8列6行矩阵&#xff08;nx,ny&#xff09;&#xff08;…

链表OJ题讲解1

&#x1f493;博主个人主页:不是笨小孩&#x1f440; ⏩专栏分类:数据结构与算法&#x1f440; &#x1f69a;代码仓库:笨小孩的代码库&#x1f440; ⏩社区&#xff1a;不是笨小孩&#x1f440; &#x1f339;欢迎大家三连关注&#xff0c;一起学习&#xff0c;一起进步&#…

MySQL高级篇第6章(索引的数据结构)

文章目录 1、为什么使用索引2、索引及其优缺点3、InnoDB中索引的推演4、MyISAM中的索引方案5、索引的代价6、MySQL数据结构选择的合理性 1、为什么使用索引 假如给数据使用 二叉树 这样的数据结构进行存储&#xff0c;如下图所示 2、索引及其优缺点 3、InnoDB中索引的推演 4、M…

Spring中声明式事务

声明式事务&#xff1a;基于Spring AOP&#xff0c;通过注解或XML配置实现&#xff0c;有助于用户将操作与事务规则进行解耦。其本质是对方法前后进行拦截&#xff0c;然后在目标方法开始之前创建或者加入一个事务&#xff0c;在执行完目标方法之后根据执行情况提交或者回滚事务…

前端小练-产品宣传页面

文章目录 前言页面结构固定钉头部轮播JS特效 完整代码总结 前言 经过一个月的爆肝&#xff0c;花费了一个月&#xff08;期间还花费了将近半个月的时间打比赛&#xff0c;还要备研&#xff09;算是把数二高数的内容强化了一遍&#xff0c;接下来刷熟练度即可&#xff0c;可惜的…

VirtualBox Ubuntu无法安装增强功能以及无法复制粘贴踩坑记录

在VirtualBox安装增强功能想要和主机双向复制粘贴&#xff0c;中间查了很多资料&#xff0c;终于是弄好了。记录一下过程&#xff0c;可能对后来人也有帮助&#xff0c;我把我参考的几篇主要的博客都贴上来了&#xff0c;如果觉得我哪里讲得不清楚的&#xff0c;可以去对应的博…

前端生成图片验证码怎么做?

##题记&#xff1a;我们实现一个功能首先想一下我们需要做哪些工作&#xff0c;比如我们需要生成一个随机的图片验证码&#xff0c;我们需要一个就是点击事件获取验证码&#xff0c;通过接口我们去获取图片路径进行渲染就行&#xff0c;这里边还要牵扯一件事情就是获取一个随机…

uniapp开发小程序-实现中间凸起的 tabbar

一、效果展示&#xff1a; 二、代码实现&#xff1a; 1.首先在pages.json文件中进行tabbar的样式和列表配置&#xff0c;代码如下&#xff1a; {"pages": [ //pages数组中第一项表示应用启动页&#xff0c;参考&#xff1a;https://uniapp.dcloud.io/collocation/p…

Stable Diffusion AI绘画学习指南【本地环境搭建win+mac】

一、硬件配配置要求 系统&#xff1a;windows 10 / Mac os 硬盘&#xff1a;C 盘预留 15GB 以上&#xff0c;其他盘 50GB 以上,Stable Ddiffusion的很多大模型都是以 GB 起步。 显卡&#xff1a;4GB 以上&#xff0c;建议 8GB, 效率高&#xff0c;能玩大尺寸的图 CPU&…

Socks IP轮换:为什么是数据挖掘和Web爬取的最佳选择?

在数据挖掘和Web爬取的过程中&#xff0c;IP轮换是一个非常重要的概念。数据挖掘和Web爬取需要从多个网站或来源获取数据&#xff0c;而这些网站通常会对来自同一IP地址的请求进行限制或封锁。为了避免这些问题&#xff0c;数据挖掘和Web爬取过程中需要使用Socks IP轮换技术。在…

Redis秒杀:一人一单问题及初步解决

优惠券秒杀一人一单 前言一、需求以及之前存在的问题二、增加一人一单逻辑1.初步代码2.封装一人一单逻辑3.控制锁的粒度 三、事务控制问题四、总结 前言 跟随黑马虎哥学习redis&#xff1a; 这是我认为b站上最好的redis教程&#xff0c;各方面讲解透彻&#xff0c;知识点覆盖…

Mr. Cappuccino的第53杯咖啡——Mybatis源码分析

Mybatis源码分析 Mybatis源码分析入口1. 读取配置文件总结 2. 解析配置文件核心代码&#xff08;一&#xff09;核心代码&#xff08;二&#xff09;分析parse()方法分析build()方法 总结 3. 获取SqlSession总结 4. 获取mapper代理对象总结 5. 使用mapper代理对象执行Sql语句二…

基于SpringBoot+Vue的会员制医疗预约服务管理信息系统设计与实现(源码+LW+部署文档等)

博主介绍&#xff1a; 大家好&#xff0c;我是一名在Java圈混迹十余年的程序员&#xff0c;精通Java编程语言&#xff0c;同时也熟练掌握微信小程序、Python和Android等技术&#xff0c;能够为大家提供全方位的技术支持和交流。 我擅长在JavaWeb、SSH、SSM、SpringBoot等框架…

Android Studio log的快捷键和使用

输入logi&#xff0c;然后按下Tab键&#xff0c;会自动补全一条info级别的打印日志。输入logw&#xff0c;按下Tab键&#xff0c;会自动补全一条warn级别的打印日志&#xff0c;以此类推。 如下图所示&#xff1a;

C#项目发布

C#项目发布 vs code 打包iis 配置 vs code 打包 iis 配置

VMware Linux Centos 配置网络并设置为静态ip

在root用户下进行以下操作 1. 查看子网ip和网关 &#xff08;1&#xff09;进入虚拟网络编辑器 &#xff08;2&#xff09;进入NAT设置 &#xff08;3&#xff09;记录子网IP和子网掩码 2. 修改网络配置文件 &#xff08;1&#xff09;cd到网络配置文件路径下 [rootlo…

【视觉SLAM入门】5.1. 特征提取和匹配--FAST,ORB(关键点描述子),2D-2D对极几何,本质矩阵,单应矩阵,三角测量,三角化矛盾

"不言而善应" 0. 基础知识1. 特征提取和匹配1.1 FAST关键点1.2 ORB的关键点--改进FAST1.3 ORB的描述子--BRIEF1.4 总结 2. 对极几何&#xff0c;对极约束2.1 本质矩阵(对极约束)2.1.1 求解本质矩阵2.1.2 恢复相机运动 R &#xff0c; t R&#xff0c;t R&#xff0c;…