前面我們說過,Qt 不是使用的“標準的” C++ 語言,而是對其進行了一定程度的“擴展”。這裏我們從Qt新增加的關鍵字就可以看出來:signals、slots 或者 emit。所以有人會覺得 Qt 的程序編譯速度慢,這主要是因爲在 Qt 將源代碼交給標準 C++ 編譯器,如 gcc 之前,需要事先將這些擴展的語法去除掉。完成這一操作的就是 moc。
moc 全稱是 Meta-Object Compiler,也就是“元對象編譯器”。Qt 程序在交由標準編譯器編譯之前,先要使用 moc 分析 C++ 源文件。如果它發現在一個頭文件中包含了宏 Q_OBJECT,則會生成另外一個 C++ 源文件。這個源文件中包含了 Q_OBJECT 宏的實現代碼。這個新的文件名字將會是原文件名前面加上 moc_ 構成。這個新的文件同樣將進入編譯系統,最終被鏈接到二進制代碼中去。因此我們可以知道,這個新的文件不是“替換”掉舊的文件,而是與原文件一起參與編譯。另外,我們還可以看出一點,moc 的執行是在預處理器之前。因爲預處理器執行之後,Q_OBJECT 宏就不存在了。
既然每個源文件都需要 moc 去處理,那麼我們在什麼時候調用了它呢?實際上,如果你使用 qmake 的話,這一步調用會在生成的 makefile 中展現出來。從本質上來說,qmake 不過是一個 makefile 生成器,因此,最終執行還是通過 make 完成的。
爲了查看 moc 生成的文件,我們使用一個很簡單的 cpp 來測試:
test.cpp
- class Test : public QObject
- {
- Q_OBJECT
- public:
- explicit Test(QObject *parent = 0);
- signals:
- public slots:
- };
這是一個空白的類,什麼都沒有實現。在經過編譯之後,我們會在輸出文件夾中找到 moc_test.cpp:
moc_test.cpp
- /****************************************************************************
- ** Meta object code from reading C++ file 'test.h'
- **
- ** Created: Thu Jul 22 13:06:45 2010
- ** by: The Qt Meta Object Compiler version 62 (Qt 4.6.3)
- **
- ** WARNING! All changes made in this file will be lost!
- *****************************************************************************/
- #include "../test.h"
- #if !defined(Q_MOC_OUTPUT_REVISION)
- #error "The header file 'test.h' doesn't include <QObject>."
- #elif Q_MOC_OUTPUT_REVISION != 62
- #error "This file was generated using the moc from 4.6.3. 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
- static const uint qt_meta_data_Test[] = {
- // content:
- 4, // 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
- };
- static const char qt_meta_stringdata_Test[] = {
- "Test\0"
- };
- const QMetaObject Test::staticMetaObject = {
- { &QObject::staticMetaObject, qt_meta_stringdata_Test,
- qt_meta_data_Test, 0 }
- };
- #ifdef Q_NO_DATA_RELOCATION
- const QMetaObject &Test::getStaticMetaObject() { return staticMetaObject; }
- #endif //Q_NO_DATA_RELOCATION
- const QMetaObject *Test::metaObject() const
- {
- return QObject::d_ptr->metaObject ? QObject::d_ptr->metaObject : &staticMetaObject;
- }
- void *Test::qt_metacast(const char *_clname)
- {
- if (!_clname) return 0;
- if (!strcmp(_clname, qt_meta_stringdata_Test))
- return static_cast<void*>(const_cast< Test*>(this));
- return QObject::qt_metacast(_clname);
- }
- int Test::qt_metacall(QMetaObject::Call _c, int _id, void **_a)
- {
- _id = QObject::qt_metacall(_c, _id, _a);
- if (_id < 0)
- return _id;
- return _id;
- }
- QT_END_MOC_NAMESPACE
可以看到,moc_test.cpp 裏面爲 Test 類增加了很多函數。然而,我們並沒有實際寫出這些函數,它是怎麼加入類的呢?別忘了,我們還有 Q_OBJECT 這個宏呢!在 qobjectdefs.h 裏面,找到 Q_OBJECT 宏的定義:
- #define Q_OBJECT \
- public: \
- Q_OBJECT_CHECK \
- static const QMetaObject staticMetaObject; \
- Q_OBJECT_GETSTATICMETAOBJECT \
- virtual const QMetaObject *metaObject() const; \
- virtual void *qt_metacast(const char *); \
- QT_TR_FUNCTIONS \
- virtual int qt_metacall(QMetaObject::Call, int, void **); \
- private:
這下了解了:正是對 Q_OBJECT 宏的展開,使我們的 Test 類擁有了這些多出來的屬性和函數。注意,QT_TR_FUNCTIONS 這個宏也是在這裏定義的。也就是說,如果你要使用 tr() 國際化,就必須使用 Q_OBJECT 宏,否則是沒有 tr() 函數的。這期間最重要的就是 virtual const QMetaObject *metaObject() const; 函數。這個函數返回 QMetaObject 元對象類的實例,通過它,你就獲得了 Qt 類的反射的能力:獲取本對象的類型之類,而這一切,都不需要 C++ 編譯器的 RTTI 支持。Qt 也提供了一個類似 C++ 的 dynamic_cast() 的函數 qobject_case(),而這一函數的實現也不需要 RTTI。另外,一個沒有定義 Q_OBJECT 宏的類與它最接近的父類是同一類型的。也就是說,如果 A 繼承了 QObject 並且定義了 Q_OBJECT,B 繼承了 A 但沒有定義 Q_OBJECT,C 繼承了 B,則 C 的 QMetaObject::className() 函數將返回 A,而不是本身的名字。因此,爲了避免這一問題,所有繼承了 QObject 的類都應該定義 Q_OBJECT 宏,不管你是不是使用信號槽。