【QT】理解QT的“元对象系统”

article/2025/9/8 2:21:58

目录

前置知识:

(1)C++运行时多态

(2)RTTI

QT的元对象系统

1.元对象系统基本内容

2.元对象代码

3.元对象系统其它特性


前置知识:

(1)C++运行时多态

C++的运行时多态是由虚函数和继承实现的。当一个基类中存在虚函数的时候,基类指针就可以指向任何派生类的对象。如果在基类中声明了虚函数,并在派生类中重写了这些虚函数,当基类指针或引用指向派生类对象并调用虚函数时,会根据对象的实际类型而不是指针类型来确定调用的函数。

例如,下面这段程序,main函数中,父类指针指向派生类对象。父类Parent中的come被声明为虚函数,在main函数中,虽然指向两个派生类对象的是父类指针,但是运行时还是调用派生类中的come()函数。

#include <iostream>using namespace std;class Parent
{
public:Parent() {cout << "我是你父母" << endl;}virtual ~Parent() {cout << "父类析构" << endl;}virtual void come(){cout << "父母来了" << endl;}
};class Son : public Parent
{
public:Son() {cout << "我是你儿子" << endl;}~Son() override {cout << "儿子析构" << endl;}void come(){cout << "儿子来了" << endl;}
};class Daughtor : public Parent
{
public:Daughtor() {cout << "我是你闺女" << endl;}~Daughtor() override {cout << "闺女析构" << endl;}void come(){cout << "闺女来了" << endl;}
};int main()
{Parent *child1 = new Son();Parent *child2 = new Daughtor();child1->come();child2->come();delete child1;delete child2;return 0;
}

输出:

但是如何判断基类指针到底指向的那个对象呢?这就用到了RTTI机制。

(2)RTTI

RTTI(Run-Time Type Identification)是C++中的一种机制,允许在运行时确定对象的类型。程序能够使用基类的指针或引用,来检查这些指针或引用所指的对象的实际派生类型。 

RTTI提供了两个非常有用的操作符:dynamic_cast和typeid。

(2.1)dynamic_cast

dynamic_cast(expression):dynamic_cast 主要用于在继承体系中进行安全的向下转换(基类指针或引用------->派生类指针或引用)。dynamic_cast 是一种安全的转换,有类型检查的功能,如果转换失败返回NULL。

因此,dynamic_cast可以用来判断父类对象是否存在某个派生类,如以下程序所示:Son继承了Parent,是Parent的派生类;daughtor没有继承Parent,不是Parent的派生类。使用dynamic_cast将p转为Daughtor类型时返回NULL。

#include <iostream>using namespace std;class Parent
{
public:Parent() {cout << "我是你父母" << endl;}virtual ~Parent() {cout << "父类析构" << endl;}virtual void come(){cout << "父母来了" << endl;}
};class Son : public Parent
{
public:Son() {cout << "我是你儿子" << endl;}~Son() override {cout << "儿子析构" << endl;}void come(){cout << "儿子来了" << endl;}
};class Daughtor 
{
public:Daughtor() {cout << "我是你闺女" << endl;}~Daughtor()  {cout << "闺女析构" << endl;}void come(){cout << "闺女来了" << endl;}
};int main()
{Parent *p = new Son();Son *son = dynamic_cast< Son *> (p);if(son != nullptr){cout << "p有son子类" << endl;}else{cout << "p没有son子类" << endl;}Daughtor *daughtor = dynamic_cast< Daughtor *> (p);if(daughtor != nullptr){cout << "p有daughtor子类" << endl;}else{cout << "p没有daughtor子类" << endl;}delete p;return 0;
}

输出:

(2.2)typeid

typeid能够返回类型的名字,在前面的代码里增加下面的代码,也可以判断指针和所指向对象的类型。

    if(typeid(p).name() ==  typeid(Parent*).name()){cout << "p指针是Parent类型" << endl; }else if(typeid(p).name() ==  typeid(Son*).name()){cout << "p指针是Son类型" << endl; } if(typeid(*p).name() ==  typeid(Parent).name()){cout << "p指针指向的是Parent类型" << endl; }else if(typeid(*p).name() ==  typeid(Son).name()){cout << "p指针指向的是Son类型" << endl; } 

