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 宏的类,为此,需要遵守以下规则

  1. 从 QObject 派生的含有 Q_OBJECT 宏的类的定义必须在头文件中。

  2. 确保 pro 文件中,是否列举了项目中的所有源文件(SOURCES 变量)和头文件(HEADERS 变量)

  3. 应在头文件中使用逻辑指令(比如#ifndef)防止头文件被包含多次。

  4. QObject 类应是基类列表中的第一个类。

  5. 由以上规则可见,使用 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 模式(反射模式或反射机制):是指在运行时,能获取任意一个类对象的所有类型信息、属性、成员函数等信息的一种机制。

  1. 元对象系统提供的功能之一是为 QObject 派生类对象提供运行时的类型信息及数据成员的当前值等信息,也就是说,在运行阶段,程序可以获取 QObject 派生类对象所属类的名称、父类名称、该对象的成员函数、枚举类型、数据成员等信息,其实这就是反射机制。

  2. 因为 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元对象系统的主要内容,如果未能解决你的问题,请参考以下文章

Qt教程 : Qt元对象系统

Qt入门教程QObject篇属性系统

Qt入门教程QObject篇属性系统

Qt入门教程QObject篇属性系统

Qt高级——Qt元对象系统源码解析

Qt笔记——元对象系统