问题:C++ 刚体对象暴露给 Lua 后,如何避免 C++ 对象销毁导致的崩溃?
在使用 C++ 编写物理引擎时,经常需要将 C++ 中的刚体(RigidBody
)对象暴露给 Lua 脚本,以便脚本可以控制其速度和位置。 但一个常见的问题是,如果 C++ 中的 RigidBody
对象被销毁了,而 Lua 脚本的回调仍然在尝试访问它,就会导致程序崩溃。 那么,有没有一种好的模式可以解决这个问题呢?
解决方案:使用智能指针和弱引用
一种有效的解决方案是结合使用智能指针(如 std::shared_ptr
)和弱引用(std::weak_ptr
)。 核心思想是:
- C++ 端使用
std::shared_ptr
管理RigidBody
对象。std::shared_ptr
能够自动追踪对象的引用计数,并在没有引用指向该对象时自动释放内存。 - 将
std::weak_ptr
传递给 Lua。std::weak_ptr
是一种非侵入式的智能指针,它不会增加对象的引用计数。 Lua 脚本可以通过std::weak_ptr
尝试获取RigidBody
对象。 - 在 Lua 访问
RigidBody
对象之前,先检查对象是否仍然有效。 Lua 脚本通过std::weak_ptr::lock()
方法尝试获取std::shared_ptr
。 如果对象仍然存在,lock()
方法会返回一个有效的std::shared_ptr
,否则返回一个空的std::shared_ptr
。
C++ 代码示例:
#include <memory>
#include <lua.hpp>
class RigidBody {
public:
float x, y;
void set_position(float x, float y) {
this->x = x;
this->y = y;
}
};
// 将 RigidBody 暴露给 Lua 的函数
int lua_RigidBody_set_position(lua_State *L) {
// 获取 RigidBody 的弱引用
auto weak_ptr = *static_cast<std::weak_ptr<RigidBody>*>(lua_touserdata(L, 1));
// 尝试获取 shared_ptr
std::shared_ptr<RigidBody> rigid_body = weak_ptr.lock();
// 检查对象是否仍然有效
if (rigid_body) {
float x = lua_tonumber(L, 2);
float y = lua_tonumber(L, 3);
rigid_body->set_position(x, y);
return 0;
} else {
// 对象已经被销毁
luaL_error(L, "RigidBody object has been destroyed!");
return 0; // never reach here
}
}
// 创建 RigidBody 对象的函数
int lua_RigidBody_create(lua_State *L) {
// 创建 shared_ptr
auto rigid_body = std::make_shared<RigidBody>();
// 创建 weak_ptr
std::weak_ptr<RigidBody> weak_ptr = rigid_body;
// 将 weak_ptr 传递给 Lua (需要存储 weak_ptr 的地址)
*static_cast<std::weak_ptr<RigidBody>*>(lua_newuserdata(L, sizeof(std::weak_ptr<RigidBody>))) = weak_ptr;
// 设置 metatable,用于垃圾回收 (可选)
luaL_getmetatable(L, "RigidBody");
lua_setmetatable(L, -2);
return 1;
}
// 注册 RigidBody 相关函数的函数
int register_RigidBody(lua_State *L) {
lua_register(L, "RigidBody_create", lua_RigidBody_create);
lua_register(L, "RigidBody_set_position", lua_RigidBody_set_position);
// 创建 metatable,用于垃圾回收 (可选)
luaL_newmetatable(L, "RigidBody");
lua_pushstring(L, "__gc");
lua_pushcfunction(L, [](lua_State *L) {
// 在垃圾回收时,释放 userdata 中存储的 weak_ptr
auto weak_ptr = static_cast<std::weak_ptr<RigidBody>*>(lua_touserdata(L, 1));
weak_ptr->~weak_ptr(); // 显式调用析构函数
return 0;
});
lua_settable(L, -3);
return 0;
}
Lua 代码示例:
-- 创建 RigidBody 对象
local rigid_body = RigidBody_create()
-- 设置位置
RigidBody_set_position(rigid_body, 10, 20)
-- ... 之后,C++ 端可能销毁了 RigidBody 对象
-- 再次尝试设置位置
RigidBody_set_position(rigid_body, 30, 40) -- 如果 RigidBody 已被销毁,会抛出错误
优点:
- 安全: 避免了 C++ 对象销毁后 Lua 脚本访问导致的崩溃。
- 灵活: 允许 C++ 端控制对象的生命周期,而无需 Lua 脚本的干预。
- 清晰: 代码逻辑清晰易懂,易于维护。
缺点:
- 性能开销: 引入了智能指针的开销,但通常可以忽略不计。
- 代码复杂度: 相比直接传递指针,代码稍微复杂一些。
总结:
使用智能指针和弱引用是一种有效的模式,可以安全地将 C++ 对象暴露给 Lua 脚本,并避免 C++ 对象销毁导致的崩溃。 这种模式在游戏开发中被广泛应用,特别是在物理引擎、AI 系统等需要跨语言交互的场景中。