当前位置: 首页 > news >正文

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

目录

前置知识:

(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.xdnf.cn/news/722251.html

相关文章:

  • Java 注解与反射(超详细!!!)
  • Linux进程替换与自定义Shell详解:从零开始理解
  • python模块和包
  • java队列
  • EMQX将社区版和企业版统一到一个强大的 EMQX 平台
  • 文件操作管理
  • 从 0 到 1 的显示革命:九天画芯张锦解码铁电液晶技术进化史
  • 力扣HOT100之动态规划:70. 爬楼梯
  • Windows 下如何打开设置环境变量的对话框
  • 男子垒球世界纪录是多少米·棒球1号位
  • 26考研 | 王道 | 第六章 应用层
  • 解析C++排序算法
  • linux服务器ssh远程中文显示问号
  • VL 中间语言核心技术架构:构建全链路开发生态
  • 【仿生系统】潜移默化 —— Claude4 的解决方案
  • java上机测试错题回顾(4)
  • JAVA与C语言之间的差异(一)
  • 王树森推荐系统公开课 特征交叉01:Factorized Machine (FM) 因式分解机
  • vue自定义穿梭框(内容体+多选框)
  • SMT贴片工艺核心要点解析
  • 连接远程桌面计算机提示:“这可能是由于CredSSP加密数据库修正” 问题解决方案
  • OpenLayers 地图打印
  • C++创建对象过程
  • 攻防世界-BadProgrammer
  • siglip2(2) Naflex模型的动态分辨率原理
  • 微信小店推客系统带来的便利性
  • IPTV电视直播 1.6.0 | 手机电视直播 秒播无卡顿
  • 短视频一键搬运 v1.7.1|短视频无水印下载 一键去重
  • 计算几何 视频截图
  • int和Integer的区别