1. 程式人生 > >Qt結構---Qt 手動生成moc 檔案

Qt結構---Qt 手動生成moc 檔案

1.問題來源

       Qt自己會先把用到Qt自己的東西編譯一遍,變成標準C++編譯器可以識別的東西。

       moc 全稱是 Meta-Object Compiler,也就是“元物件編譯器”。Qt 程式在交由標準編譯器編譯之前,先要使用 moc 分析 C++ 原始檔。如果它發現在一個頭檔案中包含了巨集 Q_OBJECT,則會生成另外一個 C++ 原始檔。這個原始檔中包含了 Q_OBJECT 巨集的實現程式碼。這個新的檔名字將會是原檔名前面加上 moc_ 構成。這個新的檔案同樣將進入編譯系統,最終被連結到二進位制程式碼中去。因此我們可以知道,這個新的檔案不是“替換”掉舊的檔案,而是與原檔案一起參與編譯。另外,我們還可以看出一點,moc 的執行是在前處理器之前。因為前處理器執行之後,Q_OBJECT 巨集就不存在了


 

                              moc是QT的預編譯器,用來處理程式碼中的slot,signal,emit,Q_OBJECT等

2. 一般當moc_xxx檔案生成後,不要去開啟,因為C++編譯器在開啟時會讀取,可能某些函式沒有關聯到相應的動態庫上而報錯

 

前面我們說過,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 巨集,不管你是不是使用訊號槽。

 

另外:

moc的功能數一數

1、處理Q_OBJECT巨集和signals/slots關鍵字,生成訊號和槽的底層程式碼

2、處理Q_PROPERTY()和Q_ENUM()生成property系統程式碼

3、處理Q_FLAGS()和Q_CLASSINFO()生成額外的類meta資訊

4、不需要moc處理的程式碼可以用預定義的巨集括起來,如下:

#ifndef Q_MOC_RUN

#endif

moc的限制數一數(太多了,眼花繚亂)

1、模板類不能使用訊號/槽機制

2、moc不擴充套件巨集,所以訊號和槽的定義不能使用巨集, 包括connect的時候也不能用巨集做訊號和槽的名字以及引數

3、從多個類派生時,QObject派生類必須放在第一個, 因為moc是這麼認為的…(比較流氓) 這也是我們前面的例子觸犯的天條

4、函式指標不能作為訊號或槽的引數, 因為其格式比較複雜,moc處理不了。 但可以用typedef把它定義成簡單的形式再使用。(這招可真夠絕的)

5、用列舉型別或typedef的型別做訊號和槽的引數時,必須fully qualified。 這個詞中文不知道怎麼翻譯才合適,簡單的說就是, 如果是在類裡定義的, 必須把類的路徑或者名稱空間的路徑都加上, 防止出現混淆。 如Qt::Alignment之類的,前面的Qt就是Alignment的qualifier, 必須加上,而且有幾級加幾級。

6、訊號和槽不能返回引用型別

7、signals和slots關鍵字區域只能放置訊號和槽的定義,不能放其它的如變數定義等