完整代码:

#include <iostream>using namespace std;class Parent
{
public:Parent() {cout << "我是你父母" << endl;}virtual ~Parent() {cout << "父类析构" << endl;}virtual void come(){cout << "父母来了" << endl;}
};class Son : public Parent
{
public:Son() {cout << "我是你儿子" << endl;}~Son() override {cout << "儿子析构" << endl;}void come(){cout << "儿子来了" << endl;}
};class Daughtor 
{
public:Daughtor() {cout << "我是你闺女" << endl;}~Daughtor()  {cout << "闺女析构" << endl;}void come(){cout << "闺女来了" << endl;}
};int main()
{Parent *p = new Son();Son *son = dynamic_cast< Son *> (p);if(son != nullptr){cout << "p有son子类" << endl;}else{cout << "p没有son子类" << endl;}Daughtor *daughtor = dynamic_cast< Daughtor *> (p);if(daughtor != nullptr){cout << "p有daughtor子类" << endl;}else{cout << "p没有daughtor子类" << endl;}if(typeid(p).name() ==  typeid(Parent*).name()){cout << "p指针是Parent类型" << endl; }else if(typeid(p).name() ==  typeid(Son*).name()){cout << "p指针是Son类型" << endl; } if(typeid(*p).name() ==  typeid(Parent).name()){cout << "p指针指向的是Parent类型" << endl; }else if(typeid(*p).name() ==  typeid(Son).name()){cout << "p指针指向的是Son类型" << endl; } delete p;return 0;
}

输出: 

通过前面的代码示例,我们知道,dynamic_cast和typeid能判断是不是某个类型,但是dynamic_cast和typeid也只能判断是不是某个类型,也就是只能知道类型名。这就是C++的缺点所在,也是Qt创建元对象系统的原因之一。

完整的描述一个类型需要很多信息,例如类的名字、有哪些父类、有哪些成员变量、有哪些成员函数、哪些是public的、哪些是private的、哪些是protected的等等。有时候一个工程项目可能包含成千上万个类,完整的保存这些信息将会消耗大量的内存资源。为了节省内存,C++标准约定typeid只能返回类名。因此,仅靠dynamic_cast和typeid两个关键字提供的类型信息实在有限。[1]

由于C++的RTTI机制只能提供有限的类型信息,于是Qt构建了自己的元对象系统(Meta-Object)。使用该系统的基类QObject所创建的派生类对象,可以在运行期获取该对象的类名、父类名、枚举类型以及有哪些成员变量、有哪些成员函数等信息。[1]

QT的元对象系统

1.元对象系统基本内容

Qt中的元对象系统(Meta-Object System)提供了对象间通信的信号和槽机制运行时类型信息动态属性系统QT中的元对象系统基于以下三个方面:

1)QObject类为能够利用元对象系统的类提供了一个基类。

2)" Q_OBJECT"宏用于启用元对象功能,例如信号槽机制。(一般建议在QObject的所有子类中使用Q_OBJECT宏,而不管它们是否使用了信号与槽。)

3)元对象编译器moc( Meta-Object Compiler )为每个QObject子类提供了实现元对象功能所需的代码。

moc工具读取一个c++源文件,如果它发现一个或多个包含Q_OBJECT宏的类声明,它会解析类的结构(信号、槽、属性、枚举等)等,并生成另一个c++源文件moc_*.cpp,其中包含每个类的元对象代码(元对象代码是Qt元对象系统的核心组成部分,它为Qt提供了信号与槽机制、动态属性系统和反射能力。)。生成的源文件要么#include到类的源文件中,要么(更常见的是)编译并链接到类的实现中。moc是由构建系统自动执行的。

2.元对象代码

例如,编写了一个widget.cpp,其中创建了widget类继承自QObject类并包含了Q_OBJECT宏。编译后,moc工具会自动生成一个名为moc_widget.cpp的文件:

1)MOC 生成的元对象数据结构存储类的所有元信息。(每个类只有一个元对象,它包含了类的名称、父类指针、属性、信号和槽等信息[4]。)

2)信号在 MOC 生成的代码中被实现为调用QMetaObject::activate():

