一、核心机制:双轨制生命周期管理
轨一:C++ 侧 RAII(资源获取即初始化)
C++ 扩展对象由 memnew/memdelete 管理,遵循 RAII 原则:
// 创建对象
MyClass *obj = memnew(MyClass);
// 销毁对象
memdelete(obj);
memnew:调用operator new+ 构造函数memdelete:调用析构函数 +operator delete- 不使用 C++ 标准库的
shared_ptr,而是 Godot 自研的内存管理器
轨二:Godot 侧引用计数(针对 RefCounted 子类)
若 C++ 类继承自 RefCounted,则采用 SafeRefCount 引用计数:
class RefCounted : public Object {
SafeRefCount refcount; // 引用计数
SafeRefCount refcount_init; // 初始化计数
SafeNumeric<uint32_t> dereference_count;
};
#### 引用计数规则
- 引用 +1:
Ref对象拷贝构造或赋值时 - 引用 -1:
Ref析构或unref()时 - 计数为 0:自动调用
memdelete销毁对象
二、GDExtension 特有的生命周期桥接
关键机制:Instance Binding
GDExtension 通过 InstanceBinding 结构体,桥接 C++ 对象与 Godot 引擎:
// Object 类中的 InstanceBinding 定义
struct InstanceBinding {
void *binding = nullptr; // C++ 对象指针
GDExtensionInstanceBindingFreeCallback free_callback = nullptr; // 释放回调
GDExtensionInstanceBindingReferenceCallback reference_callback = nullptr; // 引用回调
};
#### Instance Binding 回调三剑客
typedef struct {
GDExtensionInstanceBindingCreateCallback create_callback; // 创建绑定
GDExtensionInstanceBindingFreeCallback free_callback; // 释放绑定
GDExtensionInstanceBindingReferenceCallback reference_callback; // 引用计数变化
} GDExtensionInstanceBindingCallbacks;
---
三、GDExtension 对象生命周期完整流程
阶段一:C++ 对象创建
// 1. C++ 侧创建对象
MyExtension *obj = memnew(MyExtension);
// 2. 将 C++ 对象绑定到 Godot Object
obj->set_instance_binding(
token, // 扩展标识
obj, // C++ 对象指针
&binding_callbacks // 回调函数表
);
阶段二:Godot 侧引用
# GDScript 侧持有引用
var ext = MyExtension.new() # 引用 +1
# ... 使用 ext ...
ext = null # 引用 -1,若无其他引用,触发销毁
阶段三:销毁流程
// 1. Godot 引用计数降为 0
// 2. 调用 reference_callback (若 C++ 类继承自 RefCounted)
// 3. 调用 free_callback 销毁 C++ 对象
// 4. 调用 memdelete 释放内存
---
四、RefCounted 在 GDExtension 中的特殊处理
问题:C++ 与 Godot 引用计数如何同步?
答案:通过 reference_func 和 unreference_func 回调。
#### 回调注册
// 注册 C++ 类时,提供引用计数回调
GDExtensionClassCreationInfo6 class_info = {
.reference_func = [](GDExtensionClassInstancePtr p_instance) {
// Godot 增加引用时,调用 C++ 侧 RefCounted::reference()
reinterpret_cast<RefCounted *>(p_instance)->reference();
},
.unreference_func = [](GDExtensionClassInstancePtr p_instance) {
// Godot 减少引用时,调用 C++ 侧 RefCounted::unreference()
if (reinterpret_cast<RefCounted *>(p_instance)->unreference()) {
// 引用计数为 0,销毁对象
memdelete(reinterpret_cast<RefCounted *>(p_instance));
}
},
// ... 其他回调
};
#### 引用计数同步流程
GDScript: ext = MyExtension.new()
↓
Godot: RefCounted::reference() # 引用 +1
↓
C++: reference_func() 回调 # 通知 C++ 侧
↓
C++: RefCounted::reference() # C++ 侧引用计数 +1
GDScript: ext = null
↓
Godot: RefCounted::unreference() # 引用 -1
↓
C++: unreference_func() 回调 # 通知 C++ 侧
↓
C++: RefCounted::unreference() # C++ 侧引用计数 -1
↓
若计数为 0: memdelete(obj) # 销毁 C++ 对象
---
五、Node 类(非 RefCounted)的生命周期
若 C++ 类继承自 Node(非 RefCounted),则不依赖引用计数,而是:
管理方式:树形结构 + 手动释放
// C++ 侧
class MyNode : public Node {
GDCLASS(MyNode, Node);
};
// GDScript 侧
var node = MyNode.new() # 创建
get_parent().add_child(node) # 加入场景树(引用 +1)
node.queue_free() # 标记销毁
# 或
node.free() # 立即销毁
销毁流程
Node::queue_free()
↓
Node::_notification(NOTIFICATION_PREDELETE)
↓
调用 GDExtension 的 free_instance_func 回调
↓
C++ 侧销毁对象(调用析构函数)
↓
memdelete(obj)
---
六、godot-cpp 的封装简化
实际开发中,godot-cpp 已封装好生命周期管理,开发者无需手动处理:
示例:RefCounted 子类
#include <godot_cpp/classes/ref_counted.hpp>
#include <godot_cpp/core/class_db.hpp>
using namespace godot;
class MyResource : public RefCounted {
GDCLASS(MyResource, RefCounted);
String data;
protected:
static void _bind_methods() {
ClassDB::bind_method(D_METHOD("set_data", "data"), &MyResource::set_data);
ClassDB::bind_method(D_METHOD("get_data"), &MyResource::get_data);
ADD_PROPERTY(PropertyInfo(Variant::STRING, "data"), "set_data", "get_data");
}
public:
void set_data(const String &p_data) { data = p_data; }
String get_data() const { return data; }
// 无需手动管理引用计数!
// godot-cpp 自动处理 reference/unreference 回调
};
示例:Node 子类
#include <godot_cpp/classes/node.hpp>
#include <godot_cpp/core/class_db.hpp>
using namespace godot;
class MyNode : public Node {
GDCLASS(MyNode, Node);
protected:
static void _bind_methods() {
ClassDB::bind_method(D_METHOD("do_something"), &MyNode::do_something);
}
public:
void _ready() {
UtilityFunctions::print("MyNode ready!");
}
void do_something() {
UtilityFunctions::print("Doing something!");
}
// 无需手动管理内存!
// Godot 场景树负责 Node 的生命周期
};
---
七、内存泄漏防范
规则一:RefCounted 对象必须用 Ref 持有
// 正确:使用 Ref<T>
Ref<MyResource> res = memnew(MyResource);
// 自动管理引用计数
// 错误:裸指针
MyResource *res = memnew(MyResource); // 引用计数为 0
// 可能导致内存泄漏!
规则二:Node 对象必须加入场景树或手动释放
// 正确:加入场景树
Node *node = memnew(MyNode);
get_parent()->add_child(node); // 场景树持有引用
// 正确:手动释放
Node *node = memnew(MyNode);
node->queue_free(); // 标记销毁
// 错误:孤立的 Node
Node *node = memnew(MyNode);
// 忘记加入场景树或释放 → 内存泄漏!
规则三:避免循环引用
// 错误:循环引用
class NodeA : public RefCounted {
Ref<NodeB> b; // 引用 B
};
class NodeB : public RefCounted {
Ref<NodeA> a; // 引用 A
};
// 解决:使用 WeakRef 或手动打破循环
---
八、总结:生命周期管理要点
| 类别 | 管理方式 | 销毁触发条件 |
|---|---|---|
| RefCounted 子类 | Godot 引用计数 + C++ 引用计数(双轨同步) | 引用计数为 0 |
| Node 子类 | 场景树持有 + 手动释放 | queue_free() 或 free() |
| 普通 Object 子类 | 手动管理(memnew/memdelete) | 显式调用 memdelete |
| Instance Binding | 回调函数表 | Godot 对象销毁时触发 free_callback |
九、关键结论
1. 非单纯依赖 C++ 引用计数,而是 Godot 引用计数与 C++ 引用计数双轨同步。
2. RefCounted 子类:通过 reference_func / unreference_func 回调同步引用计数。
3. Node 子类:依赖场景树管理生命周期,无需引用计数。
4. godot-cpp 已封装细节,开发者只需遵循 RAII 原则,避免内存泄漏。