Qt的元对象系统提供了信号和槽机制(用于对象间的通信)、运行时类型信息和动态属性系统。
元对象系统基于三个要素:
1、QObject 类为那些可以利用元对象系统的对象提供了一个基类 。
2、在类声明中使用Q_OBJECT 宏用于启用元对象特性,比如动态属性、信号和槽。
3、元对象编译器(moc)为每个QObject子类提供必要的代码来实现元对象特性。
moc
工具读取C++
源文件,如果发现一个或多个包含Q_OBJECT
宏的类声明,它会生成另一个C++
源文件,其中包含了这些类的每个元对象的代码。这个生成的源文件被#include进入类的源文件,更常见的是被编译并链接到类的实现中。
引入这个系统的主要原因是信号和槽机制,此外它还提供了一些额外功能:
1、QObject::metaObject()
返回与该类相关联的元对象。
2、QMetaObject::className()
在运行时以字符串形式返回类名,而无需通过 C++ 编译器提供本地运行时类型信息(RTTI)支持。
3、QObject::inherits()
函数返回一个对象是否是在 QObject 继承树内继承了指定类的实例。
4、QObject::tr()
和 QObject::trUtf8()
用于国际化的字符串翻译。
5、QObject::setProperty()
和 QObject::property()
动态地通过名称设置和获取属性。
6、QMetaObject::newInstance()
构造该类的新实例。
QMetaObject类包含有关Qt对象的元信息。每个在应用程序中使用的QObject子类都会创建一个QMetaObject实例,该实例存储了该QObject子类的所有元信息。此对象可通过QObject::metaObject()方法获得。
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
是个结构体,没有构造函数。忽略掉所有方法声明,只剩一个结构体变量。
QMetaObject
的变量会在moc_*.cpp文件中赋值:
//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
} };
|-------------------------------------------------|------------------------------|
| 变量名 | 值 |
| const QMetaObject *superdata | &QMainWindow::staticMetaObj |
| const QByteArrayData *stringdata | qt_meta_stringdata_MainWin |
| 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
。
做个小测试:
//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
*/
从这输出结果看,我们可以看出任何类的staticMetaObject
都持有了父类的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 sigTest();
重新编译,可以看到,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, 7), // "sigTest"
QT_MOC_LITERAL(2, 19, 0) // ""
},
"MainWindow\0sigTest\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
的其他成员。
//qobjectdefs.h
struct Q_CORE_EXPORT QMetaObject
{
class Connection;
//...
}
class Q_CORE_EXPORT QMetaObject::Connection {
//...
};
Connection
,QMetaObject
的内部类,它代表了信号-槽的连接,那就是说我们平常使用的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
等宏修饰的成员才具有"元能力"。
和信号-槽相关的接口:
//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(/*...*/);
//...
}
用于调用obj
的信号或者槽。
接下来是newInstance()
:
//qobjectdefs.h
struct Q_CORE_EXPORT QMetaObject
{
//...
QObject *newInstance(/*...*/);
//...
}
它是用来调用构造函数的。
觉得有帮助的话,打赏一下呗。。