You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

错误原因分析

  1. 元表初始化逻辑错误:lua_setmethods中的if (luaL_newmetatable(...))只在元表不存在时执行方法注册和元表绑定。第一次创建对象时元表不存在,执行正常;后续创建时元表已存在,if条件为假,跳过元表绑定步骤,导致新userdata没有元表,被Lua识别为普通表。
  2. 冗余操作: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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.08 08:02:07