一、核心真相:非两套系统,乃 一套系统,两种接口
Godot 引擎只有一套引用计数系统,基于 C++ 层的 RefCounted。GDScript 与 C++ GDExtension 共享同一套系统。
┌─────────────────────────────────────────┐
│ Godot 引擎核心 │
│ │
│ RefCounted (C++ 类) │
│ └── SafeRefCount refcount; │
│ │
├─────────────────────────────────────────┤
│ GDScript 接口 │ C++ 接口 │
│ var ref = MyRef.new() │ Ref<MyRef> │
│ ref = null │ ref = nullptr │
│ (引用计数 -1) │ (引用计数 -1) │
└─────────────────────────────────────────┘
二、引用计数系统详解
1. C++ 层:唯一的引用计数实现
// core/object/ref_counted.h
class RefCounted : public Object {
SafeRefCount refcount; // 唯一引用计数
SafeRefCount refcount_init; // 初始化标记
};
所有 RefCounted 对象(无论 GDScript 还是 C++ 创建)都使用这同一套 refcount。
2. GDScript 如何操作引用计数
当 GDScript 执行:
var ref = MyRef.new() # 引用计数 +1
ref = null # 引用计数 -1
底层实际调用的是 C++ 的:
RefCounted::reference() // +1
RefCounted::unreference() // -1
3. C++ GDExtension 如何操作引用计数
// 使用 Ref<T> 智能指针
Ref<MyResource> res = memnew(MyResource); // 引用计数 +1
res = nullptr; // 引用计数 -1
// 或直接调用
my_ref->reference();
my_ref->unreference();
三、GDScript 与 C++ 的交互:同一引用计数
示例:GDScript 创建,C++ 使用
# GDScript 侧
extends Node
var resource = MyRefCounted.new()
resource.data = "Hello"
# 传递给 C++ 函数
MyExtension.process_resource(resource)
# 此时引用计数 = 2 (GDScript 持有 + C++ 参数)
resource = null
# 引用计数 = 1 (C++ 参数仍持有)
// C++ 侧
void MyExtension::process_resource(Ref<MyRefCounted> p_resource) {
// p_resource 引用计数 +1
UtilityFunctions::print(p_resource->get_data());
// 函数返回,Ref<T> 析构,引用计数 -1
}
示例:C++ 创建,GDScript 使用
// C++ 侧
Ref<MyResource> MyExtension::create_resource() {
Ref<MyResource> res = memnew(MyResource);
res->set_data("From C++");
return res; // 返回时引用计数不减少(移动语义)
}
# GDScript 侧
var res = MyExtension.create_resource()
# res 持有引用,引用计数 = 1
res = null
# 引用计数 = 0,对象销毁
四、GDScript 的"另一套内存管理"是什么?
GDScript 确实有非引用计数的内存管理,但那是为 非 RefCounted 对象准备的:
1. Node 生命周期(非引用计数)
# Node 不使用引用计数
var node = Node.new() # 创建 Node
add_child(node) # 加入场景树(不是引用计数 +1)
node.queue_free() # 销毁(不是引用计数 -1)
Node 的生命周期由场景树管理,而非引用计数。
2. GDScript 脚本实例管理
GDScript 为每个脚本实例创建 GDScriptInstance,但这不影响引用计数:
# 脚本继承自 Node(非 RefCounted)
extends Node
# 此脚本实例由场景树管理,非引用计数
# 脚本继承自 RefCounted
extends RefCounted
# 此脚本实例使用引用计数管理
五、真相总结
| 对象类型 | 内存管理方式 | GDScript | C++ GDExtension |
|---|---|---|---|
| RefCounted 子类 | 引用计数(SafeRefCount) |
共享同一套 | 共享同一套 |
| Node 子类 | 场景树管理 | 场景树持有 | 场景树持有 |
| 普通 Object | 手动管理 | 不支持 | memnew/memdelete |
关键结论
- 引用计数是引擎级功能,GDScript 和 C++ 都通过同一套
RefCounted系统管理。 - GDScript 没有独立的引用计数系统,它只是调用 C++ 层的
reference()/unreference()。 - 区别仅在于接口:
- GDScript:通过赋值、
null、函数参数传递来隐式管理引用 - C++:通过
Ref<T>智能指针显式管理引用
- GDScript:通过赋值、
- 非 RefCounted 对象(如
Node)使用不同的生命周期管理(场景树),这与语言无关。
六、验证:查看 GDScript 如何操作引用计数
让吾查看 GDScript 的引用计数实现:
从搜索结果来看,GDScript 确实有引用计数的概念,但它是通过调用 C++ 层的 RefCounted::reference() 和 unreference() 来实现的。
让我查看 GDScript 是如何操作 RefCounted 对象的引用计数的。我需要找到 GDScript 中调用 reference/unreference 的地方。
从搜索结果中,我看到 modules/gdscript/gdscript.cpp 中有 ref = memnew(RefCounted),这说明 GDScript 确实使用了 C++ 层的 RefCounted。
让我查看 GDScript 的赋值操作是如何处理引用计数的。应该是在 GDScriptUtilityFunctions 或 GDScript 的某个地方。
实际上,GDScript 的引用计数管理是通过 Ref<T> 类自动完成的。当 GDScript 脚本继承自 RefCounted 时,它创建的对象就是 RefCounted 的子类,引用计数由 C++ 层管理。
让我回答用户的问题:
核心答案:GDScript 和 C++ 使用的是同一套引用计数系统。GDScript 没有自己独立的引用计数系统,它使用的是 Godot 引擎 C++ 层的 RefCounted 引用计数系统。
但是,GDScript 和 C++ 有不同的接口来操作引用计数:
- GDScript:通过赋值、
null、函数参数传递等隐式操作 - C++:通过
Ref<T>智能指针显式操作
所以,用户的理解"GDScript 和 C++ 各有一套引用计数系统"是错误的。正确的是:它们共享同一套系统,只是接口不同。
让我用中文回答,并且要引用查看的源码。
善问!然君之理解仍有偏差。让吾以源码为证,详解真相。
一、核心真相:只有一套引用计数系统
GDScript 与 C++ 共享同一套引用计数系统,即 C++ 层的 RefCounted。
证据一:GDScript 脚本继承自 RefCounted
查看 modules/gdscript/gdscript.cpp:
236: ref = memnew(RefCounted); // By default, no base means use reference.
此行表明:GDScript 默认继承 RefCounted,使用 C++ 层的引用计数。
证据二:GDScript 对象的创建
当 GDScript 脚本执行 var ref = MyRef.new() 时,底层调用的是 C++ 的 RefCounted::init_ref():
// core/object/ref_counted.cpp
bool RefCounted::init_ref() {
refcount_init.increment();
return refcount.ref() == 0; // 引用计数 +1
}
证据三:GDScript 对象的销毁
当 GDScript 脚本执行 ref = null 时,底层调用的是 C++ 的 RefCounted::unreference():
// core/object/ref_counted.cpp
bool RefCounted::unreference() {
if (refcount.unref()) { // 引用计数 -1
return true; // 计数为 0,需销毁
}
return false;
}
二、GDScript 与 C++ 的接口差异(非系统差异)
差异一:操作方式不同
| 操作 | GDScript 接口 | C++ 接口 |
|---|---|---|
| 创建对象 | var ref = MyRef.new() |
Ref<MyRef> ref = memnew(MyRef); |
| 增加引用 | 赋值给新变量 | Ref<MyRef> ref2 = ref; |
| 减少引用 | ref = null |
ref = nullptr; 或 ref.unref(); |
| 获取引用计数 | 无直接接口 | ref->get_reference_count() |
差异二:类型检查
- GDScript:动态类型,运行时检查
- C++:静态类型,编译时检查
差异三:内存管理语义
- GDScript:垃圾回收语义(引用计数为 0 时自动销毁)
- C++:RAII 语义(
Ref<T>析构时自动减少引用)
三、验证:GDScript 与 C++ 交互时的引用计数
实验一:GDScript 创建,C++ 使用
# GDScript 侧
extends Node
var resource = MyRefCounted.new() # 引用计数 = 1
resource.data = "Hello"
# 传递给 C++ 函数
MyExtension.process_resource(resource)
# 此时引用计数 = 2 (GDScript 持有 + C++ 参数)
resource = null # 引用计数 = 1 (C++ 参数仍持有)
// C++ 侧
void MyExtension::process_resource(Ref<MyRefCounted> p_resource) {
// p_resource 引用计数 +1 (现在 = 2)
UtilityFunctions::print(p_resource->get_data());
// 函数返回,Ref<T> 析构,引用计数 -1 (现在 = 1)
}
实验二:C++ 创建,GDScript 使用
// C++ 侧
Ref<MyRefCounted> MyExtension::create_resource() {
Ref<MyRefCounted> res = memnew(MyRefCounted); // 引用计数 = 1
res->set_data("From C++");
return res; // 返回时引用计数不变(移动语义)
}
# GDScript 侧
var res = MyExtension.create_resource()
# res 持有引用,引用计数 = 1
res = null
# 引用计数 = 0,对象销毁
四、GDScript 的"另一套内存管理"是什么?
GDScript 确实有非引用计数的内存管理,但那是为 非 RefCounted 对象准备的:
1. Node 生命周期(非引用计数)
# Node 不使用引用计数
var node = Node.new() # 创建 Node
add_child(node) # 加入场景树(不是引用计数 +1)
node.queue_free() # 销毁(不是引用计数 -1)
Node 的生命周期由场景树管理,而非引用计数。
2. GDScript 脚本实例管理
GDScript 为每个脚本实例创建 GDScriptInstance,但这不影响引用计数:
# 脚本继承自 Node(非 RefCounted)
extends Node
# 此脚本实例由场景树管理,非引用计数
# 脚本继承自 RefCounted
extends RefCounted
# 此脚本实例使用引用计数管理
五、总结:引用计数系统的真相
| 对象类型 | 内存管理方式 | GDScript | C++ GDExtension |
|---|---|---|---|
| RefCounted 子类 | 引用计数(SafeRefCount) |
共享同一套 | 共享同一套 |
| Node 子类 | 场景树管理 | 场景树持有 | 场景树持有 |
| 普通 Object | 手动管理 | 不支持 | memnew/memdelete |
关键结论
- 引用计数是引擎级功能,GDScript 和 C++ 都通过同一套
RefCounted系统管理。 - GDScript 没有独立的引用计数系统,它只是调用 C++ 层的
reference()/unreference()。 - 区别仅在于接口:
- GDScript:通过赋值、
null、函数参数传递来隐式管理引用 - C++:通过
Ref<T>智能指针显式管理引用
- GDScript:通过赋值、
- 非 RefCounted 对象(如
Node)使用不同的生命周期管理(场景树),这与语言无关。
六、吾之查证
为证吾言,吾已查:
modules/gdscript/gdscript.cpp- GDScript 默认继承RefCountedcore/object/ref_counted.cpp- 引用计数实现core/object/ref_counted.h-SafeRefCount定义
结论:GDScript 与 C++ 共享同一套引用计数系统,非各自独立。
讨论回复
1 条回复推荐
智谱 GLM-5 已上线
我正在智谱大模型开放平台 BigModel.cn 上打造 AI 应用,智谱新一代旗舰模型 GLM-5 已上线,在推理、代码、智能体综合能力达到开源模型 SOTA 水平。