【QT】理解QT机制之“元对象系统”

article/2025/8/13 15:39:46

目录

前置知识:

(1)C++运行时多态

(2)RTTI

QT的元对象系统

1.元对象系统基本内容

2.元对象代码

3.元对象系统其它特性


前置知识:

在理解Qt的元对象系统之前,有必要理解C++的动态多态相关知识。

(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/RfoIAfOVgM.shtml

相关文章

男子蹭2天爱心小面顿顿2斤多 暖心之举遭遇“羊毛党”

上海面馆老板为困难人士提供免费重庆小面,男子连蹭两天“爱心小面”,顿顿吃两斤多。老板:我们也是农民,我们也很辛苦,你这样子我们也吃不消。” 暖心老板提供免费面被薅羊毛狂吃。责任编辑:zhangxiaohua

“苏超”战火端午再燃 十三城绿茵对决

当粽香与呐喊齐飞,绿茵共龙舟同频。江苏城市足球联赛在端午佳节迎来第三轮对决。十三座城市的足球悍将身披战袍,将龙舟精神注入绿茵场,让楚汉豪情碰撞江南热血,用足球为端午小长假点燃战火。“苏超”斗图第一波上线,带你直击“端午大战”前哨战。十三城用创意图片互放“狠…

业内人士:洛杉矶港5月货运量将减少 关税政策影响显现

近期美国政府的关税政策变化频繁,导致美国港口运营受到显著影响。业内人士透露,洛杉矶港作为美国最大、最繁忙的集装箱港口,5月的货运量可能大幅下降。洛杉矶港执行董事吉恩塞罗卡表示,在即将到来的传统海运旺季,港口业务却表现疲软。五月第一周和第四周的货运量已经减少了…

Java文件操作全解:File类与IO流实战指南

一、File类&#xff1a;文件系统操作核心 1. 核心功能概览 // 创建File对象&#xff08;文件或目录&#xff09; File file new File("/data/test.txt");// 常用方法 boolean exists file.exists(); // 存在性检查 boolean isDir file.isDirectory(); // 目…

波音CEO:六月恢复向中国交付飞机 预计影响金额少于5亿

美国波音公司首席执行官凯利奥特伯格5月29日宣布,该公司将于下个月恢复向中国交付飞机。这一消息是在伯恩斯坦战略决策会议上披露的。奥特伯格提到,波音预计受关税影响的金额将少于5亿美元,但未明确指出是对销售额还是利润造成的影响。这些影响主要来自为购买外国制造的飞机…

5岁抗癌男孩“豆丁”去世 历经8次手术仍未敌病魔

5月29日清晨7点23分,5岁的豆丁不幸病逝。他的父亲在社交媒体上更新了这一消息,并向所有关心和帮助过豆丁的网友表达了感谢。豆丁在短暂的生命中经历了八次手术,展现了极大的勇气。他曾面临严重的健康挑战,父亲甚至考虑过用自己的肝脏来挽救儿子的生命,但因病情恶化迅速,未…

端午将至 民俗体验游热度持续上升 亲子家庭成出行主力

今年端午节假期恰逢儿童节,亲子家庭成为出行主力军。短途游和民俗体验游热度持续上升。端午节假期旅游市场以本地游和周边游为主,微度假与民俗体验相结合的旅游方式受到游客青睐。数据显示,今年端午节假期,周边游预订量同比增长23%,市场整体呈现稳步增长态势。端午民俗体验…

最高法再点名余华英案 严惩侵害未成年人犯罪

近日,最高人民法院再次聚焦“余华英案”和“姐弟坠亡案”,明确传达出对侵害未成年人犯罪从严惩处的坚定态度。余华英长期实施拐卖儿童犯罪行为,严重破坏了多个家庭的幸福与安宁,给受害者及其家庭带来了无法磨灭的伤痛。拐卖儿童不仅侵犯了未成年人的人身权利,更挑战了社会…

实时超清直播延时分析及优化策略

前言:各直播技术延时分析 目录 RTSP传输延时分析 1.转换图像数据耗时. 2.编码耗时 3.网络传输 4.协议栈缓冲&#xff08;deepseek&#xff09;&#xff1a; 5.服务器处理&#xff08;deepseek&#xff09;&#xff1a; 6.客户端缓冲&#xff1a; RTSP传输延时分析 0.成…

话剧演员转行卖猫砂一年销售7亿元 跨界创新获成功

早上8点17分,我看到一条消息,简直惊掉了下巴:一个话剧演员转行去做猫砂,一年居然卖了7个亿。这就像是一位原本在舞台上大放异彩的明星,突然投身到宠物用品领域,还一下子成了冠军。这位“跨界大神”是上海宠物品牌pidan的创始人马文飞。他原本是话剧舞台上的重要角色,表演…

MySQL中事务并发的几类问题

不得不说&#xff0c;无论是常年与数据打交道的数据分析师和数据科学家&#xff0c;经常需要管理和维护数据库的数据库管理员&#xff0c;还是是需要了解嵌入式数据库的移动开发工程师,都免不了与SQL打交道。 事务并发的几类问题&#xff1a; 1.脏读&#xff1a;一个事务可以…

Excel 中的TEXTJOIN用法(基础版),将Excel 多个单元格内容按条件合并到一个单元格

1.新建一张数据透视表 选择你需要的维度所在的列 2.点击确定生成&#xff0c;勾选右边的维度 3.选中单元格&#xff0c;通过 ShiftF3 查看函数参数 第一个参数&#xff1a;分隔符&#xff0c;用来分隔合并的文本&#xff0c;不需要分隔用"" 第二个参数&#xff1a;…

Linux 基础开发工具的使用

目录 前言 一&#xff1a;下载工具yum 二&#xff1a;文本编辑器vim 1. 命令模式 2. 插入模式 3. 底行模式 三&#xff1a;gcc和g 基本使用格式 常用选项及作用 编译过程示例 四、Linux 项目自动化构建工具 ——make/Makefile 1. make 与 Makefile 的关系 2. Make…

清华大学教授李兆杰因病逝世 法学界痛失领军人物

5月29日晚,清华大学法学院发布讣告,沉痛悼念李兆杰教授。李兆杰教授于2025年5月29日在北京因病逝世,享年70岁。李兆杰教授是汉族,籍贯山东省东明县,1955年出生于吉林省长春市。他曾在北京大学国际法研究所和清华大学法学院任教。作为改革开放以来我国新一代国际法学者中的…

换完国货后才发现几千的工资够用了!

换完国货后才发现几千的工资够用了!责任编辑:zx0002

晚上10点睡和11点睡的区别!11点后睡觉浪费了人体的褪黑素

褪黑素的作用是帮助睡眠,其分泌量受光线影响:白天,褪黑素维持在很低的水平;晚上10点左右,褪黑素的分泌量迎来第一个拐点,分泌量开始陡然增加。22点入睡,正好与褪黑素大量分泌的时间契合,可充分发挥其促进睡眠的作用,提高入睡效率;如果到了23点才睡,敏感的人可能出现…

立志成为一名优秀测试开发工程师(第七天)——unittest框架的学习

目录 unittest框架的学习 一、测试类的编写 创建相关测试类cal.py、CountTest.py 二、常见断言方法 使用unittest单元测试框架编写测试用例CountTest.py 注意&#xff1a;执行的时候光标一定要放在括号后面&#xff0c;鼠标右键运行 三、对测试环境的初始化和清除模块…

断眉《歌手》将挑战单依纯或马嘉祺 网友票选对决对象

5月30日,湖南卫视歌手发布第二场揭榜赛制。微博网友可以从7位在线歌手中选择1位作为袭榜歌手查理普斯的对决对象。这7位在线歌手包括陈楚生、Mickey Guyton、GAI周延、白举纲、格瑞丝金斯勒、单依纯和马嘉祺。截止至当天17:00,推荐次数最高的歌手将作为守榜歌手,在节目中与袭…

罗家英第四次患癌 自曝剩9年寿命 乐观面对生命挑战

现年78岁的香港知名男星、老戏骨罗家英在接受媒体采访时透露,自己第四次确诊癌症,这次是前列腺癌。医生告诉他,还剩下9年寿命。有了前三次患癌的经历,罗家英对此表现得特别淡定。考虑到身体和年龄问题,罗家英拒绝接受化疗、电疗等治疗手段,选择顺其自然。他认为自己已经7…

断眉《歌手》合伙人是蒋一侨 音乐创作鬼才加盟

5月30日,湖南卫视《歌手》官方微博宣布查理普斯为袭榜歌手,他的音乐合伙人是蒋一侨。官博提到,查理普斯不仅才华横溢,现场演唱感染力十足,他将带来两首歌曲《Attention》和《See You Again》,但未透露他的袭榜对象。2024年5月31日晚,在《歌手2024》第四期的直播竞演中,…