Qt moc系统的黑魔法?

news2024/10/6 14:07:58

Qt的元对象系统(Meta-Object System)是Qt框架的核心功能之一,为C++语言增加了一些动态特性,借助元对象系统Qt可以实现以下功能

  • 信号与槽机制(Signals and Slots)
  • 运行时类型信息(Run-Time Type Information, RTTI)
  • 属性系统(Property System)
  • 动态对象创建(Dynamic Object Creation)
  • 动态方法调用(Dynamic Method Invocation)
  • 安全的类型转换(Dynamic Cast)

元对象系统的实现需要借助moc (Meta-Object_Compiler)工具, moc工具扫描包含Q_OBJECT宏的头文件,为为我们自动生成了元对象系统的实现代码,我们以一个简单的MainWindow例子来探究一下moc系统有什么黑魔法,示例完整代码放在文章最后。

QObject派生类的元信息编码和存储

元(Meta)在C++中是和编译期相关的概念,元对象用来存储一个类在编译器就确定的类型相关信息, 即每一个类的类型和方法信息都是编译期都是已经确定了的,在一般自己实现C++反射系统的方案中,存储类的元信息,无不例外的都是借助一个静态成员变量,由于静态变量是存储在静态存储区,生命周期伴随整个程序,并且其初始化是在程序加载后,main入口函数之前,所以用一个静态类来存储元信息便很自然了,如下,Qt中就是给每一个定义Q_OBJECT宏的类定义了一个static QMetaObject成员对象。

staticMetaObject这个静态成员的定义就在moc工具自动生成的moc_mainwindow.cpp文件之中,其中主要包含类的派生信息和信号与槽函数信息。

qt_meta_stringdata_MainWindow和qt_meta_data_MainWindow的定义在下面两张截图之中,我们先来看一下qt_meta_stringdata_MainWindow,这个结构里面将信号和槽函数的信息编码存储到了一个字符串之中,QT_MOC_LITERAL(0, 0, 10)中,第一个参数为编号,第二个为在下面字符串中的起始位置,第二个参数为函数名称的长度。

qt_meta_data_MainWindow这个结构指定了每个信号和槽函数在上面的“字符串”中的位置以及函数的返回值,参数类型信息,这两个结构就编码存储了信号槽函数的所有信息了。

槽函数的调用

这里先讨论一个问题,如果知道一个函数的字符串名称,怎么调用这个函数? 注意,这里的问题是,你现在只知道一个字符串, 虽然你知道它是那个函数,但是你怎么来调用?

std::string func_name = "my_slot";
//how to call my_slot() ?
func_name()?  // this is error~

//the possiable way
if(func_name == "my_slot"){
    my_slot();
} else if( func_name = "my_slot1"){
    my_slot1();
}

在老版本的Qt中,connect链接信号和槽的时候,也是直接连接的函数字符串名称,通过字符串来调用函数,就不得不借用我们moc文件里面生成的qt_static_metacall了,这里的实现也是很明了,首先找到函数的索引,然后通过switch case进行函数调用。

void MainWindow::qt_static_metacall(QObject *_o, QMetaObject::Call _c, int _id, void **_a)
{
    if (_c == QMetaObject::InvokeMetaMethod) {
        auto *_t = static_cast<MainWindow *>(_o);
        Q_UNUSED(_t)
        switch (_id) {
        case 0: _t->my_signal(); break;
        case 1: _t->my_signal_param((*reinterpret_cast< int(*)>(_a[1])),(*reinterpret_cast< double(*)>(_a[2]))); break;
        case 2: _t->on_actionExit_triggered(); break;
        case 3: _t->my_slot(); break;
        case 4: _t->my_slot_param((*reinterpret_cast< int(*)>(_a[1]))); break;
        default: ;
        }
    } else if (_c == QMetaObject::IndexOfMethod) {
        int *result = reinterpret_cast<int *>(_a[0]);
        {
            using _t = void (MainWindow::*)();
            if (*reinterpret_cast<_t *>(_a[1]) == static_cast<_t>(&MainWindow::my_signal)) {
                *result = 0;
                return;
            }
        }
        {
            using _t = void (MainWindow::*)(int , double );
            if (*reinterpret_cast<_t *>(_a[1]) == static_cast<_t>(&MainWindow::my_signal_param)) {
                *result = 1;
                return;
            }
        }
    }
}

信号函数的实现

我们在定义信号的时候,只给出了一个信号函数的申明,信号函数的实现也是moc自动生成的

// SIGNAL 0
void MainWindow::my_signal()
{
    QMetaObject::activate(this, &staticMetaObject, 0, nullptr);
}

