基于Qt5.14.2和mingw的Qt源码学习(三) — 元对象系统简介及moc工具是如何保存类属性和方法的


Qt中的特性信号槽,其实现就依赖于Qt的元对象系统。那么Qt的元对象系统是指什么呢?Qt的元对象系统依赖于什么实现的,又怎样使用呢?

一、什么是元对象系统

首先看下Qt官方的对元对象系统的描述The Meta-Object System

1、元对象系统目的

对象间通信
运行时类型信息
动态属性

2、实现元对象系统的关键

继承自QObject
声明Q_OBJECT宏
moc工具自动生成必要代码

3、元对象系统的其他一些特性

QObject::metaObject()
QMetaObject::className()
QObject::inherits()
QObject::tr() and QObject::trUtf8()
QObject::setProperty() and QObject::property()
QMetaObject::newInstance()
qobject_cast

二、Q_OBJECT

// qobjectdefs.h
/* 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, "")

这里定义的变量和方法我们下面会说道,都是用于获取类属性及方法调用的,现在主要来看看这里的宏的作用。

1、QT_WARNING_PUSH QT_WARNING_POP

// 	qcompilerdetection.h 
#define QT_DO_PRAGMA(text)                      _Pragma(#text)
#  define QT_WARNING_PUSH                       QT_DO_PRAGMA(clang diagnostic push)
#  define QT_WARNING_POP                        QT_DO_PRAGMA(clang diagnostic pop)

这两个宏对应的都是编译器提供的。

(1)_Pragma #pragma

这两个的作用其实类似,都是用在具体编译器实现特定编译选项的宏。#pragma 是C90的定义。其缺点每次声明编译选项时都要重新写一遍,而无法用一个宏定义去简化。因为#在宏定义中的作用咱们也说过。因此C99中就提供了 _Pragma 关键字简化我们的使用。

(2)作用

参考clang diagnostic的使用。其作用就是用于忽略代码中间产生的特定种类的警告。

2、Q_OBJECT_NO_OVERRIDE_WARNING

#  define Q_OBJECT_NO_OVERRIDE_WARNING      QT_WARNING_DISABLE_CLANG("-Winconsistent-missing-override")

这个警告的作用是子类继承父类并覆盖虚函数实现时如果没有使用override关键字会发出警告。

3、QT_TR_FUNCTIONS

#  define QT_TR_FUNCTIONS \
    static inline QString tr(const char *s, const char *c = nullptr, int n = -1) \
        { return staticMetaObject.tr(s, c, n); } \
    QT_DEPRECATED static inline QString trUtf8(const char *s, const char *c = nullptr, int n = -1) \
        { return staticMetaObject.tr(s, c, n); }

这部分主要的作用是对于本地化的支持,即使用QTranslator时可以支持多语言。

4、QT_ANNOTATE_CLASS(qt_qobject, “”)

// The following macros can be defined by tools that understand Qt
// to have the information from the macro.
#ifndef QT_ANNOTATE_CLASS
# define QT_ANNOTATE_CLASS(type, ...)
#endif

展开的话发现其实什么都没有。但是注意上面的注释,用于工具去解读信息。那么我们看下源码中对这个宏的调用。全局搜了下只有在qdoc工具中找到了,以后学习qdoc咱们再来看看。

// clangcodeparser.cpp
static const char *defaultArgs_[] = {
   
    "-std=c++14",
#ifndef Q_OS_WIN
    "-fPIC",
#else
    "-fms-compatibility-version=19",
#endif
    "-DQ_QDOC",
    "-DQ_CLANG_QDOC",
    "-DQT_DISABLE_DEPRECATED_BEFORE=0",
    "-DQT_ANNOTATE_CLASS(type,...)=static_assert(sizeof(#__VA_ARGS__),#type);",
    "-DQT_ANNOTATE_CLASS2(type,a1,a2)=static_assert(sizeof(#a1,#a2),#type);",
    "-DQT_ANNOTATE_FUNCTION(a)=__attribute__((annotate(#a)))",
    "-DQT_ANNOTATE_ACCESS_SPECIFIER(a)=__attribute__((annotate(#a)))",
    "-Wno-constant-logical-operand",
    "-Wno-macro-redefined",
    "-Wno-nullability-completeness",
    "-ferror-limit=0",
    "-I" CLANG_RESOURCE_DIR
};

三、QMetaObject 和 moc

QMetaObject可以说是元对象的基础。不过大部分时候这部分的实现都是由编译器调用 moc 工具自动生成的。我们就从这个类开始看元对象系统的构成。还是自下而上的进行分析,中间可能为了了某些功能的作用或实现可能要分析moc源码。

1、QMetaObject 中的 struct SuperData

这个结构体定义的实际是元对象的超类对象(什么是超类对象下面再说)。声明如下:

// qobjectdefs.h
struct SuperData {
   
        const QMetaObject *direct;
        SuperData() = default;
        constexpr SuperData(std::nullptr_t) : direct(nullptr) {
   }
        constexpr SuperData(const QMetaObject *mo) : direct(mo) {
   }

        constexpr const QMetaObject *operator->() const {
    return operator const QMetaObject *(); }

#ifdef QT_NO_DATA_RELOCATION
        using Getter = const QMetaObject *(*)();
        Getter indirect = nullptr;
        constexpr SuperData(Getter g) : direct(nullptr), indirect(g) {
   }
        constexpr operator const QMetaObject *() const
        {
    return indirect ? indirect() : direct; }
        template <const QMetaObject &MO> static constexpr SuperData link()
        {
    return SuperData(QMetaObject::staticMetaObject<MO>); }
#else
        constexpr operator const QMetaObject *() const
        {
    return direct; }
        template <const QMetaObject &MO> static constexpr SuperData link()
        {
    return SuperData(QMetaObject::staticMetaObject<MO>()); }
#endif
    };

这里direct就是实际保存的超类对象指针。

(1)nullptr std::nullptr_t

C++11支持的两个特性。
nullptr 关键字用于声明空指针,主要是为了解决C++重载时对于C中的 NULL 无法判断类型的问题,详情可以参考nullptr详解
std::nullptr_t 是空指针类型,定义如下。

// c++config.h
#if __cplusplus >= 201103L
  typedef decltype(nullptr)	nullptr_t;
#endif

这种类型主要用于重载函数时,若参数为指针,可以用此类型指定传参为空指针所调用的函数。详情可以参考std::nullptr_t

(2)constexpr

constexpr也是C++11支持的特性。其主要作用是用于 优化编译,告诉编译器编译的时候即可得出该表达式的值。
这个关键字和 const 的区别在于 const 只是用于声明具名常量的,在编译时并不会把具名常量直接用所对应的值替代。

(3)link函数

这个函数是一个 static 函数,用于直接通过类名调用。模板类为 QMetaObject 类引用。这里调用的 QMetaObject::staticMetaObject 也是 QMetaObject 类的函数,声明如下:

template <const QMetaObject &MO> static constexpr const QMetaObject *staticMetaObject()
{
   
    return &MO;
}

其实就是返回引用的地址,具体的使用下面再说。

2、moc 工具

这里并不主要讨论moc是怎么编译文件的,感兴趣的可以参考Qt元对象系统(Meta-Object)(四)、Moc源代码分析(其实这篇文章我也没有深入看过,不过如果有需要我自己也会看下源码)。其实这过程感觉就类似ini文件的读写,都是字符串操作。我们就简单说下moc源代码的位置以及其实现过程中调用的函数。

(1)moc源代码位置

Qt安装目录下Src\qtbase\src\tools\moc
主函数 main.cpp->main->runMoc

(2)运行过程

解析命令行参数
预处理 调用 Preprocessor::preprocessed 位于preprocessor.cpp中
解析文件 调用 Moc::parse — 位于moc.cpp文件中
生成moc文件 调用 Moc::generate -> Generator::generateCode — 位于generator.cpp文件中

这里有一个关键点就是预处理过程中的 符号解析。解析的目的就是把特殊符号对应的行数,起止字符位置,以及token类型解读出来。包括 #include class 这些都是被解析的类型。解析后方便后续生成过程的处理。这里解析的时候用到了两个数组,keywordskeyword_trans 用的是一种表驱动法对数据进行处理。

(3)调用方法

moc 源文件名 -o 目标文件名

3、qt_meta_stringdata_QObject_t、QT_MOC_LITERAL、qt_meta_stringdata_QObject

这部分用于初始化元对象。我们使用 moc 工具对源码中的 qobject.h 进行编译得到 moc_qobject.cpp,我们从这个文件进行学习。

struct qt_meta_stringdata_QObject_t {
   
    QByteArrayData data[8];
    char stringdata0[87];
};
#define QT_MOC_LITERAL(idx, ofs, len) \
    Q_STATIC_BYTE_ARRAY_DATA_HEADER_INITIALIZER_WITH_OFFSET(len, \
    qptrdiff(offsetof(qt_meta_stringdata_QObject_t, stringdata0) + ofs \
        - idx * sizeof(QByteArrayData)) \
    )
static const qt_meta_stringdata_QObject_t qt_meta_stringdata_QObject = {
   
    {
   
QT_MOC_LITERAL(0, 0, 7), // "QObject"
QT_MOC_LITERAL(1, 8, 9), // "destroyed"
QT_MOC_LITERAL(2, 18, 0), // ""
QT_MOC_LITERAL(3, 19, 17), // "objectNameChanged"
QT_MOC_LITERAL(4, 37, 10), // "objectName"
QT_MOC_LITERAL(5, 48, 11), // "deleteLater"
QT_MOC_LITERAL(6, 60, 19), // "_q_reregisterTimers"
QT_MOC_LITERAL(7, 80, 6) // "parent"

    },
    "QObject\0destroyed\0\0objectNameChanged\0"
    "objectName\0deleteLater\0_q_reregisterTimers\0"
    "parent"
};

qt_meta_stringdata_CLS_ReflectionTest_t 是根据类名生成的结构体,qt_meta_stringdata_CLS_ReflectionTest 是用这个结构体定义的一个变量。QT_MOC_LITERAL 是用来初始化结构体的一个宏定义。
这三个概念的理解得放在一起,首先我们知道了这个结构体的定义。先不管这个宏的意义,我们先从 qt_meta_stringdata_QObject 的初始化开始。

(1)qt_meta_stringdata_QObject_t::stringdata及其初始化

首先我们要弄明白这个字段表示什么意思。从下面初始化的内容中我们可以看出来至少里面是包括了一些方法名称,同时各字段以"\0"分隔。那么具体包含那些特殊的方法?或者是否还包含成员变量呢?我们需要简单看下 moc 工具的源代码。

// moc.cpp 
// void Moc::generate(FILE *out)
for (i = 0; i < classList.size(); ++i) {
   
    Generator generator(&classList[i], metaTypes, knownQObjectClasses, knownGadgets, out);
    generator.generateCode();
}

这里我们可以看出对每个类都会创建这样一段由类名定义的类型及变量。
那么每个类中的stringdata字段由哪些数据构成呢?

// generator.cpp
// Generator::generateCode()
strreg(cdef->qualified);
registerClassInfoStrings();
registerFunctionStrings(cdef->signalList);
registerFunctionStrings(cdef->slotList);
registerFunctionStrings(cdef->methodList);
registerFunctionStrings(cdef->constructorList);
registerByteArrayVector(cdef->nonClassSignalList);
registerPropertyStrings();
  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
Qt 5.14.2是一个跨平台的应用程序开发框架。它提供了丰富的功能和工具,使开发人员能够轻松地创建高质量的应用程序。其中,"mingw73_static"是Qt 5.14.2的一个编译版本。 "Mingw73_static"指的是使用MinGW 7.3编译器编译的Qt库。MinGW是Windows平台上的一个开发工具集,它允许您使用GNU工具集来编译和构建应用程序。使用MinGW进行静态编译意味着所有依赖的库都被链接到最终生成的应用程序中,这使得应用程序更容易分发和部署。 "Mingw73_static"版本的Qt提供了一些特定的好处。首先,它附带了一个稳定的MinGW 7.3编译器,这意味着您可以使用更新的C++标准和更好的性能优化。其次,静态编译可以减少运行时所需的依赖项,这提高了应用程序的可移植性和发行效率。尤其对于需要分发到不同电脑上、不同环境的应用程序来说,这一点尤为重要。 然而,需要注意的是,静态编译可能会增加应用程序的文件大小,因为所有依赖项都被包含在应用程序中。此外,由于静态编译在编译时解决了依赖项,因此在运行时无法动态更新这些库。这可能会对应用程序的灵活性和可扩展性产生一定影响。 总之,"Qt 5.14.2 mingw73_static"是一个基于Qt 5.14.2版本的编译版本,使用MinGW 7.3编译器进行静态编译。它提供了更好的性能优化和便于分发的优势,但对应用程序的大小和动态更新能力可能产生一些影响。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值