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源码解析——元对象系统热身的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 嗨,别着急做度量,平台工程需要先从“数据
- 下一篇: 一个操作系统的设计与实现——第1章 什么