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

目录

前置知识:

(1)C++运行时多态

(2)RTTI

QT的元对象系统

1.元对象系统基本内容

2.元对象代码

3.元对象系统其它特性


前置知识:

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

(1)C++运行时多态

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

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

cpp 复制代码
#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。

cpp 复制代码
#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能够返回类型的名字,在前面的代码里增加下面的代码,也可以判断指针和所指向对象的类型。

cpp 复制代码
    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; 
    } 

完整代码:

cpp 复制代码
#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类的对象;

cpp 复制代码
MyClass obj;
const QMetaObject* metaObj = obj.metaObject();

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

cpp 复制代码
// 获取类名
qDebug() << "类名:" << metaObj->className(); // 输出: "MyClass"

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

cpp 复制代码
#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"。 ​

cpp 复制代码
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()可以在运行时动态创建对象。

cpp 复制代码
// 编译时已知类型创建实例
MyClass* obj = new MyClass(parent);
cpp 复制代码
// 运行时通过元对象创建实例
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()的使用 - 知乎](https://zhuanlan.zhihu.com/p/625372030 "QMetaObject::newInstance()的使用 - 知乎") [\[6\]使用Qt的meta-object系统,如QMetaObject::newInstance,QMetaObject::invokeMethod等创建对象 - 不败剑坤 - 博客园](https://www.cnblogs.com/light-LifeClub/p/18733669 "[6]使用Qt的meta-object系统,如QMetaObject::newInstance,QMetaObject::invokeMethod等创建对象 - 不败剑坤 - 博客园")

相关推荐
星沁城5 分钟前
172. 阶乘后的零
java·算法·leetcode
卖猪肉的痴汉16 分钟前
5.2 Qt Creator 使用FFmpeg库
开发语言·qt·ffmpeg
teeeeeeemo38 分钟前
Number.toFixed() 与 Math.round() 深度对比解析
开发语言·前端·javascript·笔记
我在北京coding44 分钟前
Uncaught (in promise) TypeError: x.isoWeek is not a function
开发语言·javascript·vue.js
showmethetime1 小时前
[设计模式]创建型模式-单例模式
开发语言
Y1_again_0_again1 小时前
Java 包装类详解
java·开发语言
玉~你还好吗1 小时前
【LeetCode#第198题】打家劫舍(一维dp)
算法·leetcode
G等你下课1 小时前
摆动序列
算法
地平线开发者1 小时前
地平线高效 backbone: HENet - V1.0
算法·自动驾驶