Lua中注册C++类后多次调用构造函数出现对象类型错误
Lua C API封装Vector2类多次创建对象异常问题排查
问题现象
使用Lua C API封装C++ Vector2类时,单次调用构造函数创建对象正常,但多次调用时,后续创建的对象被识别为普通表,无法正常访问属性和方法。
问题代码
封装的Vector2类代码
namespace API { struct Vector2 { double x = 0; double y = 0; Vector2(const LuaStack& args); }; static int Vector2_new(lua_State* L) { lua_newclass<Vector2>(L); static auto destructor = [](lua_State* L) { delete lua_getclass<Vector2>(L, "Vector2", 1); return 0; }; static auto get_len = [](lua_State* L) { lua_pushnumber(L, 2); return 1; }; static auto to_string = [](lua_State* L) { const auto self = lua_getclass<Vector2>(L, "Vector2", 1); std::string result; result = "{ " + std::to_string(self->x) + ", " + std::to_string(self->y) + " }"; lua_pushstring(L, result.c_str()); return 1; }; static auto index_get = [](lua_State* L) { const auto self = lua_getclass<Vector2>(L, "Vector2", 1); if (lua_isnumber(L, 2)) { switch (lua_tointeger(L, 2)) { case 1: lua_pushnumber(L, self->x); break; case 2: lua_pushnumber(L, self->y); break; default: lua_pushnil(L); } } else { const std::string_view key = luaL_checkstring(L, 2); if (key == "x") lua_pushnumber(L, self->x); else if (key == "y") lua_pushnumber(L, self->y); else lua_pushnil(L); } return 1; }; static auto index_set = [](lua_State* L) { const auto self = lua_getclass<Vector2>(L, "Vector2", 1); double new_value = luaL_checknumber(L, 3); if (lua_isnumber(L, 2)) { switch (lua_tointeger(L, 2)) { case 1: self->x = new_value; break; case 2: self->y = new_value; break; } } else { const std::string_view key = luaL_checkstring(L, 2); if (key == "x") self->x = new_value; if (key == "y") self->y = new_value; } return 0; }; lua_setmethods(L, "Vector2", { { "__gc", destructor }, { "__len", get_len }, { "__index", index_get }, { "__newindex", index_set }, { "__tostring", to_string }, }); return 1; } }
注册构造函数代码
lua_register(lua_state, "Vector2", API::Vector2_new);
辅助函数代码
template<typename Class> __forceinline Class* lua_getclass(lua_State* L, const std::string& class_name, size_t index) { return *static_cast<Class**>(luaL_checkudata(L, index, class_name.c_str())); }; template<typename Class> __forceinline void lua_newclass(lua_State* L) { *static_cast<Class**>(lua_newuserdata(L, sizeof(Class*))) = new Class(std::move(LuaStack(L))); }; void lua_setmethods(lua_State* L, const std::string& name, static const std::vector<std::pair<std::string, lua_CFunction>>& methods) { if (luaL_newmetatable(L, name.c_str())) { for (const auto& function : methods) { lua_pushcfunction(L, function.second); lua_setfield(L, -2, function.first.c_str()); } lua_setmetatable(L, -2); } }
Lua测试代码
local vec1 = Vector2(500, 800) local vec2 = Vector2(10, 15) print(tostring(vec1)) -- output: { 500, 800 } print(vec1.x, vec1.y) -- output: 500 800 print(tostring(vec2)) -- output: table: 08996658 print(vec2.x, vec2.y) -- output: nil nil
错误原因分析
- 元表初始化逻辑错误:
lua_setmethods中的if (luaL_newmetatable(...))只在元表不存在时执行方法注册和元表绑定。第一次创建对象时元表不存在,执行正常;后续创建时元表已存在,if条件为假,跳过元表绑定步骤,导致新userdata没有元表,被Lua识别为普通表。 - 冗余操作:
Vector2_new每次调用都重复定义静态lambda和注册方法,完全没必要,且会引发逻辑冲突。
修复方案
1. 修复辅助函数lua_setmethods
void lua_setmethods(lua_State* L, const std::string& name, const std::vector<std::pair<std::string, lua_CFunction>>& methods) { // 确保元表存在,不存在则创建 luaL_newmetatable(L, name.c_str()); // 注册方法(重复注册不影响逻辑) for (const auto& function : methods) { lua_pushcfunction(L, function.second); lua_setfield(L, -2, function.first.c_str()); } // 弹出元表,不影响后续栈操作 lua_pop(L, 1); }
2. 重构Vector2_new及元表初始化逻辑
namespace API { struct Vector2 { double x = 0; double y = 0; Vector2(const LuaStack& args); }; // 提前定义静态方法,避免重复创建 static auto destructor = [](lua_State* L) { delete *static_cast<Vector2**>(luaL_checkudata(L, 1, "Vector2")); return 0; }; static auto get_len = [](lua_State* L) { lua_pushnumber(L, 2); return 1; }; static auto to_string = [](lua_State* L) { const auto self = *static_cast<Vector2**>(luaL_checkudata(L, 1, "Vector2")); std::string result = "{ " + std::to_string(self->x) + ", " + std::to_string(self->y) + " }"; lua_pushstring(L, result.c_str()); return 1; }; static auto index_get = [](lua_State* L) { const auto self = *static_cast<Vector2**>(luaL_checkudata(L, 1, "Vector2")); if (lua_isnumber(L, 2)) { switch (lua_tointeger(L, 2)) { case 1: lua_pushnumber(L, self->x); break; case 2: lua_pushnumber(L, self->y); break; default: lua_pushnil(L); } } else { const std::string_view key = luaL_checkstring(L, 2); if (key == "x") lua_pushnumber(L, self->x); else if (key == "y") lua_pushnumber(L, self->y); else lua_pushnil(L); } return 1; }; static auto index_set = [](lua_State* L) { auto self = *static_cast<Vector2**>(luaL_checkudata(L, 1, "Vector2")); double new_value = luaL_checknumber(L, 3); if (lua_isnumber(L, 2)) { switch (lua_tointeger(L, 2)) { case 1: self->x = new_value; break; case 2: self->y = new_value; break; } } else { const std::string_view key = luaL_checkstring(L, 2); if (key == "x") self->x = new_value; if (key == "y") self->y = new_value; } return 0; }; // 元表初始化函数,仅调用一次 static void Vector2_init(lua_State* L) { lua_setmethods(L, "Vector2", { { "__gc", destructor }, { "__len", get_len }, { "__index", index_get }, { "__newindex", index_set }, { "__tostring", to_string }, }); } static int Vector2_new(lua_State* L) { // 创建userdata并初始化对象 Vector2** ptr = static_cast<Vector2**>(lua_newuserdata(L, sizeof(Vector2*))); *ptr = new Vector2(LuaStack(L)); // 绑定元表到当前userdata luaL_getmetatable(L, "Vector2"); lua_setmetatable(L, -2); return 1; } }
3. 修改注册逻辑
// 先初始化元表,仅执行一次 API::Vector2_init(lua_state); // 再注册构造函数 lua_register(lua_state, "Vector2", API::Vector2_new);
修复后逻辑说明
- 元表初始化仅执行一次,避免重复操作和逻辑冲突。
- 每次创建userdata时,主动获取已存在的元表并绑定,确保所有对象都有正确的元表。
- 移除了冗余的静态lambda重复定义,优化了代码结构。
内容的提问来源于stack exchange,提问作者b0ryakha
相关产品推荐
相关产品推荐

