目录
[3.Qt 元对象模型QMetaObject](#3.Qt 元对象模型QMetaObject)
[3.4.枚举信息 enumerator](#3.4.枚举信息 enumerator)
[4.MOS(Meta Object System)示例](#4.MOS(Meta Object System)示例)
1.元对象系统的构成
-
QObject为所有需要利用元对象系统的对象提供一个基类。也就是说只有继承QObject类才能使用MOS。
-
Q_OBJECT宏,在类的声明体内定义在每一个类的私有数据段,用来启用元对象功能,比如,动态属性、信号和槽
-
元对象编译器moc(Meta Object Compiler),如果一个头文件中包含Q_OBJECT宏定义,moc就会将该文件编译成C++源文件。该原文件包含了Q_OBJECT的实现代码,也被编译,最终链接到这个类的二进制代码中。因为它也是这个类的一部分。通常,这个新的C++原文件会再以前的C++原文件前面加上moc_作为新的文件名。
如下图所示:

- QObject定义了从一个QObject对象访问meta-object功能的接口,Q_OBJECT宏用来告诉编译器该类需要激活meta-object功能,编译器在扫描一个源文件时,如果发现类的声明中有这个宏,就会生成一些代码来为支持meta-object功能------主要是生成该类对应MetaObject类以及对QObject的函数override(重载)。
2.QObject和QMetaObject的关系
Q_OBJECT宏的定义如下:
cpp
#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, "")
staticMetaObject是静态常量,metaObject()是获取该对象指针的方法。所有QObject的对象都会共享staticMetaObject变量,靠它完成所有信号和槽的功能。
QMetaObject包含了QObject的所谓的元数据,也就是QObject信息的一些描述信息:除了类型信息外,还包含QT中特有的signal&slot信息。
cpp
virtual QObject::metaObject();
该方法返回一个QObject对应的metaObject对象,如上文所说,如果一个类的声明中包含了Q_OBJECT宏,编译器会生成代码来实现这个类对应的QMetaObject类,并重载QObject::metaObject()方法来返回这个QMetaObject类的实例引用。这样当通过QObject类型的引用调用metaObejct方法时,返回的是这个引用的所指的真实对象的metaobject。
如果一个类从QObject派生,确没有声明Q_OBJECT宏,那么这个类的metaobject对象不会被生成,这样这个类所声明的signal slot都不能使用,而这个类实例调用metaObject()返回的就是其父类的metaobject对象,这样导致的后果就是你从这个类实例获得的元数据其实都是父类的数据,这显然给你的代码埋下隐患。因此如果一个类从QOBject派生,它都应该声明Q_OBJECT宏,不管这个类有没有定义signal&slot和Property。
这样每个QObject类都有一个对应的QMetaObject类,形成一个平行的类型层次。
3.Qt 元对象模型QMetaObject
笔者环境:win平台vs2019,Qt 版本5.12.12,以下源码分析都是基于这个版本
3.1.基本信息
Qt 元对象声明位于.\5.12.12\Src\qtbase\src\corelib\kernel\qobjectdefs.h
中,位于代码的337行处,我们可以看到其中存放的元数据的结构:
cpp
struct Q_CORE_EXPORT QMetaObject
{
const char *className() const;
const QMetaObject *superClass() const;
。。。
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类所定义的所有数据成员,就是这些数据成员记录了所有的signal、slot、property、class information、enumerator等相关信息。
const QMetaObject *superdata,该变量指向与之对应的QObject类的父类对象,或者是祖先类的QMetaObject对象。每一个QObject类或者其派生类可能有一个父类或者父类的父类。那么superdata就是指向与其最接近的祖先类中的QMetaObject对象。如果没有父类,那么该变量就是一个NULL指针。
3.2.类信息classinfo
提供额外的类信息-名值对。用户可以在类的生命中以Q_CLASSINFO(name,value)的方式添加。
cpp
int classInfoOffset() const;
int classInfoCount() const;
int indexOfClassInfo(const char *name) const;
QMetaClassInfo classInfo(int index) const;
示例如下:
cpp
class MyClass : public QObject
{
Q_OBJECT
Q_CLASSINFO("author", "xiaohe")
Q_CLASSINFO("url", "http://www.baidu.com/")
public:
...
};
3.3.类构造函数constructor
提供该类的构造方法信息
cpp
int constructorCount() const;
int indexOfConstructor(const char *constructor) const;
QMetaMethod constructor(int index) const;
3.4.枚举信息 enumerator
描述该类声明体内所包含的枚举类型信息
cpp
int enumeratorOffset() const;
int enumeratorCount() const;
int indexOfEnumerator(const char *name) const;
QMetaEnum enumerator(int index) const;
3.5.类方法method
描述类中所包含方法信息:包括property,signal,slot等。
cpp
int methodOffset() const;
int methodCount() const;
int indexOfMethod(const char *method) const;
int indexOfSignal(const char *signal) const;
int indexOfSlot(const char *slot) const;
QMetaMethod method(int index) const;
3.6.类属性peoproty
描述类的属性信息
cpp
int propertyOffset() const;
int propertyCount() const;
int indexOfProperty(const char *name) const;
QMetaProperty property(int index) const;
注意:对于类里面定义的函数,构造函数,枚举,只有加上一些宏才表示你希望为方法提供meta信息。比如 Q_ENUMS用来注册宏,Q_INVACABLE用来注册方法(包括构造函数)。Qt这么设计的原因应该是避免meta信息的臃肿。
4.MOS(Meta Object System)示例
TestObject继承QObject,定义了两个Property:PropertyA和PropertyB;两个classinfo:Author,version;一个枚举:TestEnum。
CTestObject.h
cpp
#pragma once
#include <qobject.h>
class TestObject : public QObject
{
Q_OBJECT
Q_PROPERTY(QString propertyA READ getPropertyA WRITE setPropertyA RESET resetPropertyA DESIGNABLE true SCRIPTABLE true STORED true USER false)
Q_PROPERTY(QString propertyB READ getPropertyB WRITE setPropertyB RESET resetPropertyB)
Q_CLASSINFO("Author", "Liuyanghe")
Q_CLASSINFO("Version", "TestObjectV1.0")
Q_ENUMS(TestEnum)
public:
enum TestEnum {
EnumValueA,
EnumValueB
};
public:
TestObject() {}
Q_INVOKABLE TestObject(const QString& a, const QString& b) : m_A(a), m_B(b) {}
QString getPropertyA() const {
return m_A;
}
void setPropertyA(const QString& newValue) {
if (newValue == m_A)
return;
m_A = newValue;
emit propertyAChanged();
}
void resetPropertyA() {
m_A = "";
}
QString getPropertyB() const {
return m_B;
}
void setPropertyB(const QString& newValue) {
if (newValue == m_B)
return;
m_B = newValue;
emit propertyBChanged();
}
void resetPropertyB() {
m_B = "";
}
Q_INVOKABLE int doWork() {
return 1;
}
signals:
void clicked();
void pressed();
void propertyAChanged();
void propertyBChanged();
public slots:
void onEventA(const QString&);
void onEventB(int);
private:
QString m_A;
QString m_B;
};
TestObject的moc文件moc_CTestObject.cpp:
cpp
/****************************************************************************
** Meta object code from reading C++ file 'CTestObject.h'
**
** Created by: The Qt Meta Object Compiler version 67 (Qt 5.12.12)
**
** WARNING! All changes made in this file will be lost!
*****************************************************************************/
#include "../../../../CTestObject.h"
#include <QtCore/qbytearray.h>
#include <QtCore/qmetatype.h>
#if !defined(Q_MOC_OUTPUT_REVISION)
#error "The header file 'CTestObject.h' doesn't include <QObject>."
#elif Q_MOC_OUTPUT_REVISION != 67
#error "This file was generated using the moc from 5.12.12. It"
#error "cannot be used with the include files from this version of Qt."
#error "(The moc has changed too much.)"
#endif
QT_BEGIN_MOC_NAMESPACE
QT_WARNING_PUSH
QT_WARNING_DISABLE_DEPRECATED
struct qt_meta_stringdata_TestObject_t {
QByteArrayData data[20];
char stringdata0[182];
};
#define QT_MOC_LITERAL(idx, ofs, len) \
Q_STATIC_BYTE_ARRAY_DATA_HEADER_INITIALIZER_WITH_OFFSET(len, \
qptrdiff(offsetof(qt_meta_stringdata_TestObject_t, stringdata0) + ofs \
- idx * sizeof(QByteArrayData)) \
)
static const qt_meta_stringdata_TestObject_t qt_meta_stringdata_TestObject = {
{
QT_MOC_LITERAL(0, 0, 10), // "TestObject"
QT_MOC_LITERAL(1, 11, 6), // "Author"
QT_MOC_LITERAL(2, 18, 9), // "Liuyanghe"
QT_MOC_LITERAL(3, 28, 7), // "Version"
QT_MOC_LITERAL(4, 36, 14), // "TestObjectV1.0"
QT_MOC_LITERAL(5, 51, 7), // "clicked"
QT_MOC_LITERAL(6, 59, 0), // ""
QT_MOC_LITERAL(7, 60, 7), // "pressed"
QT_MOC_LITERAL(8, 68, 16), // "propertyAChanged"
QT_MOC_LITERAL(9, 85, 16), // "propertyBChanged"
QT_MOC_LITERAL(10, 102, 8), // "onEventA"
QT_MOC_LITERAL(11, 111, 8), // "onEventB"
QT_MOC_LITERAL(12, 120, 6), // "doWork"
QT_MOC_LITERAL(13, 127, 1), // "a"
QT_MOC_LITERAL(14, 129, 1), // "b"
QT_MOC_LITERAL(15, 131, 9), // "propertyA"
QT_MOC_LITERAL(16, 141, 9), // "propertyB"
QT_MOC_LITERAL(17, 151, 8), // "TestEnum"
QT_MOC_LITERAL(18, 160, 10), // "EnumValueA"
QT_MOC_LITERAL(19, 171, 10) // "EnumValueB"
},
"TestObject\0Author\0Liuyanghe\0Version\0"
"TestObjectV1.0\0clicked\0\0pressed\0"
"propertyAChanged\0propertyBChanged\0"
"onEventA\0onEventB\0doWork\0a\0b\0propertyA\0"
"propertyB\0TestEnum\0EnumValueA\0EnumValueB"
};
#undef QT_MOC_LITERAL
static const uint qt_meta_data_TestObject[] = {
// content:
8, // revision
0, // classname
2, 14, // classinfo
7, 18, // methods
2, 69, // properties
1, 75, // enums/sets
1, 84, // constructors
0, // flags
4, // signalCount
// classinfo: key, value
1, 2,
3, 4,
// signals: name, argc, parameters, tag, flags
5, 0, 53, 6, 0x06 /* Public */,
7, 0, 54, 6, 0x06 /* Public */,
8, 0, 55, 6, 0x06 /* Public */,
9, 0, 56, 6, 0x06 /* Public */,
// slots: name, argc, parameters, tag, flags
10, 1, 57, 6, 0x0a /* Public */,
11, 1, 60, 6, 0x0a /* Public */,
// methods: name, argc, parameters, tag, flags
12, 0, 63, 6, 0x02 /* Public */,
// signals: parameters
QMetaType::Void,
QMetaType::Void,
QMetaType::Void,
QMetaType::Void,
// slots: parameters
QMetaType::Void, QMetaType::QString, 6,
QMetaType::Void, QMetaType::Int, 6,
// methods: parameters
QMetaType::Int,
// constructors: parameters
0x80000000 | 6, QMetaType::QString, QMetaType::QString, 13, 14,
// properties: name, type, flags
15, QMetaType::QString, 0x00095107,
16, QMetaType::QString, 0x00095107,
// enums: name, alias, flags, count, data
17, 17, 0x0, 2, 80,
// enum data: key, value
18, uint(TestObject::EnumValueA),
19, uint(TestObject::EnumValueB),
// constructors: name, argc, parameters, tag, flags
0, 2, 64, 6, 0x0e /* Public */,
0 // eod
};
void TestObject::qt_static_metacall(QObject *_o, QMetaObject::Call _c, int _id, void **_a)
{
if (_c == QMetaObject::CreateInstance) {
switch (_id) {
case 0: { TestObject *_r = new TestObject((*reinterpret_cast< const QString(*)>(_a[1])),(*reinterpret_cast< const QString(*)>(_a[2])));
if (_a[0]) *reinterpret_cast<QObject**>(_a[0]) = _r; } break;
default: break;
}
} else if (_c == QMetaObject::InvokeMetaMethod) {
auto *_t = static_cast<TestObject *>(_o);
Q_UNUSED(_t)
switch (_id) {
case 0: _t->clicked(); break;
case 1: _t->pressed(); break;
case 2: _t->propertyAChanged(); break;
case 3: _t->propertyBChanged(); break;
case 4: _t->onEventA((*reinterpret_cast< const QString(*)>(_a[1]))); break;
case 5: _t->onEventB((*reinterpret_cast< int(*)>(_a[1]))); break;
case 6: { int _r = _t->doWork();
if (_a[0]) *reinterpret_cast< int*>(_a[0]) = std::move(_r); } break;
default: ;
}
} else if (_c == QMetaObject::IndexOfMethod) {
int *result = reinterpret_cast<int *>(_a[0]);
{
using _t = void (TestObject::*)();
if (*reinterpret_cast<_t *>(_a[1]) == static_cast<_t>(&TestObject::clicked)) {
*result = 0;
return;
}
}
{
using _t = void (TestObject::*)();
if (*reinterpret_cast<_t *>(_a[1]) == static_cast<_t>(&TestObject::pressed)) {
*result = 1;
return;
}
}
{
using _t = void (TestObject::*)();
if (*reinterpret_cast<_t *>(_a[1]) == static_cast<_t>(&TestObject::propertyAChanged)) {
*result = 2;
return;
}
}
{
using _t = void (TestObject::*)();
if (*reinterpret_cast<_t *>(_a[1]) == static_cast<_t>(&TestObject::propertyBChanged)) {
*result = 3;
return;
}
}
}
#ifndef QT_NO_PROPERTIES
else if (_c == QMetaObject::ReadProperty) {
auto *_t = static_cast<TestObject *>(_o);
Q_UNUSED(_t)
void *_v = _a[0];
switch (_id) {
case 0: *reinterpret_cast< QString*>(_v) = _t->getPropertyA(); break;
case 1: *reinterpret_cast< QString*>(_v) = _t->getPropertyB(); break;
default: break;
}
} else if (_c == QMetaObject::WriteProperty) {
auto *_t = static_cast<TestObject *>(_o);
Q_UNUSED(_t)
void *_v = _a[0];
switch (_id) {
case 0: _t->setPropertyA(*reinterpret_cast< QString*>(_v)); break;
case 1: _t->setPropertyB(*reinterpret_cast< QString*>(_v)); break;
default: break;
}
} else if (_c == QMetaObject::ResetProperty) {
TestObject *_t = static_cast<TestObject *>(_o);
Q_UNUSED(_t)
switch (_id) {
case 0: _t->resetPropertyA(); break;
case 1: _t->resetPropertyB(); break;
default: break;
}
}
#endif // QT_NO_PROPERTIES
}
QT_INIT_METAOBJECT const QMetaObject TestObject::staticMetaObject = { {
&QObject::staticMetaObject,
qt_meta_stringdata_TestObject.data,
qt_meta_data_TestObject,
qt_static_metacall,
nullptr,
nullptr
} };
const QMetaObject *TestObject::metaObject() const
{
return QObject::d_ptr->metaObject ? QObject::d_ptr->dynamicMetaObject() : &staticMetaObject;
}
void *TestObject::qt_metacast(const char *_clname)
{
if (!_clname) return nullptr;
if (!strcmp(_clname, qt_meta_stringdata_TestObject.stringdata0))
return static_cast<void*>(this);
return QObject::qt_metacast(_clname);
}
int TestObject::qt_metacall(QMetaObject::Call _c, int _id, void **_a)
{
_id = QObject::qt_metacall(_c, _id, _a);
if (_id < 0)
return _id;
if (_c == QMetaObject::InvokeMetaMethod) {
if (_id < 7)
qt_static_metacall(this, _c, _id, _a);
_id -= 7;
} else if (_c == QMetaObject::RegisterMethodArgumentMetaType) {
if (_id < 7)
*reinterpret_cast<int*>(_a[0]) = -1;
_id -= 7;
}
#ifndef QT_NO_PROPERTIES
else if (_c == QMetaObject::ReadProperty || _c == QMetaObject::WriteProperty
|| _c == QMetaObject::ResetProperty || _c == QMetaObject::RegisterPropertyMetaType) {
qt_static_metacall(this, _c, _id, _a);
_id -= 2;
} else if (_c == QMetaObject::QueryPropertyDesignable) {
_id -= 2;
} else if (_c == QMetaObject::QueryPropertyScriptable) {
_id -= 2;
} else if (_c == QMetaObject::QueryPropertyStored) {
_id -= 2;
} else if (_c == QMetaObject::QueryPropertyEditable) {
_id -= 2;
} else if (_c == QMetaObject::QueryPropertyUser) {
_id -= 2;
}
#endif // QT_NO_PROPERTIES
return _id;
}
// SIGNAL 0
void TestObject::clicked()
{
QMetaObject::activate(this, &staticMetaObject, 0, nullptr);
}
// SIGNAL 1
void TestObject::pressed()
{
QMetaObject::activate(this, &staticMetaObject, 1, nullptr);
}
// SIGNAL 2
void TestObject::propertyAChanged()
{
QMetaObject::activate(this, &staticMetaObject, 2, nullptr);
}
// SIGNAL 3
void TestObject::propertyBChanged()
{
QMetaObject::activate(this, &staticMetaObject, 3, nullptr);
}
QT_WARNING_POP
QT_END_MOC_NAMESPACE
QMetaObjectPrivate
-
qt_meta_data_TestObject:定义的正是QMetaObject::d.data指向的信息块。
-
qt_meta_stringdata_TestObject:定义的是QMetaObject::d.stringdata指向的信息块。
-
const QMetaObject TestObject::staticMetaObject:定义TestObject类的MetaObject实例,从中可以看出QMetaObject各个字段是如何被赋值的。
-
const QMetaObject *TestObject::metaObject() const:重写了QObject::metaObject函数,返回上述的MetaObject实例指针。
-
TestObject::qt_metacall()是重写QObject的方法,依据传入的参数来调用signal&slot或访问property,动态方法调用属性访问正是依赖于这个方法。
6)TestObject::clicked()和TestObject::pressed()正是对两个signal的实现,可见,signal其实就是一种方法,只不过这种方法由qt meta system来实现,不用我们自己实现。
TestObject类的所有meta信息就存储在 qt_meta_data_TestObject和qt_meta_stringdata_TestObject这两个静态数据中。 QMetaObject的接口的实现正是基于这两块数据。下面就对这两个数据进行分块说明。
static const uint qt_meta_data_TestObject[] = {
// content:
8, // revision
0, // classname
2, 14, // classinfo
7, 18, // methods
2, 69, // properties
1, 75, // enums/sets
1, 84, // constructors
0, // flags
4, // signalCount
//...
}
这块数据可以被看做meta信息的头部,正好和QMetaObjectPrivate数据结构相对应,在QMetaObject的实现中,正是将这块数据映射为QMetaObjectPrivate进行使用的。
QMetaObjectPrivate是QMetaObject的私有实现类,其数据定义部分如下(见头文件qmetaobject_p.h)。该数据结构全是int类型,一些是直接的int型信息,比如classInfoCount、
methodCount等,还有一些是用于在QMetaObject的stringdata和data内存块中定位信息的索引值。
cpp
struct QMetaObjectPrivate
{
// revision 7 is Qt 5.0 everything lower is not supported
// revision 8 is Qt 5.12: It adds the enum name to QMetaEnum
enum { OutputRevision = 8 }; // Used by moc, qmetaobjectbuilder and qdbus
int revision;
int className;
int classInfoCount, classInfoData;
int methodCount, methodData;
int propertyCount, propertyData;
int enumeratorCount, enumeratorData;
int constructorCount, constructorData;
int flags;
int signalCount;
static inline const QMetaObjectPrivate *get(const QMetaObject *metaobject)
{ return reinterpret_cast<const QMetaObjectPrivate*>(metaobject->d.data); }
//...
};
第一行数据"8":版本号;
第二行数据"0":类名,该值是qt_meta_stringdata_TestObject的索引,qt_meta_stringdata_TestObject[0](QT_MOC_LITERAL(0, 0, 10), "TestObject" 这个字符串不正是类名"TestObject"吗。
第三行数据"2,14",第一个表明有2个classinfo被定义,第二个是说具体的 classinfo信息在qt_meta_data_TestObject中的索引,

qt_meta_data_TestObject[14]的位置两个 classinfo名值对的定义;这两对键值1和2、3和4分别
qt_meta_stringdata_TestObject的索引,找到:

这4个值正是那两个键值对。
第四行数据"7,18",指明method的信息,7是指方法的个数;18是指方法(包括信号、槽、Q_INVOKABLE标注的函数)开始的位置:

信号signals在qt_meta_stringdata_TestObject的索引是5、7、8、9,找到位置如下图所示:

5, 0, 53, 6, 0x06 /* Public */,是定义信号clicked,其它类似
槽slots在qt_meta_stringdata_TestObject的索引是10、11,找到位置如下图所示:

Q_INVOKABLE标注的函数在qt_meta_stringdata_TestObject的索引是12,找到位置如下图所示:

第五行数据"2,69",指明属性properties的信息,2是指属性的个数;69是指属性在qt_meta_data_TestObject索引开始的位置:

开始位置的15和16正是qt_meta_stringdata_TestObject的索引,找到位置正是类属性:

第六行数据"1,75",指明enum信息信息,1是指enun信息的个数;75是指enum信息在qt_meta_data_TestObject索引开始的位置:

开始位置17正是enum名称在qt_meta_data_TestObject索引开始的位置:

其它分析类似
第七行数据"1,84",指明类声明Q_INVOKABLE的构造函数信息,1是指构造函数个数;84是指构造函数在qt_meta_data_TestObject索引开始的位置:

0, 2, 64, 6, 0x0e /* Public */中的
0是指构造函数名称在qt_meta_stringdata_TestObject[0]
2是指这个构造函数的参数为两个
64是指构造函数的具体参数在qt_meta_data_TestObject[64]:

上图中的13和14是参数名的位置qt_meta_stringdata_TestObject[13]、qt_meta_stringdata_TestObject[14]:

cpp
static const char qt_meta_stringdata_TestObject[] = {
//这块数据就是meta信息所需的字符串。是一个字符串的序列。
"TestObject\0Author\0Liuyanghe\0Version\0"
"TestObjectV1.0\0clicked\0\0pressed\0"
"propertyAChanged\0propertyBChanged\0"
"onEventA\0onEventB\0doWork\0a\0b\0propertyA\0"
"propertyB\0TestEnum\0EnumValueA\0EnumValueB"
};
5.总结
Qt 中的元对象系统,简单的可以分为以下几步:
1.在继承 QObject 的类中使用 Q_OBJECT 宏,该宏定义了元对象和相关的方法
2.进行 C++ 编译前,Qt 会运行 moc,解析带有 Q_OBJECT 宏的相关类的信息,生成moc文件,得到元数据并构造元对象
3.将生成的文件和源文件一起编译
Qt 元对象系统通过信号与槽机制、运行时类型信息、动态属性系统和国际化支持等功能,极大地增强了 C++ 的功能,使开发者能够更高效地构建复杂的应用程序。它不仅简化了对象间的通信,还提供了强大的运行时反射能力,是 Qt 框架不可或缺的一部分。