// SIGNAL 1
void MainWindow::my_signal_param(int _t1, double _t2)
{
    void *_a[] = { nullptr, const_cast<void*>(reinterpret_cast<const void*>(std::addressof(_t1))), const_cast<void*>(reinterpret_cast<const void*>(std::addressof(_t2))) };
    QMetaObject::activate(this, &staticMetaObject, 1, _a);
}

总结:

我们可以发现,QT的元对象系统并没有什么神奇之处,主要干了下面几件事情

  • 通过一个静态成员QMetaObject存储类的元信息
  • 通过一个字符串编码存储了信号和槽函数信息
  • 通过信号和槽函数的索引,在switch case中调用槽函数
  • 自动生成信号函数的实现代码

mainwindow示例:

#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include <QMainWindow>

namespace Ui {
class MainWindow;
}

class MainWindow : public QMainWindow
{
    Q_OBJECT

public:
    explicit MainWindow(QWidget *parent = 0);
    explicit MainWindow(QWidget* parent, int id);
    ~MainWindow();
    void my_general_fun();

private slots:
    void on_actionExit_triggered();
    void my_slot();
    void my_slot_param(int iiii);

signals:
    void my_signal();
    void my_signal_param(int jjjj, double kkkk);

private:
    Ui::MainWindow *ui;
};

#endif // MAINWINDOW_H

moc生成的实现代码:

/****************************************************************************
** Meta object code from reading C++ file 'mainwindow.h'
**
** Created by: The Qt Meta Object Compiler version 67 (Qt 5.15.2)
**
** WARNING! All changes made in this file will be lost!
*****************************************************************************/

#include <memory>
#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.15.2. 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[10];
char stringdata0[99];
};
#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"
QT_MOC_LITERAL(1, 11, 9), // "my_signal"
QT_MOC_LITERAL(2, 21, 0), // ""
QT_MOC_LITERAL(3, 22, 15), // "my_signal_param"
QT_MOC_LITERAL(4, 38, 4), // "jjjj"
QT_MOC_LITERAL(5, 43, 4), // "kkkk"
QT_MOC_LITERAL(6, 48, 23), // "on_actionExit_triggered"
QT_MOC_LITERAL(7, 72, 7), // "my_slot"
QT_MOC_LITERAL(8, 80, 13), // "my_slot_param"
QT_MOC_LITERAL(9, 94, 4) // "iiii"

},
"MainWindow\0my_signal\0\0my_signal_param\0"
"jjjj\0kkkk\0on_actionExit_triggered\0"
"my_slot\0my_slot_param\0iiii"
};
#undef QT_MOC_LITERAL

static const uint qt_meta_data_MainWindow[] = {
// content:
8,       // revision
0,       // classname
0,    0, // classinfo
5,   14, // methods
0,    0, // properties
0,    0, // enums/sets
0,    0, // constructors
0,       // flags
2,       // signalCount

// signals: name, argc, parameters, tag, flags
1,    0,   39,    2, 0x06 /* Public */,
3,    2,   40,    2, 0x06 /* Public */,

// slots: name, argc, parameters, tag, flags
6,    0,   45,    2, 0x08 /* Private */,
7,    0,   46,    2, 0x08 /* Private */,
8,    1,   47,    2, 0x08 /* Private */,

// signals: parameters
QMetaType::Void,
QMetaType::Void, QMetaType::Int, QMetaType::Double,    4,    5,

// slots: parameters
QMetaType::Void,
QMetaType::Void,
QMetaType::Void, QMetaType::Int,    9,

0        // eod
};

void MainWindow::qt_static_metacall(QObject *_o, QMetaObject::Call _c, int _id, void **_a)
{
    if (_c == QMetaObject::InvokeMetaMethod) {
        auto *_t = static_cast<MainWindow *>(_o);
        Q_UNUSED(_t)
        switch (_id) {
        case 0: _t->my_signal(); break;
        case 1: _t->my_signal_param((*reinterpret_cast< int(*)>(_a[1])),(*reinterpret_cast< double(*)>(_a[2]))); break;
        case 2: _t->on_actionExit_triggered(); break;
        case 3: _t->my_slot(); break;
        case 4: _t->my_slot_param((*reinterpret_cast< int(*)>(_a[1]))); break;
        default: ;
        }
    } else if (_c == QMetaObject::IndexOfMethod) {
        int *result = reinterpret_cast<int *>(_a[0]);
        {
            using _t = void (MainWindow::*)();
            if (*reinterpret_cast<_t *>(_a[1]) == static_cast<_t>(&MainWindow::my_signal)) {
                *result = 0;
                return;
            }
        }
        {
            using _t = void (MainWindow::*)(int , double );
            if (*reinterpret_cast<_t *>(_a[1]) == static_cast<_t>(&MainWindow::my_signal_param)) {
                *result = 1;
                return;
            }
        }
    }
}

