Qt C++ 如何實現自我反射?

比如

在c++中如何獲取所有已經定義了的命名形式為「XXX_on_YYY()」的所有函數(即所有名字里有_on_的函數)?

或者

有個字元串str="fun1",我如何通過這個字元串str的值來調用fun1()函數?

我在QT里看到這樣一個函數

QMetaObject::connectSlotsByName(MainWindow);

作用是把 實例 MainWindow中的名為"on_ objectname_signal( )"形式函數與相應 objectname_signal 聯繫起來。

Qt"s meta-object system provides a mechanism to automatically connect

signals and slots between QObject subclasses and their children. As long

as objects are defined with suitable object names, and slots follow a

simple naming convention, this connection can be performed at run-time

by the QMetaObject::connectSlotsByName() function.

於是我想看看他是怎麼通過類似反射的手段把這些名字形式為 "on_ objectname_signal( )" 函數找出來?他找出來的是字元串的函數名吧,那他又是如何把字元串對應為調用地址?

最後能否給出實現這種「反射」的簡單例子?


元對象編譯器moc的作用:

Qt 使用的不是標準的C++ 語言,而是對其進行了一定程度的「擴充」。

有人會覺得Qt 的程序編譯速度慢,這主要是因為在 Qt 將源代碼交給標準 C++ 編譯器之前,需要事先將QT其特有的功能(包括這裡的"自我反射")轉換為標準c++。

而完成這一操作的工具就是所謂的moc。moc 全稱是 Meta-Object Compiler,也就是「元對象編譯器」。

Qt 程序在交由標準編譯器編譯之前,先要使用 moc 分析 C++ 源文件。為了實現這些功能,我們可以看到qt中會有很多實際是空的宏(slots,signals,etc.)用以標記,輔助moc。

為了實現反射,moc會把類(繼承自metaobject)的函數名、類名等實現反射所必須的數據都放到這個類中一個由moc自動生成的叫d的結構體中。


qt的build過程有代碼生成。剩下的就很自然了。


Qt安裝時是可以選擇包含源代碼的,在安裝選項里是Source Components

把這個裝上,然後寫一個帶信號槽的程序,編譯,然後看moc_xxx.cpp文件

重點看信號函數——Qt的信號函數不需要寫實現,因為實現已經自動生成在moc文件里了。而emit關鍵字其實不做任何事情,所以emit一個信號,就是直接調用那個信號函數

然後順著信號函數,一步步摸過去,摸到庫調用的,就去看qt源碼里的庫代碼,這樣一直摸到槽函數,基本你就看懂了

信號槽的走法基本是這麼搞的

1、通過元對象系統查詢QConnection連接,並逐一分發至對應的槽函數

2、如果是跨線程的隊列連接,則需要通過Q_ARG將參數打包,通過Q_INVOKABLE的反射機制,將調用請求和打包好的參數發送至目標線程的事件隊列,由目標線程的事件循環負責調用


QT能實現類似的反射是因為有個叫做meta的東西,每個類都有,你看下生成的代碼就知道,這個是有個moc的東西生成的,分析下源碼就能知道其中的奧妙


所有方法,無論是你寫還是工具來codegen還是用宏,最終都指向把這些名字和對應的指針存在一個map里。C++是不提供這個功能的,我也沒仔細研究過qt怎麼做,不過我在我自己的gacui裡面實現了類似的東西,見Gac Library -- C++ Utilities for GPU Accelerated GUI and Script 和 GacLib - Demos


connect是通過給信號編號去調用槽函數的,每一個moccpp中都有完整的源代碼,connecton??函數其實是簡單的多了一個字元串比對。


去看源碼吧,QMetaObject就是用來干這事的。你可以先moc一下,然後看一下對應生成的 ClassName_moc.cpp文件就明白了。


推薦閱讀:

C++里,1[p]這麼訪問為什麼可以這麼寫?
C++ Primer 看不懂 怎麼辦?
c語言中int main()主函數的結尾為何有時有return 0有時沒有?
C語言中case後真的不能跟範圍?

TAG:編程 | QtC開發框架 | C編程語言 | C |