基于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 这些都是被解析的类型。解析后方便后续生成过程的处理。这里解析的时候用到了两个数组,keywords 和 keyword_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();