【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等创建对象 - 不败剑坤 - 博客园