如何将C ++类构造函数注册到Lua userdata并默认使用它

使用Lua C API,我将一个简单的Object类注册到了Lua中,就像这样:

// 我的C++ Object类
class Object {
private:
    double x;
public:
    Object(double x) : x(x){}
};

// 创建并返回Object类的实例到Lua
int object_new(lua_State* L)
{
    double x = luaL_checknumber(L, 1);
    *reinterpret_cast<Object**>(lua_newuserdata(L, sizeof(Object*))) = new Object(x);
    luaL_setmetatable(L, "Object");
    return 1;
}

// 要注册到Lua的函数
const luaL_Reg functions[] =
{
    {"new", object_new},
    {nullptr, nullptr}
};

// 将Object类注册到Lua
luaL_newmetatable(L, "Object");
luaL_setfuncs(L, functions, 0);
lua_pushvalue(L, -1);
lua_setfield(L, -2, "__index");

在我的Lua脚本中,以下内容非常好用:

// 非常好用!
my_object = Object.new(42)

但是,我想能够做到这一点(即省略.new部分):

// 失败:(
my_object = Object(42)

但是,当我执行Lua脚本时,我会收到此错误:

...attempt to call a table value (global 'Object').

有没有一种方法可以注册C ++类,以便在不提供函数名的情况下调用构造函数? 我错过了什么使它起作用? 对于临时对象,这将特别有用。

谢谢!

点赞
用户2546626
用户2546626

你应该检查 luaL_newmetatable 的返回值,只需注册一次你的元方法。

您可以将 luaL_setmetatable 替换为 luaL_newmetatable,这样您的代码就可以与 Lua 5.1 兼容,您可以将元表注册嵌入到“构造函数”中,它的效果相同(除了额外的 lua_setmetatable)。

对于构造函数,只需注册一个函数即可。元表应管理一个实例,而不是它的创建。

不要忘记添加一个析构函数( __gc)以释放您分配的 C++ 类实例。

最后,您只需注册函数 Object,它在第一次调用时创建元表。

#define LUA_META_OBJECT "Object"

class Object {
private:
    double x;
public:
    Object(double x) : x(x){}
};

static int object_free(lua_State* L)
{
    delete *static_cast<Object**>(luaL_checkudata(L, 1, LUA_META_OBJECT));
    return 0;
}

static int object_new(lua_State* L)
{
    const lua_Number x = luaL_checknumber(L, 1);
    *static_cast<Object**>(lua_newuserdata(L, sizeof(Object*))) = new Object(x);
    if(luaL_newmetatable(L, LUA_META_OBJECT)){
        static const luaL_Reg functions[] =
        {
            {"__gc", object_free},
            {nullptr, nullptr}
        };
        luaL_setfuncs(L, functions, 0);
        lua_pushvalue(L, -1);
        lua_setfield(L, -2, "__index");
    }
    lua_setmetatable(L, -2);
    return 1;
}

...
    lua_register(L, "Object", object_new);
...
2016-02-24 05:00:17
用户3586583
用户3586583

我非常喜欢 Youka 的回答,但我想以不同的方式实现它。这里的大部分代码都来自于 Youka 的回答。

Youka 也做了一个重要的注释,你需要创建一个 __gc 元方法来避免在对象实例超出范围时产生内存泄漏。

在下面的代码中,我创建了一个独立的函数,将元表推入堆栈,并在元表不存在时创建它。元表本身有一个元表,其中包含一个 __call 元方法,用于调用对象创建。这种行为应该允许您使用 o = Object(42) 来创建新对象。我还包含了使用 o = Object.new(42) 仍然具有旧功能的代码。 在代码底部,元表被推一次,以初始化元表。

#define LUA_META_OBJECT "Object"

class Object {
private:
    double x;
public:
    Object(double x) : x(x){}
};

// declaration so we can use this in object_new function
int push_object_metatable(lua_State* L);

static int object_free(lua_State* L)
{
    delete *static_cast<Object**>(luaL_checkudata(L, 1, LUA_META_OBJECT));
    return 0;
}

static int object_new(lua_State* L)
{
    const lua_Number x = luaL_checknumber(L, 1);
    *static_cast<Object**>(lua_newuserdata(L, sizeof(Object*))) = new Object(x);
    push_object_metatable(L);
    lua_setmetatable(L, -2);
    return 1;
}

static int call_object_new(lua_State* L)
{
    lua_remove(L, 1);
    object_new(L);
    return 1;
}

// Pushes the metatable for Object and creates if it doesnt exist yet
int push_object_metatable(lua_State* L)
{
    if(luaL_newmetatable(L, LUA_META_OBJECT)){
        static const luaL_Reg functions[] =
        {
            {"new", object_new},
            {"__gc", object_free},
            {nullptr, nullptr}
        };
        luaL_setfuncs(L, functions, 0);
        lua_pushvalue(L, -1);
        lua_setfield(L, -2, "__index");

        // Set a metatable for the metatable :D
        // This allows using Object(42) to make new objects
        lua_newtable(L);
        lua_pushcfunction(L, call_object_new);
        lua_setfield(L, -2, "__call");
        lua_setmetatable(L, -2);
    }
    return 1;
}

...
    // Register Object metatable for lua (Create and push it)
    push_object_metatable(L);
    lua_setglobal(L, LUA_META_OBJECT);
...
2016-02-24 17:52:50