QT_INIT_METAOBJECT const QMetaObject MainWindow::staticMetaObject = { {
    QMetaObject::SuperData::link<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);
    if (_id < 0)
        return _id;
    if (_c == QMetaObject::InvokeMetaMethod) {
        if (_id < 5)
            qt_static_metacall(this, _c, _id, _a);
        _id -= 5;
    } else if (_c == QMetaObject::RegisterMethodArgumentMetaType) {
        if (_id < 5)
            *reinterpret_cast<int*>(_a[0]) = -1;
        _id -= 5;
    }
    return _id;
}

// SIGNAL 0
void MainWindow::my_signal()
{
    QMetaObject::activate(this, &staticMetaObject, 0, nullptr);
}

// SIGNAL 1
void MainWindow::my_signal_param(int _t1, double _t2)
{
    void *_a[] = { nullptr, const_cast<void*>(reinterpret_cast<const void*>(std::addressof(_t1))), const_cast<void*>(reinterpret_cast<const void*>(std::addressof(_t2))) };
    QMetaObject::activate(this, &staticMetaObject, 1, _a);
}
QT_WARNING_POP
QT_END_MOC_NAMESPACE

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

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

相关文章

激光雷达和相机外参标定

1、外参标定标定什么&#xff1f; 标定激光雷达和相机之间的相对位置&#xff0c;变换矩阵T 2、原理 通过激光扫描出来的轮廓和相机识别出来的栅格的轮廓进行PNP匹配。 3、论文

IDEA的妙用

IDEA 安装破解 复制JetbrainsIdesCrack-4.2.jar到安装目录下 修改安装目录下的bin目录的idea64.exe.vmoptions&#xff1a; 最后一行添加&#xff1a;-javaagent:E:\develop\JetBrains\IntelliJ IDEA 2018.3.5\bin\JetbrainsIdesCrack-4.2.jar(注意&#xff1a;使用自己的路…

大模型时代,掌握Event Stream技术提升Web响应速度

大模型时代,每天搜索都可能会用到一种或多种大模型,在大文本输出的时候,页面是一字一字,一段一段的慢慢输出出来的,这背后是如何实现的呢?我们以KIMI为例 先抓个请求 我们发现界面展示是一句话,但是接口返回的时候是一个字一个字的。 普通请求 多了Event Stream的处理 …

OS复习笔记ch5-5

引言 上文我们了解了一些关于信号量机制的一些经典应用&#xff0c;知识点比较繁杂&#xff0c;可能会有点啰嗦&#xff0c;但是对初学者而言还是越详细越好。接下来我们介绍一下管程。 管程 管程是程序设计语言结构&#xff0c;可提供易于控制的同步机制&#xff0c;我们熟…

基于 LlaMA 3 + LangGraph 在windows本地部署大模型 (十一)

基于 LlaMA 3 LangGraph 在windows本地部署大模型 &#xff08;十一&#xff09; LlaMA 3 系列博客 基于 LlaMA 3 LangGraph 在windows本地部署大模型 &#xff08;一&#xff09; 基于 LlaMA 3 LangGraph 在windows本地部署大模型 &#xff08;二&#xff09; 基于 Lla…

【网络安全】2030年十大新兴网络安全威胁

欧盟网络安全局&#xff08;ENISA&#xff09;已发布了一份全面的清单&#xff0c;列出了预计到2030年将影响数字领域的十大新兴网络安全威胁。 该预测是为期八个月的广泛研究的成果&#xff0c;融合了ENISA前瞻专家小组、CSIRTs网络以及欧盟CyCLONe专家的见解。 这项研究突显…

Wpf 使用 Prism 实战开发Day23

自定义对话框服务 当原有对话框不能满足需求的时候&#xff0c;可以通过自定义对话框来实现特殊的需求 一.自定义对话框主机服务步骤&#xff1a; 1.建立一个IDialogHostService 接口类&#xff0c;继承自 IDialogService 对话框服务类。并且自定义基类的服务方法。 public …

真实案例分享,终端pc直接telnet不到出口路由器。

1、背景信息 我终端pc的网卡地址获取的网关是在核心交换机上&#xff0c;在核心交换机上telnet出口路由器可以实现。 所有终端网段都不能telnet出口路由器&#xff0c;客户希望能用最小的影响方式进行解决。 2、现有配置信息 终端的无线和有线分别在两个网段中&#xff0c;…

案例题(第一版)

