Qt教程 : Qt元对象系统
Posted QtHalcon
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Qt教程 : Qt元对象系统相关的知识,希望对你有一定的参考价值。
元对象是指用于描述另一个对象结构的对象。使用编程语言具体实现时,其实就是一个类的对象,只不过这个对象专门用于描述另一个对象而已,比如 class B{…}; class A{…B mb;…};假设 mb 是用来描述类 A 创建的对象的,则 mb 就是元对象。
一、元对象系统
Qt 的元对象系统提供的功能有:对象间通信的信号和槽机制、运行时类型信息和动态属性系统等。
元对象系统是 Qt 对原有的 C++进行的一些扩展,主要是为实现信号和槽机制而引入的,信号和槽机制是 Qt 的核心特征。
要使用元对象系统的功能,需要满足以下三个条件
-
该类必须继承自 QObject 类。
-
必须在类声明的私有区域添加 Q_OBJECT 宏,该宏用于启动元对象特性,然后便可使用动态特性、信号和槽等功能了。
-
元对象编译器(moc)为每个 QObject 的子类,提供实现了元对象特性所必须的代码。
元对象系统具体运行原则
-
因为元对象系统是对 C++的扩展,因此使用传统的编译器是不能直接编译启用了元对象系统的 Qt 程序的,对此在编译 Qt 程序之前,需要把扩展的语法去掉,该功能就是 moc 要做的事。
-
moc 全称是 Meta-Object Compiler(元对象编译器),它是一个工具(类似于 qmake),该工具读取并分析 C++源文件,若发现一个或多个包含了 Q_OBJECT 宏的类的声明,则会生成另外一个包含了 Q_OBJECT 宏实现代码的 C++源文件(该源文件通常名称为 moc_*.cpp) ,这个新的源文件要么被#include 包含到类的源文件中,要么被编译链接到类的实现中(通常是使用的此种方法)。注意:新文件不会“替换”掉旧的文件,而是与原文件一起编译。
二、Q_OBJECT
Q_OBJECT源码如下:
/* qmake ignore Q_OBJECT */
#define Q_OBJECT \\
public: \\
QT_WARNING_PUSH \\
Q_OBJECT_NO_OVERRIDE_WARNING \\
static const QMetaObject staticMetaObject; \\
virtual const QMetaObject *metaObject() const; \\
virtual void *qt_metacast(const char *); \\
virtual int qt_metacall(QMetaObject::Call, int, void **); \\
QT_TR_FUNCTIONS \\
private: \\
Q_OBJECT_NO_ATTRIBUTES_WARNING \\
Q_DECL_HIDDEN_STATIC_METACALL static void qt_static_metacall(QObject *, QMetaObject::Call, int, void **); \\
QT_WARNING_POP \\
struct QPrivateSignal {}; \\
QT_ANNOTATE_CLASS(qt_qobject, "")
/* qmake ignore Q_OBJECT */
#define Q_OBJECT_FAKE Q_OBJECT QT_ANNOTATE_CLASS(qt_fake, "")
#ifndef QT_NO_META_MACROS
/* qmake ignore Q_GADGET */
#define Q_GADGET \\
public: \\
static const QMetaObject staticMetaObject; \\
void qt_check_for_QGADGET_macro(); \\
typedef void QtGadgetHelper; \\
private: \\
QT_WARNING_PUSH \\
Q_OBJECT_NO_ATTRIBUTES_WARNING \\
Q_DECL_HIDDEN_STATIC_METACALL static void qt_static_metacall(QObject *, QMetaObject::Call, int, void **); \\
QT_WARNING_POP \\
QT_ANNOTATE_CLASS(qt_qgadget, "") \\
/*end*/
Q_OBJECT宏可以告诉预编译器该类具有gui元素,并且需要通过“ moc”运行。
此时 moc 工具是通过 Qt Creator 来使用的,因此必须保证 moc 能发现并处理项目中包含有 Q_OBJECT 宏的类,为此,需要遵守以下规则
-
从 QObject 派生的含有 Q_OBJECT 宏的类的定义必须在头文件中。
-
确保 pro 文件中,是否列举了项目中的所有源文件(SOURCES 变量)和头文件(HEADERS 变量)
-
应在头文件中使用逻辑指令(比如#ifndef)防止头文件被包含多次。
-
QObject 类应是基类列表中的第一个类。
-
由以上规则可见,使用 Qt Creator 编写代码时,类应定义在头文件中,成员函数的定义应位于源文件中(这样可避免头文件被包含多次产生的重定义错误),虽然这样编写程序比较麻烦,但这是一种良好的代码组织方式。
不按规则 1 的方法编写程序,则 moc 工具就不能正确生成代码,这时的错误原因通常是未定义由 Q_OBJECT 展开后在类中声明的虚函数引起的,其错误信息如下:
若使用 MinGw 编译,产生的错误信息类似如下:
undefined reference to `vtable for A'
//表示类 A 的虚函数表(vtable)不能正常生成,通常是有虚函数未定义。
若使用 VC++2015 编译,产生的错误信息类似如下:
LNK2001: 无法解析的外部符号 "public: virtual struct QMetaObject const * __thiscall A::metaObject(void)const "
// 表示虚函数 A::metaObject 未定义。
若定义了QObject 类的派生类,并进行了构建,在这之后再添加 Q_OBJECT 宏,则此时必须执行一次 qmake 命令(“构建”>“执行 qmake”),否则 moc 不能生成代码。
三、反射机制
reflection 模式(反射模式或反射机制):是指在运行时,能获取任意一个类对象的所有类型信息、属性、成员函数等信息的一种机制。
-
元对象系统提供的功能之一是为 QObject 派生类对象提供运行时的类型信息及数据成员的当前值等信息,也就是说,在运行阶段,程序可以获取 QObject 派生类对象所属类的名称、父类名称、该对象的成员函数、枚举类型、数据成员等信息,其实这就是反射机制。
-
因为 Qt 的元对象系统必须从 QObject 继承,又从反射机制的主要作用可看到,Qt 的元对象系统主要是为程序提供了 QObject 类对象及其派生类对象的信息,也就是说不是从 QObject 派生的类对象,则无法使用 Qt 的元对象系统来获取这些信息。
3.1 Qt 具体实现反射机制的方法
-
Qt 使用了一系列的类来实现反射机制,这些类对对象的各个方面进行了描述,其中QMetaObject 类描述了 QObject 及其派生类对象的所有元信息,该类是 Qt 元对象系统的核心类,通过该类的成员函数可以获取 QObject 及其派生类对象的所有元信息,因此可以说 QMetaObject 类的对象是 Qt 中的元对象。注意:要调用 QMetaObject 类中的成员函数需要使用 QMetaObject 类型的对象。
-
对对象的成员进行描述:一个对象包含数据成员、函数成员、构造函数、枚举成员等成员,在 Qt 中,这些成员分别使用了不同的类对其进行描述,比如函数成员使用类QMetaMethod 进行描述,属性使用 QMetaProperty 类进行描述等,然后使用QMetaObject 类对整个类对象进行描述,比如要获取成员函数的函数名,其代码如下:
QMetaMethod qm = metaObject->method(1); //获取索引为 1 的成员函数
qDebug()<<qm.name()<<"\\n"; //输出该成员函数的名称。
3.2 使用 Qt 反射机制的条件
-
需要继承自 QObject 类,并需要在类之中加入 Q_OBJECT 宏。
-
注册成员函数:若希望普通成员函数能够被反射,需要在函数声明之前加入QObject::Q_INVOKABLE 宏。
-
注册成员变量:若希望成员变量能被反射,需要使用 Q_PROPERTY 宏。
3.3 Qt 反射机制实现原理简述
Q_OBJECT 宏展开之后有一个虚拟成员函数 meteObject(),该函数会返回一个指向QMetaObject 类型的指针,其原型为
virtual const QMetaObject *metaObject() const;
因为启动了元对象系统的类都包含 Q_OBJECT 宏,所以这些类都有含有 metaObject()虚拟成员函数,通过该函数返回的指针调用 QMetaObject 类中的成员函数,便可查询到 QObject 及其派生类对象的各种信息。
Qt 的 moc 会完成以下工作
-
为 Q_OBJECT 宏展开后所声明的成员函数的成生实现代码
-
识别 Qt 中特殊的关键字及宏,比如识别出 Q_PROPERTY 宏、Q_INVOKABLE宏、slot、signals 等
3.4 使用反射机制获取与类相关的信息
QMetaObject 类中获取与类相关的信息的成员函数有
const char* className() const;
获取类的名称,注意,若某个 QObject 的子类未启动元对象系统(即未使用 Q_OBJECT宏),则该函数将获取与该类最接近的启动了元对象系统的父类的名称,而不再返回该类的名称,因此建议所有的 QObject 子类都使用 Q_OBJECT 宏。
const QMetaObject* superClass() const;
返回父类的元对象,若没有这样的对象则返回 0。
bool inherits(const QMetaObject* mo) const;
若该类继承自 mo 描述的类型,则返回 true,否则返回 false。类被认为继承自身。
QObject 类中获取与类相关的信息的成员函数有
bool inherits(const char* className) const;
若该类是className 指定的类的子类则返回true,否则返回false。类被认为继承自身。
以下是一个示例:
//头文件 m.h 的内容
#ifndef M_H //要使用元对象系统,需在头文件中定义类。
#define M_H
#include<QObject>
class A:public QObject{ Q_OBJECT};
class B:public A{ Q_OBJECT};
class C:public QObject{Q_OBJECT};
class D:public C{};
#endif // M_H
//源文件 m.cpp 的内容
#include "m.h"
#include <QMetaMethod>
#include <iostream>
using namespace std;
int main()
{
A ma; B mb; C mc; D md;
const QMetaObject *pa=ma.metaObject();
const QMetaObject *pb=mb.metaObject();
cout<<pa->className()<<endl; //输出类名 A
//使用 QMetaObject::inherits()函数判断继承关系。
cout<<pa->inherits(pa)<<endl; //输出 1,类被认为是自身的子类
cout<<pa->inherits(pb)<<endl; //输出 0,由 pb 所描述的类 B 不是类 A 的子类。
cout<<pb->inherits(pa)<<endl; //输出 1,由 pa 所描述的类 A 是类 B 的子类。
//使用 QObject::inherits()函数判断继承关系。
cout<<ma.inherits("B")<<endl; //输出 0,类 A 不是类 B 的子类。
cout<<ma.inherits("A")<<endl; //输出 1,类被认为是自身的子类
cout<<md.inherits("D")<<endl; //输出 0,因为类 D 未启动元对象系统。
cout<<md.inherits("C")<<endl; //输出 1,虽然类 D 未启动元对象系统,但类 C 已启动,此种情形下能正确判断继承关系。
cout<<md.metaObject()->className()<<endl; /输出 C,此处未输出 D,因为类 D 未启动元对象系统,与类 D 最接近的启动了元对象系统的父类是 C,因此返回 C。
return 0;
}
以上是关于Qt教程 : Qt元对象系统的主要内容,如果未能解决你的问题,请参考以下文章