3)每个槽函数对应一个元方法索引,用于运行时调用:

4)每个类的元对象通过staticMetaObject访问:

3.元对象系统其它特性

元对象系统主要是为了实现信号和槽机制才被引入的,不过除了信号和槽机制以外,元对象系统还提供了其他一些特性:

  • QObjeCt::metaObject()函数可以返回一个类的元对象,它是QMetaObject类的对象;
  • QMetaObject::className()可以在运行时以字符串形式返回类名,而不需要C+ +编辑器原生的运行时类型信息(RTTI)的支持;
  • QObject:: “inherits()函数返回一个对象是否是QObject继承树上一个类的实例的信息;
  • QObject: :tr()和QObject: :trUtf8()迸行字符串翻译来实现国际化;
  • QObject::setProperty()和QObject::property()通过名字来动态设置或者获取对象属性;
  • QMetaObject: :newlnstance()构造该类的一个新实例。

1)QObjeCt::metaObject()函数可以返回一个类的元对象,它是QMetaObject类的对象;

MyClass obj;
const QMetaObject* metaObj = obj.metaObject();

2) QMetaObject::className()可以在运行时以字符串形式返回类名,而不需要C+ +编辑器原生的运行时类型信息(RTTI)的支持;

// 获取类名
qDebug() << "类名:" << metaObj->className(); // 输出: "MyClass"

3)QObject:: “inherits()函数返回一个对象是否是QObject继承树上一个类的实例的信息;

#include <QObject>
#include <QWidget>
#include <QPushButton>
#include <QDebug>void printObjectHierarchy (const QObject* obj) {
qDebug () << "对象类型:" << obj->metaObject ()->className ();qDebug () << "是否是 QObject:" << obj->inherits ("QObject");
qDebug () << "是否是 QWidget:" << obj->inherits ("QWidget");
qDebug () << "是否是 QPushButton:" << obj->inherits ("QPushButton");
qDebug () << "是否是 QLabel:" << obj->inherits ("QLabel");
}int main() {
QObject baseObj;
QWidget widget;
QPushButton button;qDebug () << "=== QObject 对象 ===";
printObjectHierarchy (&baseObj);qDebug () << "\n=== QWidget 对象 ===";
printObjectHierarchy (&widget);qDebug () << "\n=== QPushButton 对象 ===";
printObjectHierarchy (&button);return 0;
}

4)QObject: :tr()和QObject: :trUtf8()迸行字符串翻译来实现国际化;

5)QObject::setProperty()和QObject::property()通过名字来动态设置或者获取对象属性;

​使用通用函数QObject::property() 和QObject::setProperty() 可以读写属性,除了属性名称外,无需知道属性所属类的任何信息。在下面的代码片段中,调用QAbstractButton::setDown() 和调用QObject::setProperty() 都设置了属性 "down"。 ​

QPushButton *button = new QPushButton;
QObject *object = button;button->setDown(true);
object->setProperty("down", true);

其他关于属性的详细内容可查看Qt官方手册: 

The Property System | Qt Core | Qt 6.9.0

6)QMetaObject: :newlnstance()构造该类的一个新实例。

当通过类型名构造类的新实例时,必须知道类型名。而newInstance()可以在运行时动态创建对象。

// 编译时已知类型创建实例
MyClass* obj = new MyClass(parent);
// 运行时通过元对象创建实例
const QMetaObject* metaObj = &MyClass::staticMetaObject;
QObject* obj = metaObj->newInstance(Q_ARG(QObject*, parent));

详细示例可查看下面的博客: 

使用Qt的meta-object系统,如QMetaObject::newInstance,QMetaObject::invokeMethod等创建对象 - 不败剑坤 - 博客园

参考文献:

[1] Qt中的元对象系统(Meta-Object System) - 知乎

[2] C++ typeid关键字详解-CSDN博客

[3] Qt对象模型之二:对象树与元对象系统 - fengMisaka - 博客园

[4] QT 元对象系统实现原理 - 知乎

[5]QMetaObject::newInstance()的使用 - 知乎

[6]使用Qt的meta-object系统,如QMetaObject::newInstance,QMetaObject::invokeMethod等创建对象 - 不败剑坤 - 博客园


