目錄
先說RTTI
再說QMeta Object System
關于Q_OBJECT
這篇文章我打算研究一下QMetaObject System,也就是Qt自己構建起來的元對象系統。
先說RTTI
啥是RTTI?這是C++編程里的一個常見術語,全稱是:運行階段類型識別(Runtime Type Identification),關于RTTI如何在原生C++中使用不是我們這里的重點,但是可以明確的一點是——跟編譯器實現密切相關,意味著可移植性略差。很多類庫已經為其類對象提供了實現這種功能的方式,但由于C++內部并不支持,因此各個廠商的機制通常互不兼容
即使編譯器支持RTTI,就目前而言,原生的支持仍然十分的不足。我們沒有辦法完全知道例如類的名字、有哪些父類、有哪些成員變量、有哪些成員函數、哪些是public的、哪些是private的、哪些是protected的等等。
有時候一個工程項目可能包含成千上萬個類,完整的保存這些信息將會消耗大量的內存資源。為了節省內存,C++標準約定typeid只能返回類名。因此,僅靠dynamic_cast和typeid兩個關鍵字提供的類型信息實在有限。更何況,他還會造成大量的系統開銷,這也是為什么這個特性并沒有被完整的納入標準。
關于RTTI,可以參看:【C++】RTTI有什么用?怎么用? - 知乎 (zhihu.com)以備快速的復習
再說QMeta Object System
下面我們聊聊,既然大家都各做各的,Qt框架作為C++早期時代就存在的框架,自然實現了自己的一套源系統機制。
這個元對象機制不光實現了類似于RTTI那樣的動態查看類信息的作用,還擴展出了信號與槽的機制(這個就是大名鼎鼎的信號與槽)
Qt's meta-object system provides the signals and slots mechanism for inter-object communication, run-time type information, and the dynamic property system.
這個對象說一千道一萬,三個核心
The QObject class provides a base class for objects that can take advantage of the meta-object system.
The Q_OBJECT macro inside the private section of the class declaration is used to enable meta-object features, such as dynamic properties, signals, and slots.
The Meta-Object Compiler (
moc
) supplies each QObject subclass with the necessary code to implement meta-object features.
也就是說:
QObject這個類提供了整個元對象系統的一個根基
Q_Object宏這是讓一個類可以使用RTTI,信號與槽機制(這就是為什么一些奇奇怪怪的Undefined Reference可以依賴這個解決,下一次發現使用信號與槽機制的時候編譯炸了排查的時候考慮這個事情)
Moc則是更加進一步的提供了元對象系統的實現的保證(嘿!想一下你編譯的時候是不是需要有moc文件,他就是Meta-object Compilers,元系統編譯器產生的)
換而言之,Qt的元對象并不完全直接依賴于語言,而是借助了外來的Moc Tools預先掃描源文件,生成自己的元對象文件,在最后納入編譯階段合并進來
當然,我們的元對象系統還可以做更多的事情:
QObject::metaObject作為一個靜態方法返回關聯的metaObject(也就是返回當前對象的元對象系統的那部分)
QMetaObject::className可以進一步返回運行時的對象名稱,而這個是基于標準實現而不是編譯器實現的,你知道的,一致性!
QObject::inherits則是檢查一個類是不是位于Qt的繼承樹上
QObject::tr則是保證了我們的對象名稱滿足國際化
QObject::setProperty和QObject::property讓我們的對象擁有了屬性這個概念!
QMetaObject::newInstance()以一種工廠方法構造了這個類的一個新實例
我們知道dynamic_cast可以用來轉化父類子類,而且轉化成不成功全看是不是真的如此。這里我們入鄉隨俗,使用qobject_cast來檢查Qt元對象的繼承問題。
我隨手寫一個簡單的demo:
#include <QWidget> #include <QMainWindow> #include <QApplication> class MyObject : public QWidget{}; ? ? int main(int argc, char *argv[]) {QApplication app(argc, argv); // Import For QWidgets enableQObject* obj = new MyObject; ?QWidget* widget = qobject_cast<QWidget*>(obj);if(widget){qDebug() << "Is Widget";} ?QMainWindow* window = qobject_cast<QMainWindow*>(obj);if(window){qDebug() << "Is Window";} ?delete obj; }
值得注意的是,如果我們希望納入一個類進入QObject的繼承對象樹中,務!必!在私有區域聲明一個Q_OBJECT。(當然要是想要直接暴露給外面的話放在public也不是不行)
手擼了一個例子
#include <QWidget> #include <QMainWindow> #include <QApplication> #define IS_USE_QOBJ_MACRO 0 ? class MyObject : public QWidget{ #if IS_USE_QOBJ_MACROQ_OBJECT #endif public:QString _ClassName(){return this->metaObject()->className();} }; ? ? int main(int argc, char *argv[]) {QApplication app(argc, argv);QObject* obj = new MyObject; ?QWidget* widget = qobject_cast<QWidget*>(obj);if(widget){qDebug() << "Is Widget";} ?QMainWindow* window = qobject_cast<QMainWindow*>(obj);if(window){qDebug() << "Is Window";} ?qDebug() << dynamic_cast<MyObject*>(obj)->_ClassName(); ?delete obj; } ? #if IS_USE_QOBJ_MACRO #include "main.moc" // 一個Demo,我們直接自己引入編譯好的main.moc #endif
你可以留意到,添加了QOBJECT宏的類的行為表現的并不一致。
#define IS_USE_QOBJ_MACRO 0 Is Widget "QWidget"
#define IS_USE_QOBJ_MACRO 1 Is Widget "MyObject"
由此,如果想要讓元對象系統正確的工作,請務必使用Q_OBJECT
關于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 { explicit QPrivateSignal() = default; }; \QT_ANNOTATE_CLASS(qt_qobject, "")
這就是我們的源碼。
可以看到他實際上就是向我們的類內嵌入了工作函數。這就是為什么需要添加一些類。
當然還有MOC編譯器的使用,以及還有屬性系統,挖個坑,有空講。