案例题目 软件架构设计考点&#xff08;历年必考&#xff09; 软件架构设计通常在每年的第一题&#xff0c;该题必考 必备概念 必备概念即考试必须要默写出来的概念 概念描述软件架构风格是指描述特定软件系统组织方式和惯用模式。组织方式描述了系统的组成构件和这些构件的组…

从“图形可视化”到“图生代码”,低代码平台的新挑战

前言&#xff1a; 低代码平台最大的一个特点就是可视化&#xff0c;将代码采用可视化的方式展示管理。一时间拥有图形化界面的各类系统都挂上了低代码的标签。但更多的代码从业者在使用中却发现&#xff0c;在众多的低代码平台中都是“别人家的代码”其可视化主要是别人家的代…

正点原子[第二期]Linux之ARM(MX6U)裸机篇学习笔记-23.1,2 讲 I2C驱动

前言&#xff1a; 本文是根据哔哩哔哩网站上“正点原子[第二期]Linux之ARM&#xff08;MX6U&#xff09;裸机篇”视频的学习笔记&#xff0c;在这里会记录下正点原子 I.MX6ULL 开发板的配套视频教程所作的实验和学习笔记内容。本文大量引用了正点原子教学视频和链接中的内容。…

SpringBoot+layuimini实现角色权限菜单增删改查(layui扩展组件 dtree)

角色菜单 相关组件方法效果图MySQL代码实现资源菜单树组件实现权限树方法js这里我先主要实现权限树的整体实现方法&#xff0c;如果是直接查看使用的话可以只看这里&#xff01; 后端代码Controlle层代码Service代码及实现类代码Service代码ServiceImpl代码 resourceMapper 代码…

指数分布的理解,推导与应用

指数分布的定义 在浙大版的教材中&#xff0c;指数分布的定义如下&#xff1a; 若连续型的随机变量 X X X的概率密度为&#xff1a; f ( x ) { 1 θ e − x θ , x>0 0 , 其他 f(x) \begin{cases} \frac{1}{\theta} e^{-\frac{x}{\theta}}, & \text{x>0}\\ 0, &a…

Jenkins 构建 Web 项目:构建服务器和部署服务器分离的情况

构建命令 #!/bin/bash node -v pnpm -v pnpm install pnpm build:prod # 将dist打包成dist.zip zip -r dist.zip dist

BLE学习笔记(0.0) —— 基础概念(0)

前言 &#xff08;1&#xff09;本章节主要是对BLE技术进行简单的介绍&#xff0c;熟悉蓝牙技术的发展过程&#xff0c;了解相关术语方便后续的学习。 &#xff08;2&#xff09;为了防止单篇博客太长以至于看不下去&#xff0c;因此我基础概念章节分为两篇来写。 &#xff08;…

创新指南|利用电商产品视频进行渠道营销的最佳策略,不断提升销售额

无论企业的利基市场如何&#xff0c;电商产品视频都已被证明是非常可靠的资产&#xff0c;可以让目标受众了解您所提供的产品——关键功能、展示重要的差异化优势甚至改变大多数营销活动的游戏规则。阅读本文&#xff0c;全面了解电商产品视频如何融入营销推广&#xff0c;以最…

IDEA 自定义注解(类注释、方法注释)

一、生成类注释 1、打开设置位置 打开File —> Settings —> Editor —> File and Code Templates —> Files —> Class 2、将自定义的类注解规则&#xff0c;复制到Class中。 /** * * 功能: * * 作者: 暗自着迷 * * 日期: ${YEAR}-${MONTH}-${DAY} ${HOU…

AI图片过拟合如何处理?答案就在其中!

遇到难题不要怕&#xff01;厚德提问大佬答&#xff01; 厚德提问大佬答8 你是否对AI绘画感兴趣却无从下手&#xff1f;是否有很多疑问却苦于没有大佬解答带你飞&#xff1f;从此刻开始这些问题都将迎刃而解&#xff01;你感兴趣的话题&#xff0c;厚德云替你问&#xff0c;你解…

头歌openGauss-存储过程第2关:修改存储过程

任务描述 本关任务&#xff1a; 修改存储过程pro0101&#xff0c;并调用&#xff1b; --修改sel_course表中成绩<60的记录为成绩10&#xff0c;然后将计算机学院所有学生的选课成绩输出&#xff1b; --a、需要先删除存储过程pro0101&#xff1b; drop procedure if exists p…

JAVA开发 基于最长公共子序列来计算两个字符串之间的重复率

计算两个字符串之间的重复率 最长公共子序列实现代码 最长公共子序列 基于最长公共子序列&#xff08;Longest Common Subsequence, LCS&#xff09;的重复率的中心逻辑是首先找到两个或多个序列中同时出现的、不一定连续但保持相对顺序的最长子序列&#xff0c;然后计算这个最…