QT 5 中元对象系统的改变

2023-11-06

Qt 5 的元对象系统作出了一定的改变,既有底层变化,又有 API 的变化。其中有些修改与 Qt 4 不是源代码兼容的。本文将介绍这些改变,以及如何修改现有代码,使其能够使用 Qt 5 进行编译。同时,我们也将阐述下新增加的一些 API,使 QMetaMethod更方便使用。

移除 Qt 4 元对象系统的支持

元对象数据(例如,moc 生成的)有一个版本号,用于描述元对象的内容(格式/布局)和特性。当我们向新的主要版本的 Qt 的元对象增加新特性时(例如让一个类的构造函数支持反射),我们必须更新这个元数据版本号。Qt 4 最新的元对象版本是 6。

为了保持向后兼容,新的小版本的 Qt 更新必须支持早期版本的元对象。这是由 Qt 内部在恰当位置检查元对象版本来实现的。如果元对象是旧的,那么就要切换到早期版本的代码。

因为 Qt 5 已经不与 Qt 4 二进制兼容,因此,我们选择不支持旧的元对象系统了。也就是说,前面我们说的保持旧有代码在 Qt 5 中已经没有了,因为 Qt 不再使用 Qt 4 所使用的那个版本的元对象了。Qt 5 的元对象版本号是 7。

Qt 4 的 moc 输出代码也不能使用 Qt 5 编译。如果你的代码中有硬编码的 moc 输出代码(希望没有,不过 Qt 自己倒是使用了一些,用于实现“特殊的”元对象),你必须自己修改这些代码。

Qt 的很多运行时的元对象构建器(QMetaObjectBuilder、QtDBus、ActiveQt 等)也必须修改代码,以便兼容第 7 版元对象。不过,因为这些代码通常是内部使用的,因此不会影响到使用这些库的程序。(希望没人自己编写了一个元对象构建器…)

函数不再使用字符串识别

在 Qt 5 之前的版本,元对象包含了完整的、一般化的类信号、槽以及Q_INVOKABLE 函数,将其作为以 0 为终止符的字符串进行存储。这在对于完全基于字符串的QObject::connect() 是必须的(使用SIGNAL()和SLOT()宏)。但是现在QObject::connect()是基于模板的,将函数的完全签名作为字符串存储就不那么明智了。(Qt 4 内部处理这些字符串供 QObject::connectNotify() 使用,我们会在后文详细说明。)

函数反射的另外一个通用使用是动态绑定(QML、QScript)。在这种情况下,一个字符串的签名就不那么合适。如果能够直接访问函数名和参数类型,由于减少了运行时解析字符串,会更为简单和有效。

注意,Qt 5 中,QMetaMethod有了新的函数:name()、parameterCount()和parameterType(int index)。完整的函数签名已经不在元对象数据中存储了(只有函数名),因为正确的签名可以从上面所说的各个函数返回的信息拼接出来。

不幸的是,现在的QMetaMethod::signature()函数返回一个const char *,这将会引起内存泄露。我们不能简单地将返回类型修改成QByteArray,因为类似如下代码的隐式类型转换将会使程序崩溃,但是却不会有任何警告:

// 如果 signature() 返回 QByteArray,在下面语句执行过后,返回值将会超出作用域(也就会被销毁)
const char *sig = someMethod.signature();
// 试试看使用 sig 做些处理吧!

为了解决这个问题,我们在 Qt 5 引入一个新的函数,QMetaMethod::methodSignature()。旧的QMetaMethod::signature()函数不再使用。这样,如果你不修改函数名,就会引发一个编译器错误。现有代码应该修改成QMetaMethod::methodSignature()。那些新的函数,例如QMetaMethod::name(),相比完整函数签名,在某些情况下也会更好用,比如在调试的时候。

connectNotify()和disconnectNotify()

