Qt源码解析——元对象系统热身
关键词:Qt 源码 QObject QMetaObject 元对象系统 属性 事件 信号 槽
概述
官方文档第二章内容就是元对象系统,它在介绍里描述到:
Qt的元对象系统提供了信号和槽机制(用于对象间的通信)、运行时类型信息和动态属性系统。
元对象系统基于三个要素:
QObject类为那些可以利用元对象系统的对象提供了一个基类。- 在类声明的私有部分中使用
Q_OBJECT宏用于启用元对象特性,比如动态属性、信号和槽。 - 元对象编译器(
moc)为每个QObject子类提供必要的代码来实现元对象特性。
moc工具读取C++源文件,如果发现一个或多个包含Q_OBJECT宏的类声明,它会生成另一个C++源文件,其中包含了这些类的每个元对象的代码。这个生成的源文件被#include进入类的源文件,更常见的是被编译并链接到类的实现中。
引入这个系统的主要原因是信号和槽机制,此外它还提供了一些额外功能:
QObject::metaObject()返回与该类相关联的元对象。QMetaObject::className()在运行时以字符串形式返回类名,而无需通过 C++ 编译器提供本地运行时类型信息(RTTI)支持。QObject::inherits()函数返回一个对象是否是在 QObject 继承树内继承了指定类的实例。QObject::tr()和QObject::trUtf8()用于国际化的字符串翻译。QObject::setProperty()和QObject::property()动态地通过名称设置和获取属性。QMetaObject::newInstance()构造该类的新实例。
上面说到的元对象系统三要素,第3点moc会在后面用单独篇章分析,下面就不再展开,第1点我们在上一篇中做了简单的分析,本篇我们看看第2点——Q_OBJECT到底怎么启用了元对象系统(然而启用非常复杂,我们先浏览个大概,所以标题叫热身)。
staticMetaObject
找到源码中出现QMetaObject的地方:
//qobject.h
class Q_CORE_EXPORT Qobject{
Q_OBJECT
//...
protected:
static const QMetaObject staticQtMetaObject;
//...
}
和QMetaObject相关的变量只有2个地方出现,既然前面说了Q_OBJECT和元对象系统相关,那我们就直接看Q_OBJECT的定义:
//qobjectdefs.h
#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, "")
我们关注变量static const QMetaObject staticMetaObject,这是一个QMetaObject类型的静态变量,它应该是和元对象系统相关,文档对QMetaObject的描述:
QMetaObject类包含有关Qt对象的元信息。每个在应用程序中使用的QObject子类都会创建一个QMetaObject实例,该实例存储了该QObject子类的所有元信息。此对象可通过QObject::metaObject()方法获得。
QMetaObject就是元对象系统的关键了,查看QMetaObject的定义:
//qobjectdefs.h
struct Q_CORE_EXPORT QMetaObject{
//...
struct { // private data
const QMetaObject *superdata;
const QByteArrayData *stringdata;
const uint *data;
typedef void (*StaticMetacallFunction)(QObject *, QMetaObject::Call, int, void **);
StaticMetacallFunction static_metacall;
const QMetaObject * const *relatedMetaObjects;
void *extradata; //reserved for future use
} d;
}
QMetaObject是个结构体,没有构造函数。忽略掉所有方法声明,只剩一个结构体变量,而且我们在qobject.cpp中也没有看到staticMetaObject对应的初始化。那会不会在子类中初始化了?我们新建一个空的QMainWindow工程,继承关系是这样的:
//MainWindow->QMainWindow->QWidget->QObject
遗憾的是我们并没有在MainWindow、QMainWindow、QWidget的构造器中找到staticMetaObject初始化的痕迹。
moc_mainwindow.cpp
想起来官方文档说moc会处理Q_OBJECT宏,那就去moc文件找找——果然找到了staticMetaObject相关的语句:
//moc_mainwindow.cpp
QT_INIT_METAOBJECT const QMetaObject MainWindow::staticMetaObject = { {
&QMainWindow::staticMetaObject,
qt_meta_stringdata_MainWindow.data,
qt_meta_data_MainWindow,
qt_static_metacall,
nullptr,
nullptr
} };
结合QMetaObject的声明,我们很容易看出这是在对QMetaObject的变量赋值:
| 变量名 | 值 |
|---|---|
const QMetaObject *superdata |
&QMainWindow::staticMetaObject |
const QByteArrayData *stringdata |
qt_meta_stringdata_MainWindow.data |
const uint *data |
qt_meta_data_MainWindow |
StaticMetacallFunction static_metacall |
qt_static_metacall |
const QMetaObject * const *relatedMetaObjects |
nullptr |
void *extradata |
nullptr |
对于const QMetaObject *superdata = &QMainWindow::staticMetaObject;
MainWindow的staticMetaObject的superdata持有了QMainWindow的staticMetaObject``,说明MainWindow可以访问QMainWindow的staticMetaObject。由于并不能看到moc_qmainwindow.cpp等,我们只能从变量名合理猜测任何类的staticMetaObject都持有了父类的staticMetaObject。
做个实验测试一下:
//mainwindow.cpp
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
, ui(new Ui::MainWindow)
{
//...
const QMetaObject *metaDta = staticMetaObject.d.superdata;
while(metaDta){
qDebug() << metaDta->className();
metaDta = metaDta->d.superdata;
}
}
/*
输出结果:
QMainWindow
QWidget
QObject
*/
果不其然,打印结果是输出了MainWindow所有父类的className。那么我们基本可以断定,继承链中staticMetaObject的持有关系如下图所示:

对于const QByteArrayData *stringdata = qt_meta_stringdata_MainWindow.data;
在moc文件里找到qt_meta_stringdata_MainWindow变量:
//moc_mainwindow.cpp
static const qt_meta_stringdata_MainWindow_t qt_meta_stringdata_MainWindow = {
{
QT_MOC_LITERAL(0, 0, 10) // "MainWindow"
},
"MainWindow"
};
qt_meta_stringdata_MainWindow是一个qt_meta_stringdata_MainWindow_t类型,这里对它进行了初始化。继续找到qt_meta_stringdata_MainWindow_t的定义:
//moc_mainwindow.cpp
struct qt_meta_stringdata_MainWindow_t {
QByteArrayData data[1];
char stringdata0[11];
};
也就是说stringdata的值为QT_MOC_LITERAL(0, 0, 10) // "MainWindow"。
继续找到QT_MOC_LITERAL的定义:
//moc_mainwindow.cpp
#define QT_MOC_LITERAL(idx, ofs, len) \
Q_STATIC_BYTE_ARRAY_DATA_HEADER_INITIALIZER_WITH_OFFSET(len, \
qptrdiff(offsetof(qt_meta_stringdata_MainWindow_t, stringdata0) + ofs \
- idx * sizeof(QByteArrayData)) \
)
这个宏的作用是创建一个静态的 QByteArrayData 结构体,该结构体包含了字符串字面值的元数据。再结合注释我们推断stringdata代表"MainWindow"字符串,这里似乎是保存的类名MainWindow。从变量名qt_meta_stringdata_MainWindow推断,这个变量应该就是保存的元对象相关的字符串字面量,但我们默认工程没有元对象,我们在代码中加一个signal:
//mainwindow.h
signals:
void testSignal();
重新编译,可以看到,qt_meta_stringdata_MainWindow变量的初始化有所改变,从注释看明显包含了我们所加信号的名称:
//moc_mainwindow.cpp
static const qt_meta_stringdata_MainWindow_t qt_meta_stringdata_MainWindow = {
{
QT_MOC_LITERAL(0, 0, 10), // "MainWindow"
QT_MOC_LITERAL(1, 11, 10), // "testSignal"
QT_MOC_LITERAL(2, 22, 0) // ""
},
"MainWindow\0testSignal\0"
};
对于const uint *data = qt_meta_data_MainWindow;
在moc文件中找到qt_meta_data_MainWindow定义,它是一个uint数组,目前还看不出它的作用。
//moc_mainwindow.cpp
static const uint qt_meta_data_MainWindow[] = {
// content:
8, // revision
0, // classname
0, 0, // classinfo
0, 0, // methods
0, 0, // properties
0, 0, // enums/sets
0, 0, // constructors
0, // flags
0, // signalCount
0 // eod
};
对于StaticMetacallFunction static_metacall = qt_static_metacall;
在moc文件里找到qt_static_metacall定义,如果是默认工程,似乎也不做什么:
//moc_mainwindow.cpp
void MainWindow::qt_static_metacall(QObject *_o, QMetaObject::Call _c, int _id, void **_a)
{
Q_UNUSED(_o);
Q_UNUSED(_id);
Q_UNUSED(_c);
Q_UNUSED(_a);
}
对于const QMetaObject * const *relatedMetaObjects = nullptr;和void *extradata = nullptr;暂时不讨论。
我们目前找到了staticMetaObject初始化的位置,知道它被赋值了一些数据结构,这些数据结构都和moc相关。
QMetaObject其他成员
回过头来,我们看看QMetaObject的其他成员。
//qobjectdefs.h
struct Q_CORE_EXPORT QMetaObject
{
class Connection;
//...
}
class Q_CORE_EXPORT QMetaObject::Connection {
//...
};
Connection,QMetaObject的内部类,文档描述:
Represents a handle to a signal-slot (or signal-functor) connection.
它代表了信号-槽的连接,那就是说我们平常使用的connect都和它相关,是个非常重要的角色。
我们可以看看我们一般使用的connect的定义:
//qobject.h
template <typename Func1, typename Func2>
static inline typename std::enable_if<QtPrivate::FunctionPointer<Func2>::ArgumentCount == -1, QMetaObject::Connection>::type
connect(/*...*/)
{
//...
return connectImpl(/*...*/);
}
调用了connectImpl():
//qobject.h
static QMetaObject::Connection connectImpl(/*...*/);
的确是返回了QMetaObject::Connection,由此可见Connection是信号-槽系统的关键角色,它代表了一个建立的连接。
再看看其他接口:
//qobjectdefs.h
struct Q_CORE_EXPORT QMetaObject
{
//...
//基本信息
const char *className() const;
const QMetaObject *superClass() const;
bool inherits(const QMetaObject *metaObject) const Q_DECL_NOEXCEPT;
//和类信息相关
int classInfoOffset() const;
int classInfoCount() const;
int indexOfClassInfo(const char *name) const;
QMetaClassInfo classInfo(int index) const;
//和方法相关
int methodOffset() const;
int methodCount() const;
int indexOfMethod(const char *method) const;
QMetaMethod method(int index) const;
//和枚举相关
int enumeratorOffset() const;
int enumeratorCount() const;
int indexOfEnumerator(const char *name) const;
QMetaEnum enumerator(int index) const;
//和属性相关
int propertyOffset() const;
int propertyCount() const;
int indexOfProperty(const char *name) const;
QMetaProperty property(int index) const;
QMetaProperty userProperty() const;
//和构造器相关
int constructorCount() const;
int indexOfConstructor(const char *constructor) const;
QMetaMethod constructor(int index) const;
//和信号、槽相关
int indexOfSignal(const char *signal) const;
int indexOfSlot(const char *slot) const;
static bool checkConnectArgs(const char *signal, const char *method);
static bool checkConnectArgs(const QMetaMethod &signal,
const QMetaMethod &method);
static QByteArray normalizedSignature(const char *method);
static QByteArray normalizedType(const char *type);
//...
}
这些方法几乎提供了获取所有"元成员"信息的方式(好玩的是源码作者强迫症一样地把功能类似的方法放到了一起),包括构造器、方法、属性等,之所以说“元成员”,是因为被Q_INVOKABLE、Q_PROPERTY等宏修饰的成员才具有"元能力"(当然,这也是后话了)。熟悉其他语言中反射特性的同学应该对这些方法的构成和名字比较熟悉,元对象系统的确为Qt提供了类似反射的能力。
接下来是和信号-槽相关的接口:
//qobjectdefs.h
struct Q_CORE_EXPORT QMetaObject
{
// internal index-based connect
static Connection connect(const QObject *sender, int signal_index,
const QObject *receiver, int method_index,
int type = 0, int *types = nullptr);
// internal index-based disconnect
static bool disconnect(const QObject *sender, int signal_index,
const QObject *receiver, int method_index);
//...
// internal index-based signal activation
static void activate(QObject *sender, int signal_index, void **argv);
//...
}
从注释来看,这些接口用于内部,是以索引为基础的一些方法,暂时没接触到它们使用的场景。
接下来是很多重载或者模板的invokeMethod():
//qobjectdefs.h
struct Q_CORE_EXPORT QMetaObject
{
//...
invokeMethod(/*...*/);
//...
}
官方文档说明:
Invokes the member (a signal or a slot name) on the object obj
看来是用于调用obj的信号或者槽。
接下来是newInstance():
//qobjectdefs.h
struct Q_CORE_EXPORT QMetaObject
{
//...
QObject *newInstance(/*...*/);
//...
}
它是用来调用构造函数的。
总结
热身就到这里,总结一下,Q_OBJECT宏用于启用元对象特性,其中staticMetaObject的初始化在moc_xxx.cpp中进行,moc_xxx.cpp包含了许多“元成员”的字符串信息和实现。QMetaObject是元对象系统的关键成员,提供了元信息的接口。
Qt源码解析——元对象系统热身的更多相关文章
- QT源码解析(一) QT创建窗口程序、消息循环和WinMain函数
QT源码解析(一) QT创建窗口程序.消息循环和WinMain函数 分类: QT2009-10-28 13:33 17695人阅读 评论(13) 收藏 举报 qtapplicationwindowse ...
- QT源码解析(七)Qt创建窗体的过程,作者“ tingsking18 ”(真正的创建QPushButton是在show()方法中,show()方法又调用了setVisible方法)
前言:分析Qt的代码也有一段时间了,以前在进行QT源码解析的时候总是使用ue,一个函数名在QTDIR/src目录下反复的查找,然后分析函数之间的调用关系,效率实在是太低了,最近总结出一个更简便的方法, ...
- Qt源码解析之-从PIMPL机制到d指针
一.PIMPL机制 PIMPL ,即Private Implementation,作用是,实现 私有化,力图使得头文件对改变不透明,以达到解耦的目的 pimpl 用法背后的思想是把客户与所有关于类的私 ...
- Qt源码阅读(三) 对象树管理
对象树管理 个人经验总结,如有错误或遗漏,欢迎各位大佬指正 @ 目录 对象树管理 设置父对象的作用 设置父对象(setParent) 完整源码 片段分析 对象的删除 夹带私货时间 设置父对象的作用 众 ...
- jvm源码解析java对象头
认真学习过java的同学应该都知道,java对象由三个部分组成:对象头,实例数据,对齐填充,这三大部分扛起了java的大旗对象,实例数据其实就是我们对象中的数据,对齐填充是由于为了规则分配内存空间,j ...
- SuperSocket源码解析之配置系统
一 继承Net配置系统 Net应用程序配置机制跟程序集引用大致类似,均具有继承性,如iis几乎每个应用程序都会有一个Web.config,比如我们使用vs2012以上版本创建一个web应用程序会自带一 ...
- QT源码解析笔记
1. QT如何绘制控件的 QT的绘制控件在QStyleSheetStyle::DrawControl里面,这里会调用默认的QSS来绘制效果 2. 在设置一次QSS以后,将会触发polish事件,里面将 ...
- Qt笔记-const-虚函数-元对象系统
const与指针 摘自C++ Primer Plus (第五版) 中文版 const(常量): const变量的地址可以给指向const的指针,但不能指向常规类型的指针: const float a= ...
- underscore.js源码解析【对象】
// Object Functions // ---------------- // Keys in IE < 9 that won't be iterated by `for key in . ...
- 看懂Qt源代码-Qt源码的对象数据存储
第一次看Qt源代码的人都会被其代码所迷惑,经常会看到代码中的d_ptr成员.d_func(函数)和Q_DECLARE_PRIVATE等奇怪的宏,总是让人一头雾水,下面这篇文章转自http://www. ...
随机推荐
- C# DateTime 时间格式化
今天做任务的时候,数据库日期拼写需要 从凌晨到晚上最后一秒,但是传过来的日期数据是 当前的时间,下面是我尝试的解决方案. endTime.ToString("yyyy-MM-dd 23:59 ...
- 解密Prompt系列12. LLM Agent零微调范式 ReAct & Self Ask
前三章我们分别介绍了思维链的使用,原理和在小模型上的使用.这一章我们正式进入应用层面,聊聊如何把思维链和工具使用结合得到人工智能代理. 要回答我们为什么需要AI代理?代理可以解决哪些问题?可以有以下两 ...
- 干了这么多年C#,后悔没早点用这种“分页”,简单/高效/易维护
[前言] 干了这么多年C#,后悔没早点用这种"分页",简单/高效/易维护,比其它的分页方式强多了,不信你自己看. [正文] 支持.Net Core(2.0及以上)与.Net Fra ...
- 微服务集成redis并通过redis实现排行榜的功能
默认你已经看过我之前的教程了,并且拥有上个教程完成的项目, 之前的教程 https://www.cnblogs.com/leafstar/p/17638933.html 由于redis的安装网上教程很 ...
- 震惊!CSS 也能实现碰撞检测?
本文,我们将一起学习,使用纯 CSS,实现如下所示的动画效果: 上面的动画效果,非常有意思,核心有两点: 小球随机做 X.Y 方向的直线运动,并且能够实现碰撞到边界的时候,实现反弹效果 小球在碰撞边界 ...
- C#中的ConcurrentExclusiveSchedulerPair类
为什么使用ConcurrentExclusiveSchedulerPair? 现实生活中的例子是一个停车场的入口和出口,多辆车可以同时进入和离开停车场,但是只有一个车辆可以进入或离开一次. 这时候就需 ...
- 精选版:用Java扩展Nginx(nginx-clojure 入门)
欢迎访问我的GitHub 这里分类和汇总了欣宸的全部原创(含配套源码):https://github.com/zq2599/blog_demos 本篇概览 今天咱们以Java程序员的视角,来聊聊如何用 ...
- 使用JAVA调用KRPANO加密XML
KRPano自带的命令行工具krpanotools可以加密XML,具体的参数说明如下语法: krpanotools32.exe encrypt [OPTIONS] inputfiles input ...
- 【krpano】淘宝buy+案例
这是一个类似淘宝buy+的案例,是基于krpano全景开发工具二次开发的全景视频.WebVR.360°环物.全景视频热点添加于一身的综合性案例.现在将案例上传网站供krpano技术人员和爱好者大家共同 ...
- 教育法学第九章单元测试MOOC
第九章单元测试 返回 本次得分为:100.00/100.00, 本次测试的提交时间为:2020-09-06, 如果你认为本次测试成绩不理想,你可以选择 再做一次 . 1 单选(5分) 作为教师最基本的 ...