Qt核心剖析: moc

前面我們說過,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

  1. class Test : public QObject 
  2.  Q_OBJECT 
  3. public
  4.  explicit Test(QObject *parent = 0); 
  5. signals: 
  6. public slots: 
  7. }; 

這是一個空白的類,什麼都沒有實現。在經過編譯之後,我們會在輸出文件夾中找到 moc_test.cpp:

moc_test.cpp

  1. /**************************************************************************** 
  2. ** Meta object code from reading C++ file 'test.h' 
  3. ** 
  4. ** Created: Thu Jul 22 13:06:45 2010 
  5. **      by: The Qt Meta Object Compiler version 62 (Qt 4.6.3) 
  6. ** 
  7. ** WARNING! All changes made in this file will be lost! 
  8. *****************************************************************************/ 
  9.  
  10. #include "../test.h" 
  11. #if !defined(Q_MOC_OUTPUT_REVISION) 
  12. #error "The header file 'test.h' doesn't include <QObject>." 
  13. #elif Q_MOC_OUTPUT_REVISION != 62 
  14. #error "This file was generated using the moc from 4.6.3. It" 
  15. #error "cannot be used with the include files from this version of Qt." 
  16. #error "(The moc has changed too much.)" 
  17. #endif 
  18.  
  19. QT_BEGIN_MOC_NAMESPACE 
  20. static const uint qt_meta_data_Test[] = { 
  21.  
  22.  // content: 
  23.        4,       // revision 
  24.        0,       // classname 
  25.        0,    0, // classinfo 
  26.        0,    0, // methods 
  27.        0,    0, // properties 
  28.        0,    0, // enums/sets 
  29.        0,    0, // constructors 
  30.        0,       // flags 
  31.        0,       // signalCount 
  32.  
  33.        0        // eod 
  34. }; 
  35.  
  36. static const char qt_meta_stringdata_Test[] = { 
  37.     "Test\0" 
  38. }; 
  39.  
  40. const QMetaObject Test::staticMetaObject = { 
  41.     { &QObject::staticMetaObject, qt_meta_stringdata_Test, 
  42.       qt_meta_data_Test, 0 } 
  43. }; 
  44.  
  45. #ifdef Q_NO_DATA_RELOCATION 
  46. const QMetaObject &Test::getStaticMetaObject() { return staticMetaObject; } 
  47. #endif //Q_NO_DATA_RELOCATION 
  48.  
  49. const QMetaObject *Test::metaObject() const 
  50.     return QObject::d_ptr->metaObject ? QObject::d_ptr->metaObject : &staticMetaObject; 
  51.  
  52. void *Test::qt_metacast(const char *_clname) 
  53.     if (!_clname) return 0; 
  54.     if (!strcmp(_clname, qt_meta_stringdata_Test)) 
  55.         return static_cast<void*>(const_cast< Test*>(this)); 
  56.     return QObject::qt_metacast(_clname); 
  57.  
  58. int Test::qt_metacall(QMetaObject::Call _c, int _id, void **_a) 
  59.     _id = QObject::qt_metacall(_c, _id, _a); 
  60.     if (_id < 0) 
  61.         return _id; 
  62.     return _id; 
  63. QT_END_MOC_NAMESPACE 

可以看到,moc_test.cpp 裏面爲 Test 類增加了很多函數。然而,我們並沒有實際寫出這些函數,它是怎麼加入類的呢?別忘了,我們還有 Q_OBJECT 這個宏呢!在 qobjectdefs.h 裏面,找到 Q_OBJECT 宏的定義:

  1. #define Q_OBJECT \ 
  2. public: \ 
  3.     Q_OBJECT_CHECK \ 
  4.     static const QMetaObject staticMetaObject; \ 
  5.     Q_OBJECT_GETSTATICMETAOBJECT \ 
  6.     virtual const QMetaObject *metaObject() const; \ 
  7.     virtual void *qt_metacast(const char *); \ 
  8.     QT_TR_FUNCTIONS \ 
  9.     virtual int qt_metacall(QMetaObject::Call, intvoid **); \ 
  10. 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 宏,不管你是不是使用信號槽。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章