欢迎光临散文网 会员登陆 & 注册

Godot Source Code Note 3

2023-06-27 15:31 作者:中专人  | 我要投稿

非静态成员函数指针类型擦除

在Godot源码中,Object类中声明并定义了一个名为_bind_methods的函数:

该函数通过调用ClassDB类中的静态方法bind_method()完成Object类中成员函数的注册。

由于Godot中关于Scene的类均直接或间接继承自Object类,因此类似Node3D、Area3D的类就可以覆写(注意:不是虚函数的重写)父类中bind_method()方法完成自身类中成员函数的注册。

class_db.h文件中对ClassDB::bind_method()函数的定义:

args数组存储函数的默认实参,argptrs数组则存储args数组中对应函数默认实参的地址。接下来通过create_method_bind函数,将成员函数指针包装成一个MethodBind派生类对象,并返回MethodBind基类指针。

create_method_bind函数的四个重载版本定义在method_bind.h文件中:

在此以第一种重载版本为例,其余不再赘述,其实现如下:

这里主要讨论无TYPED_METHOD_BIND宏的MethodBindT类构造方法(MethodBindT类继承自MethodBind类),这是简化了的MethodBindT类定义:

可以看出MethodBindT类有一个void (MB_T::*)(P...)类型的成员变量method,而其构造函数接受一个相同类型的变量p_method并将其赋值给method。

从create_method_bind函数中也可以看到,在MethodBindT构造函数中,reinterpret_cast<void (MB_T::*)(P...)>(p_method)将p_method重新解释为void (MB_T::*)(P...)类型的成员函数指针。

在代码中找到了关于MB_T的部分:

也就是说,MB_T只是__UnexistingClass的别名,但__UnexistingClass只有声明没有定义!

为什么要这么做?

为了弄清这个问题,不得不简要了解一下C++类中的内存布局。

概念上来说,从C++类中实例化出的每个对象,都独立拥有非静态的成员变量数据与成员函数代码。

单独为每个对象保存成员函数代码段会造成大量内存浪费,因此成员函数代码段其实是仅有一份且共享地址的。

不难猜测,&ClassName::FunctionName取得的结构体一定包含成员函数地址(注意:为什么是结构体,而不仅仅是函数地址,后面会提到)。

众所周知,非静态成员函数的调用必须要在对象中,那么仅仅知道成员函数地址还不行,因此编译器会在调用成员函数时传入this指针以代表当前对象。这样,就可以完成对象对成员函数的调用。

这个所谓的成员函数指针到底是什么?

首先根据之前的推测,成员函数指针中一定包含了对应函数代码段地址。

那么为什么成员函数指针无法转换为一个函数地址呢?

考虑一下多继承的情况,比如D类同时继承了A类和B类,那么在调用父类(A类和B类)成员函数时,需要根据父类(A类和B类)在子类(D类)中的内存布局来调整this指针并传入对应父类(A类和B类)成员函数中。

那么很容易想到,成员函数指针包含的另一个数据肯定与类中内存布局有关,即父类在子类中的偏移量,而这就可满足计算得到父类this指针的要求。

总结一下,成员函数指针应该是一个结构体,其中包含了成员函数代码段地址与类的内存偏移量。

&ClassName::FunctionName是对象无关的,因此不经过对象也能直接取到成员函数指针也就不奇怪了。

当然了,以上仅为一种可能的实现方式,具体实现根据平台不同存在差异。

解释清楚成员函数指针,reinterpret_cast<void (MB_T::*)(P...)>(p_method)也就很好理解了。

任意类型的成员函数指针p_method,可以直接解释为结构相同的__UnexistingClass::*类型,并赋值给method变量。

由此便完成了对成员函数指针的类型擦除。

从MethodBindT的构造函数中返回,就得到了成员函数指针的MethodBind包装类型的指针。

接下来将类的名称存储在MethodBind对象的成员变量中,将指针从create_method_bind中返回。

标记返回值类型是否为Object指针后,调用bind_methodfi()函数完成非静态成员函数注册。

最后再看一下成员函数指针的调用方法:

传入具体对象完成成员函数的调用,语法为:

最后再附一个简化后的例子:

但在C++11后可以使用std::function库函数完成对函数的包装:


如侵删。
欢迎评论指正。

Godot Source Code Note 3的评论 (共 条)

分享到微博请遵守国家法律