天天看點

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

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

moc_test.cpp

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

這下了解了:正是對 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 宏,不管你是不是使用信号槽。

繼續閱讀