QObject::connectNotify()和disconnectNotify()是两个很少被重写的虚函数。这两个函数适用在任何 slot 连接或者断开你的类的 signal 的时候。一个潜在的使用情景是,实现隐藏代理的时候,将一个内部对象(后端)连接到一个 public 的 signal 上面。qtsystems 模块就大量使用了这一特性。

在 Qt 5 之前,connectNotify()为了配合基于字符串的QObject::connect()函数,其参数是函数签名的const char *,用于识别出连接到的是哪一个 signal。在基于模板的QObject::connect()以及自定义的连接,例如 QML 或 QtScript 中,让 Qt 准备一个以字符串形式表达的 signal,只需要调用一个通常没有重写的虚函数。

另外,基于char *的connectNotify()非常容易引发一些错误。即便你能够拼写正确每一个函数签名,你也有可能陷入下面的陷阱(这样的问题代码甚至出现在 Qt 内部):

void MyClass::connectNotify(const char *signal)
{
    if (signal == SIGNAL(mySignal())) {
        // 这永远不会执行,因为比较的是指针而不是字符串内容

文档说,你应该将 signal 参数包装成QLatin1String,但经常会忘记这么做。Qt 5 的解决方案则不会有这种问题。

本文福利,费领取Qt开发学习资料包、技术视频,内容包括(C++语言基础,Qt编程入门,QT信号与槽机制,QT界面开发-图像绘制,QT网络,QT数据库编程,QT项目实战,QT嵌入式开发,Quick模块等等)↓↓↓↓↓↓见下面↓↓文章底部点击费领取↓↓

在 Qt 5 中,QObject::connectNotify()和disconnectNotify()接受一个QMetaMethod,而不是const char *。QMetaMethod仅仅包含两个数据:QMetaObject指针和索引。这就不会有任何问题,偏向某种连接方式。

这种变化同时允许我们将对connectNotify()和disconnectNotify()的调用转移到内部实现的基于索引的connect()和disconnect()函数(QMetaObject::connect(),该函数在 Qt 内部有好几处使用)。在实际应用中,这意味着你重新实现的connectNotify()函数将会按照你期望的方式调用,即使QObject::connect()没有显式调用(例如connectSlotsByName())。最后,我们可以丢弃掉某些 Qt 中现存的代码,例如那些手工调用的connectNotify(const char *),转而使用基于索引的connect()。

已经重写了connectNotify()和disconnectNotify()的现有代码需要迁移到新的 API。有两个函数会让这个工作变得简单:QMetaMethod::fromSignal()和QObject::isSignalConnected()。

QMetaMethod::fromSignal()

新的 static 函数QMetaMethod::fromSignal()将一个成员函数(一个 signal)作为参数,返回对应的QMetaMethod。它可以很方便地用于新的connectNotify():

void MyClass::connectNotify(const QMetaMethod &signal)
{
    if (signal == QMetaMethod::fromSignal(&MyClass::mySignal)) {
        // 连接到 mySignal ...

为了避免每次调用connectNotify()的时候都要重新查找一个 signal,应该将QMetaMethod::fromSignal()的返回值作为一个 static 变量存储起来。

另外一个fromSignal()容易混淆的地方是在执行 signal 的 queued 调用时(QMetaObject::invokeMethod()也可以这么做,但它是基于字符串的):

QMetaMethod::fromSignal(&MyClass::mySignal)
    .invoke(myObject, Qt::QueuedConnection /* 其他参数 ... */);

QObject::isSignalConnected()

新的函数QObject::isSignalConnected()用于检查一个 signal 是否有 slot 连接。这是一个基于QMetaMethod的,用于替代QObject::receivers()的函数。

void MyClass::disconnectNotify(const QMetaMethod &signal)
{
    if (signal == QMetaMethod::fromSignal(&MyClass::mySignal)) {
        // 有 slot 从 mySignal 断开连接
        if (!isSignalConnected(signal)) {
            // 该 signal 没有连接,我们可以释放其资源 ...

另外,你可以使用这个函数用于避免一次 emit 许多 signal,例如,如果 emit 需要非常复杂的计算,那么这将严重影响到系统的性能。

QTBUG-4844: QObject::disconnectNotify() is not called when receiver is destroyed

这个 bug 现在依然存在。它是说,当接收者销毁的时候,disconnectNotify()没有调用。这里我们需要看看如何避免这一问题。

为了实现所期望的行为,连接需要记住其 signal id。因为当 receiver 销毁时,我们无法再获取 signal id(如果显式调用disconnect()函数则不会有这个问题)。这将给所有的连接都增加一个 int。如果你有更好的解决方案,不要忘记将修改意见提交给 Qt 5。

函数返回类型

在 Qt 5 之前的版本中,我们必须使用QMetaMethod::typeName()判断一个函数的返回值类型,其格式是const char *。如果返回值是 void,typeName()返回空字符串,而不是像QMetaType::typeName() 那样返回字符串“void”。这种不兼容但是很多奇怪的地方,例如:

if (!method.typeName() || !*method.typeName() || !strcmp(method.typeName(), "void")){
    // 返回值是 void ...

我们必须这么判断。

在 Qt 5 中,我们可以使用新的函数QMetaMethod::returnType(),其返回值是一个 meta-type id:

if (method.returnType() == QMetaType::Void) {
    // 返回值是 void ...

在 Qt 4,你不能使用QMetaType::Void区分 void 和未注册类型(它们都是整型 0)。Qt 5 中的 QMetaType::Void就是 void,新的QMetaType::UnknownType则用于指定一个未注册到 Qt 类型系统中的类型。

(这么做的后果是,如果你现在有代码是将类型 id 与QMetaType::Void(或者整型 0)进行比较,那么我的建议是,在切换到 Qt 5 的时候,再三检查你的逻辑:是检查是不是 void,未知类型,还是两个都要?)

为了与QMetaType保持一致,当返回值类型是 void 的时候,Qt 5 的QMetaMethod::typeName()返回字符串“void”。现有代码,用typeName()返回空字符串代表 void,必须要进行修改(将returnType()与QMetaType::Void进行比较)。

函数参数

类似QMetaMethod::returnType(),新的QMetaMethod::parameterType()函数返回参数类型的 meta-type id 。QMetaMethod::parameterCount()返回参数个数。使用这两个函数就可以替代旧的QMetaMethod::parameterTypes(),后者以字符串的形式返回所有参数类型(名字)。

如果在元对象定义时,类型已知(内建类型),类型 id 会直接嵌入到元对象数据中。对于另外的类型,获得 id 则会是基于字符串的查找,这也并不会比之前慢(对于内建类型则会快很多)。

结论

Qt 5 的元对象系统(以及元类型系统)有了一些小的变化。函数现在有更恰当更易于维护的语义,而不是普通的 C 字符串。与 Qt 4 源代码兼容最大程度的保持着(如果没有错误,就不要修改——当然,还是会有些问题,我们需要在编译时修正)。现在,Qt 5 元对象系统几乎完成。许多 Qt 模块都使用到其中的一些新特性,从而获得更清晰的代码、更快速的运行效率。希望那些对于元对象系统持批评态度的人可以偃旗息鼓了。

本文福利,费领取Qt开发学习资料包、技术视频,内容包括(C++语言基础,Qt编程入门,QT信号与槽机制,QT界面开发-图像绘制,QT网络,QT数据库编程,QT项目实战,QT嵌入式开发,Quick模块等等)↓↓↓↓↓↓见下面↓↓文章底部点击费领取↓↓

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

QT 5 中元对象系统的改变 的相关文章

  • Poco c++Net:Http 从响应中获取标头

    我使用 POCO C Net 库进行 http 我想尝试制定持久缓存策略 首先 我认为我需要从缓存标头中获取过期时间 并与缓存值进行交叉检查 如果我错了 请告诉我 那么我如何从中提取缓存头httpResponse 我已经看到你可以用 Jav
  • 单元测试验证失败

    我正在运行我的单元测试PostMyModel路线 然而 在PostMyModel 我用的是线Validate
  • C++ 长 switch 语句还是用地图查找?

    在我的 C 应用程序中 我有一些值充当代表其他值的代码 为了翻译代码 我一直在争论使用 switch 语句还是 stl 映射 开关看起来像这样 int code int value switch code case 1 value 10 b
  • 解析 JWT 令牌以仅获取有效负载内容,无需 C# 或 Blazor 中的外部库

    我正在使用 Blazor 编写可以访问 JWT 的客户端应用程序 我想知道一种简单的方法来读取令牌有效负载内容而不添加额外的依赖项 因为我不需要其他信息 也不需要验证令牌 我认为解析有效负载内容应该足够简单 只需将其写入方法即可 JwtTo
  • CSharpRepl emacs 集成?

    我碰巧知道莫诺CSharpRepl http www mono project com CsharpRepl 是否有 emacs csharp 模式使用它在一个窗口中运行 REPL 并像 python 模式一样在另一个窗口中编译 运行 C
  • 从代码中,如何创建对存储在附加属性中的对象的属性的绑定?

    我们有一个继承的附加属性来存储一个对象 在可视化树的更下方 我们希望从代码绑定到该对象的属性 通常我们像这样构建绑定的路径部分 var someBinding new Binding Path new PropertyPath Attach
  • 如何制作可启动程序?

    所以 这个问题可能看起来很奇怪 但假设我编译了 int main void int x 3 int y 4 int z x y 是否可以让CPU这样运行 如何 例如 这允许我写入监视器吗 如果我没记错的话 内存中有些地方可以写入要显示的内容
  • 为什么'enable_if'不能用于禁用这里声明

    include
  • 使用查询表达式对 List 进行排序

    我在使用 Linq 订购这样的结构时遇到问题 public class Person public int ID get set public List
  • 使用 C# 和 wpf 创建类似 Dock 的应用程序

    我需要创建一个与我们购买笔记本电脑时获得的应用程序类似的应用程序 仅当鼠标指针到达窗口顶部时它才可见 那么我怎样才能使用 C 4 0 来做到这一点呢 http www notebookcheck net uploads pics win2
  • C#6 中的长字符串插值行

    我发现 虽然字符串插值在应用于现有代码库的字符串 Format 调用时非常好 但考虑到通常首选的列限制 字符串对于单行来说很快就会变得太长 特别是当被插值的表达式很复杂时 使用格式字符串 您将获得一个可以拆分为多行的变量列表 var str
  • 在 asp.net MVC 中使用活动目录进行身份验证

    我想使用活动目录对我的 asp net mvc 项目中的用户进行身份验证 在网上冲浪了几个小时后 我没有找到任何对我有用的东西 我已经看到了所有结果 但什么也没有 我尝试按照许多帖子的建议编辑我的 web config 如果有人可以帮助我提
  • 使用具有抗锯齿功能的 C# 更改抗锯齿图像的背景颜色

    我有一个图像需要更改背景颜色 例如 将下面示例图像的背景更改为蓝色 然而 图像是抗锯齿的 所以我不能简单地用不同的颜色替换背景颜色 我尝试过的一种方法是创建第二个图像 仅作为背景 并更改其颜色并将两个图像合并为一个图像 但是这不起作用 因为
  • 为什么 Cdecl 调用在“标准”P/Invoke 约定中经常不匹配?

    我正在开发一个相当大的代码库 其中 C 功能是从 C P Invoked 的 我们的代码库中有很多调用 例如 C extern C int stdcall InvokedFunction int 使用相应的 C DllImport CPlu
  • Project Euler #8,我不明白我哪里出了问题[关闭]

    Closed 这个问题是无法重现或由拼写错误引起 help closed questions 目前不接受答案 我正在做项目欧拉第八题 https projecteuler net problem 8 其中我得到了这个大得离谱的数字 7316
  • 英文日期差异

    接近重复 如何计算相对时间 https stackoverflow com questions 11 how do i calculate relative time 如何在 C 中计算某人的年龄 https stackoverflow c
  • Linux mremap 不释放旧映射?

    我需要一种方法将页面从一个虚拟地址范围复制到另一个虚拟地址范围 而无需实际复制数据 范围很大 延迟很重要 mremap 可以做到这一点 但问题是它也会删除旧的映射 由于我需要在多线程环境中执行此操作 因此我需要旧映射能够同时使用 因此稍后当
  • 为什么以下 C 程序会出现总线错误?

    我认为这是第一个失败的 strtok 调用 好久没写C了 有点不知所措 非常感谢 include
  • LINQ 中的“from..where”或“FirstOrDefault”

    传统上 当我尝试从数据库中获取用户的数据时 我使用了以下方法 在某种程度上 DbUsers curUser context DbUsers FirstOrDefault x gt x u LoginName id string name c
  • 来自 3rd 方库的链接器错误 LNK2019

    我正在将旧的 vc 6 0 应用程序移植到 vs2005 我收到以下链接器错误 我花了几天时间试图找到解决方案 错误LNK2019 无法解析的外部符号 imp 创建AwnService 52 在函数 public int thiscall

随机推荐

  • iOS 15 适配踩坑:NavigationBar、UITabBar失效问题

    苹果前两天推出了iOS 15 秋天都等不及 相关链接 ios 15 0 适配问题 NavigationBar和UITabBar失效问题 Xcode 13 beta版 iOS 15 beta 3的系统 除了客户提出的问题 自己还发现了两处UI
  • python常用的类间关系

    1 类之间的关系 1 1 定义 1 2 例子 2 定义可访问性 2 1 定义 2 2 例子 附录 1 类之间的关系 1 1 定义 简单的说 类和类之间的关系有三种 is a has a和use a关系 is a关系也叫继承或泛化 比如学生和
  • 《软件测试的艺术》第七章 可用性(或用户体验)测试

    软件测试的艺术 第七章 可用性 或用户体验 测试 7 0 前言 7 1 可用性测试基本要素 7 2 可用性测试流程 7 2 1 测试用户的选择 7 2 2 需要多少用户进行测试 7 2 3 数据采集方法 7 2 4 可用性调查问卷 7 2
  • 网页使用jssdk微信分享报错

    网页使用jssdk微信分享报错 显示找不到文件 jssdk php文件如下
  • 判断字符串是否以 endStr 为结尾

    String prototype endWith function endStr 判断字符串以 endStr 为结尾 let d this length endStr length return d gt 0 this lastIndexO
  • ubuntu16.04 从源码编译安装caffe(纯CPU版)

    需要做caffe在嵌入式的移植 决定先在X86上理清所有依赖包关系 再做交叉编译 由于目的是用在嵌入式 暂不支持GPU 1 boost 官网 http www boost org Caffe 中主要使用了Boost 的智能指针 新版v1 6
  • python中的字典(Dictionary)

    python中的字典 Dictionary 在Python中 字典 Dictionary 是一种键 值对的无序集合 用于存储和查找具有唯一键的元素 字典提供了一个高效的方式来根据键访问和操作值 特点 字典是无序的 其中的元素没有固定的顺序
  • 51单片机入门——单片机最小系统

    单片机最小系统 1 什么是最小系统 2 最小系统的三要素 2 1 电源 2 2 晶振 2 3 复位电路 2 3 1 外部RST引脚复位 2 3 2 软件复位 2 3 3 上电复位 掉电复位 2 3 4 看门狗复位 2 3 5 冷启动复位和热
  • 从小学开始学机器人编程教育的好处

    不过对于绝大多数孩子来说 情况也许并不是这样 他们学习机器人编程并非一定要成为程序员 更不一定要为将来创业做准备 但是他们同样能从编程学习中获益 获得多方面的思维训练 格物斯坦表示 通过学习编程 除了通常被提及的一些如促进学科知识学习 了解
  • C语言经典100例题(46)--宏#define命令练习(1)

    目录 题目 问题分析 代码 运行结果 题目 宏 define命令练习 1 问题分析 define是宏定义 程序在预处理阶段将用define定义的内容进行了 替换 因此在程序运行时 常量表中并没有用define定义的常量 系统不为它分配内存
  • Navicat 无法连接 MySQL 怎么办?

    本文背景 Navicat 是图形化操作 MySQL 的强大工具 但是当数据库的服务器没有开放 3306 端口给办公网络时 在办公网使用 navicat 连接数据库是连不上的 要操作数据库 只能先 ssh 登陆到数据库服务器 然后在黑屏敲命令
  • 修改网站在浏览器上方显示的logo

    1 准备好要显示的图片 通过百度 ico在线制作 转换成为ico的格式 放在对应的位置中 2 在html的head中添加 3 href是ico的位置 4 刷新页面 清除缓存即可
  • OPC通讯的安全防护

    http www dqjsw com cn dianqi OPC 111931 html OPC通讯的安全防护 OPC 用于过程控制的OLE 被广泛应用在控制系统中 用于提供不同供应商的设备和软件之间的互操作性 最新版本的OPC OPC U
  • 所有项目-环境-教程-链接地址汇总

    一 项目相关 1 项目清单 不定期更新 myDemo源码汇总 qq com 5 成品视频展示网站 https space bilibili com 397822494 因为有部分视频因为部分问题 无法上传 可直接添加作者索要演示视频 2 环
  • 一篇小孩子都能看懂的 ChatGPT 原理解析

    本文作者小宝 85 后程序员 现在蚂蚁金服从事后端架构 爱读书 爱编码 过去半年 随着 ChatGPT 的大火 大模型已经成为一种新的社交货币 现在见面都不是问 天气怎么样 而是你用 ChatGPT 了么 国内各大巨头也纷纷下场 制作自己的
  • Oulipo 【POJ - 3461】【双值哈希】

    题目链接 题意 给你两个字符串 前一个是小字符串 后一个是大的字符串 问你 大的字符串中有几组可以与小的字符串相等的子字符串 此题其实不用双值哈希好像也可以的 但为了确保A就敲了个双值哈希 我们想把字符串的形式用数的值来表示 那么 我们可以
  • (多核DSP快速入门)5.SYS/BIOS的使用实例分析

    原创文章 转载请注册来源http blog csdn net tostq 系列教程目录 http blog csdn net tostq article details 51245979 SYS BIOS是一个可扩展的实时内核 或者说是操作
  • 燕千云ChatGPT应用,用过的都说香

    本期受访人物 张礼军 甄知科技联合创始人 CTO 首席产品官 2022年底 基于人工智能技术驱动的自然语言工具横空出世 一经推出 ChatGPT迅速火遍全球 几乎各行各业都在探索ChatGPT具体业务场景的应用 希望通过ChatGPT来做业
  • 3ar用计算机弹奏,网课计算机网络期末考试参考答案查询

    但也有学者不支持 远程教育 和 远程学习 这两个专业术语可以互换的说法 因为这两个术语有它们各自的特点 5 一般情况下 远程学习被视为一种能力 远程学习中需要根据学习者原有的水平来确定教学内容 教学策略以及评价方式 6 而远程教育只是在远距
  • QT 5 中元对象系统的改变

    Qt 5 的元对象系统作出了一定的改变 既有底层变化 又有 API 的变化 其中有些修改与 Qt 4 不是源代码兼容的 本文将介绍这些改变 以及如何修改现有代码 使其能够使用 Qt 5 进行编译 同时 我们也将阐述下新增加的一些 API 使