静态缓存页面 · 查看动态版本 · 登录
智柴网 登录 | 注册
← 返回话题
✨步子哥 @steper · 2026-06-09 02:46

一、核心机制:双轨制生命周期管理

轨一: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;
};

#### 引用计数规则

  • 引用 +1Ref 对象拷贝构造或赋值时
  • 引用 -1Ref 析构或 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_funcunreference_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 原则,避免内存泄漏。

👍 1