http://www.hkcw.cn/article/wIGIRUfXAa.shtml

相关文章

Windows Server 2019--10 网络地址转换

本章要点 了解 NAT 技术的核心原理。掌握 NAT 技术的应用。掌握 NAT 网关的配置。 IP 地址分为两类&#xff0c;分别是私有IP地址(Private IP)与公用IP地址(Public IP)。 位于内部网络中的计算机使用的是私有IP地址&#xff0c;其不需要向IP地址发放机构提出申请。私有IP地址…

掌握STP技术:网络环路终结者实战

实验目的 理解生成树协议&#xff08;STP&#xff09;的基本原理及其在网络中的作用。掌握STP的配置方法&#xff0c;包括根桥选举、端口状态转换等关键机制。通过实验验证STP如何防止网络环路并实现冗余路径的优化管理。 实验环境 设备&#xff1a;支持STP的交换机&#xf…

通义灵码2.5——基于MCP打造我的12306火车票智能查询小助手

前沿技术应用全景图 本项目作为通义灵码2.5的标杆实践案例&#xff0c;展现了AI辅助开发在复杂业务系统中的革命性突破。通过深度集成12306 MCP服务体系&#xff0c;我们构建了一个融合智能决策、环境感知和自主优化的新一代火车票查询系统。 #mermaid-svg-4D7QqwJjsQRdKVP7 {…

不可变集合类型转换异常

记录一个异常&#xff1a;class java.util.ImmutableCollections$ListN cannot be cast to class java.util.ArrayList (java.util.ImmutableCollections$ListN and java.util.ArrayList 文章目录 1、原因2、解决方式一3、解决方式二4、关于不可变集合的补充4.1 JDK8和9的对比4…

初学python的我开始Leetcode题10-1

提示&#xff1a;100道LeetCode热题10-1主要是回溯相关&#xff0c;包括四题&#xff1a;全排列、子集、电话号码的字母组合、组合总和。由于初学&#xff0c;所以我的代码部分仅供参考。 前言 下周是第十六周&#xff0c;然后是两周的期末周&#xff0c;所以马上会缺两周左右…

IPTV电视直播 1.6.0 | 手机电视直播 秒播无卡顿

电视直播是一款功能强大且用户体验优秀的电视直播软件。它提供了丰富的节目资源&#xff0c;并支持高清画质播放&#xff0c;无论是家庭娱乐、移动办公还是学习&#xff0c;都能满足用户的需求。该应用完全无广告、无弹窗&#xff0c;确保用户享受纯净的观看体验。此外&#xf…

BugKu Web渗透之备份是个好习惯

启动场景后&#xff0c;网页显示一段字符串。 看起来像md5值&#xff0c;但是又过长了。 步骤一&#xff1a;右键查看源代码&#xff0c;没有发现任何异常。 步骤二&#xff1a;使用dirsearch去查看是否有其他可疑文件。 在终端输入&#xff1a; dirsearch -u http://117.72.…

深入理解 SELinux:通过 Nginx 和 SSH 服务配置实践安全上下文与端口策略

目录 一、引言 二、实验环境说明 三、实验 1&#xff1a;Nginx 服务安全上下文配置 3.1 实验目标 3.2 操作步骤 1. 开启 SELinux 并重启系统 2. 安装 Nginx 并创建自定义目录 3. 配置 Nginx 指向自定义目录 4. 分析 SELinux 上下文冲突 5. 修改上下文为合法类型 6. 验…

Linux 开发工具

1.sudo白名单 我们如果要让普通用户有sudo的权限 我们就要登录root用户 在/etc/sudoers目录下 通过文本编辑器&#xff08;我用的是vim&#xff09; 将要添加的用户 直接添加进去 如下图光标行就是我添加的白名单用户 然后我们添加的这个ly_centos就有sudo的权限了 2.gcc…

React 第四十九节 Router中useNavigation的具体使用详解及注意事项

前言 useNavigation 是 React Router 中一个强大的钩子&#xff0c;用于获取当前页面导航的状态信息。 它可以帮助开发者根据导航状态优化用户体验&#xff0c;如显示加载指示器、防止重复提交等。 一、useNavigation核心用途 检测导航状态&#xff1a;判断当前是否正在进行…

