Godot Source Code Note 3
非靜態(tài)成員函數(shù)指針類型擦除
在Godot源碼中,Object類中聲明并定義了一個(gè)名為_bind_methods的函數(shù):
該函數(shù)通過調(diào)用ClassDB類中的靜態(tài)方法bind_method()完成Object類中成員函數(shù)的注冊。
由于Godot中關(guān)于Scene的類均直接或間接繼承自O(shè)bject類,因此類似Node3D、Area3D的類就可以覆寫(注意:不是虛函數(shù)的重寫)父類中bind_method()方法完成自身類中成員函數(shù)的注冊。
class_db.h文件中對(duì)ClassDB::bind_method()函數(shù)的定義:
args數(shù)組存儲(chǔ)函數(shù)的默認(rèn)實(shí)參,argptrs數(shù)組則存儲(chǔ)args數(shù)組中對(duì)應(yīng)函數(shù)默認(rèn)實(shí)參的地址。接下來通過create_method_bind函數(shù),將成員函數(shù)指針包裝成一個(gè)MethodBind派生類對(duì)象,并返回MethodBind基類指針。
create_method_bind函數(shù)的四個(gè)重載版本定義在method_bind.h文件中:
在此以第一種重載版本為例,其余不再贅述,其實(shí)現(xiàn)如下:
這里主要討論無TYPED_METHOD_BIND宏的MethodBindT類構(gòu)造方法(MethodBindT類繼承自MethodBind類),這是簡化了的MethodBindT類定義:
可以看出MethodBindT類有一個(gè)void (MB_T::*)(P...)類型的成員變量method,而其構(gòu)造函數(shù)接受一個(gè)相同類型的變量p_method并將其賦值給method。
從create_method_bind函數(shù)中也可以看到,在MethodBindT構(gòu)造函數(shù)中,reinterpret_cast<void (MB_T::*)(P...)>(p_method)將p_method重新解釋為void (MB_T::*)(P...)類型的成員函數(shù)指針。
在代碼中找到了關(guān)于MB_T的部分:
也就是說,MB_T只是__UnexistingClass的別名,但__UnexistingClass只有聲明沒有定義!
為什么要這么做?
為了弄清這個(gè)問題,不得不簡要了解一下C++類中的內(nèi)存布局。
概念上來說,從C++類中實(shí)例化出的每個(gè)對(duì)象,都獨(dú)立擁有非靜態(tài)的成員變量數(shù)據(jù)與成員函數(shù)代碼。
單獨(dú)為每個(gè)對(duì)象保存成員函數(shù)代碼段會(huì)造成大量內(nèi)存浪費(fèi),因此成員函數(shù)代碼段其實(shí)是僅有一份且共享地址的。
不難猜測,&ClassName::FunctionName取得的結(jié)構(gòu)體一定包含成員函數(shù)地址(注意:為什么是結(jié)構(gòu)體,而不僅僅是函數(shù)地址,后面會(huì)提到)。
眾所周知,非靜態(tài)成員函數(shù)的調(diào)用必須要在對(duì)象中,那么僅僅知道成員函數(shù)地址還不行,因此編譯器會(huì)在調(diào)用成員函數(shù)時(shí)傳入this指針以代表當(dāng)前對(duì)象。這樣,就可以完成對(duì)象對(duì)成員函數(shù)的調(diào)用。
這個(gè)所謂的成員函數(shù)指針到底是什么?
首先根據(jù)之前的推測,成員函數(shù)指針中一定包含了對(duì)應(yīng)函數(shù)代碼段地址。
那么為什么成員函數(shù)指針無法轉(zhuǎn)換為一個(gè)函數(shù)地址呢?
考慮一下多繼承的情況,比如D類同時(shí)繼承了A類和B類,那么在調(diào)用父類(A類和B類)成員函數(shù)時(shí),需要根據(jù)父類(A類和B類)在子類(D類)中的內(nèi)存布局來調(diào)整this指針并傳入對(duì)應(yīng)父類(A類和B類)成員函數(shù)中。
那么很容易想到,成員函數(shù)指針包含的另一個(gè)數(shù)據(jù)肯定與類中內(nèi)存布局有關(guān),即父類在子類中的偏移量,而這就可滿足計(jì)算得到父類this指針的要求。
總結(jié)一下,成員函數(shù)指針應(yīng)該是一個(gè)結(jié)構(gòu)體,其中包含了成員函數(shù)代碼段地址與類的內(nèi)存偏移量。
&ClassName::FunctionName是對(duì)象無關(guān)的,因此不經(jīng)過對(duì)象也能直接取到成員函數(shù)指針也就不奇怪了。
當(dāng)然了,以上僅為一種可能的實(shí)現(xiàn)方式,具體實(shí)現(xiàn)根據(jù)平臺(tái)不同存在差異。
解釋清楚成員函數(shù)指針,reinterpret_cast<void (MB_T::*)(P...)>(p_method)也就很好理解了。
任意類型的成員函數(shù)指針p_method,可以直接解釋為結(jié)構(gòu)相同的__UnexistingClass::*類型,并賦值給method變量。
由此便完成了對(duì)成員函數(shù)指針的類型擦除。
從MethodBindT的構(gòu)造函數(shù)中返回,就得到了成員函數(shù)指針的MethodBind包裝類型的指針。
接下來將類的名稱存儲(chǔ)在MethodBind對(duì)象的成員變量中,將指針從create_method_bind中返回。
標(biāo)記返回值類型是否為Object指針后,調(diào)用bind_methodfi()函數(shù)完成非靜態(tài)成員函數(shù)注冊。
最后再看一下成員函數(shù)指針的調(diào)用方法:
傳入具體對(duì)象完成成員函數(shù)的調(diào)用,語法為:
最后再附一個(gè)簡化后的例子:
但在C++11后可以使用std::function庫函數(shù)完成對(duì)函數(shù)的包裝:
如侵刪。
歡迎評(píng)論指正。