从数据持久化到网络通信与OpenCV:Qt应用程序开发的深度探索与实战

文章目录 前言一、QSettings&#xff1a;轻量级数据持久化方案1.1 QSettings 主要特点1.2 QSettings 常用函数整理 二、数据库2.1 连接SQLite数据库2.2 建表2.3 增删改 三、网络编程3.1 网络分层3.2 IP地址3.3 端口号3.4 基于TCP的Socket通信3.4 相关接口3.4.1核心类3.4.2 通信…

【产品经理从0到1】自媒体端产品设计

后台的定义 “后台” 与“前台”都是相对独立的平台&#xff0c;前台是服务于互联网用户的平台 &#xff0c;后台主要是支撑前台页面内容、数据及对前台业务情况的统计分析的系统&#xff1b; 后台与前台的区别 第1&#xff1a;使用用户不同 前台用户&#xff1a;互联网用户…

Ubuntu20.04操作系统ssh开启oot账户登录

文章目录 1 前提2 设置root密码3 允许ssh登录root账户3.1 编辑配置文件3.2 重启ssh服务 4 安全注意事项 1 前提 ssh可以使用普通用户正常登录。 2 设置root密码 打开终端&#xff0c;设置密码 sudo passwd root # 设置root密码3 允许ssh登录root账户 3.1 编辑配置文件 su…

四叉树实现四边形网格

import matplotlib.pyplot as plt import matplotlib.patches as patches import numpy as np # 四叉树节点 class QuadNode:def __init__(self, x, y, width, height, depth):self.x xself.y yself.width widthself.height heightself.depth depthself.children []self.…

园区智能化集成平台汇报方案

该方案为园区智能化集成平台设计,依据《智能建筑设计标准》等 20 余项国家与行业规范,针对传统园区信息孤岛、反应滞后、经验流失、管理粗放等痛点,构建可视化智慧园区管理平台,实现大屏数据可视化、三维设备监控、智慧运维(含工单管理、巡检打卡)、能源能耗分析、AI 安防…

C#中的BeginInvoke和EndInvoke:异步编程的双剑客

文章目录 引言1. BeginInvoke和EndInvoke的基本概念1.1 什么是BeginInvoke和EndInvoke1.2 重要概念解释 2. 委托中的BeginInvoke和EndInvoke2.1 BeginInvoke方法2.2 EndInvoke方法2.3 两者的关系 3. 使用方式与模式3.1 等待模式3.2 轮询模式3.3 等待句柄模式3.4 回调模式 4. 底…

基于通义千问的儿童陪伴学习和成长的智能应用架构。

1.整体架构概览 我们的儿童聊天助手将采用典型的语音交互系统架构,结合大模型能力和外部知识库: 2. 技术方案分解 2.1. 前端应用/设备 选择: 移动App(iOS/Android)、Web应用,或者集成到智能音箱/平板等硬件设备中。技术栈: 移动App: React Native / Flutter (跨平台…

【STIP】安全Transformer推理协议

Secure Transformer Inference Protocol 论文地址&#xff1a;https://arxiv.org/abs/2312.00025 摘要 模型参数和用户数据的安全性对于基于 Transformer 的服务&#xff08;例如 ChatGPT&#xff09;至关重要。虽然最近在安全两方协议方面取得的进步成功地解决了服务 Transf…

MyBatisPlus(1):快速入门

我们知道&#xff0c;MyBatis是一个优秀的操作数据库的持久层框架&#xff08;优秀持久层框架——MyBatis&#xff09;&#xff0c;其基于底层的JDBC进行高度封装&#xff0c;极大的简化了开发。但是对于单表操作而言&#xff0c;我们需要重复地编写简单的CRUD语句。这其实是不…

【ARM】【FPGA】【硬件开发】Chapter.1 AXI4总线协议

Chapter.1 AXI4总线协议 作者&#xff1a;齐花Guyc(CAUC) 一、总线介绍 AXI4总线 AXI4总线就像是SoC内部的“高速公路”&#xff0c;负责在不同硬件模块之间高效传输数据。 AXI4协议通过 5个独立通道 传输数据和控制信号&#xff0c;每个通道都有自己的信号线&#xff0c;互…