通过 Lua 包装器获取 C++ 类状态会产生垃圾值

我正在尝试通过 Lua 的 C API 将简单的 C++ 类公开给 Lua。我知道已有的库已经解决了这个问题,但我想了解如何只使用 C API 来完成这个任务。

这是我的程序:

#define LUA_LIB

#include <cassert>

extern "C"
{
  #include "lua.h"
  #include "lauxlib.h"
}

namespace
{

class Foo
{
public:
  Foo(int state)
  : _state(state)
  {}

  int get_state() const
  { return _state; }

private:
  int _state;
};

int foo_new(lua_State *L)
{
  assert(lua_gettop(L) == 2);

  luaL_checktype(L, 1, LUA_TTABLE);

  lua_newtable(L);
  lua_pushvalue(L, 1);
  lua_setmetatable(L, -2);
  lua_pushvalue(L, 1);
  lua_setfield(L, 1, "__index");

  int state = lua_tointeger(L, 2);

  auto **__obj = static_cast<Foo **>(lua_newuserdata(L, sizeof(Foo *)));
  *__obj = new Foo(state);

  lua_setfield(L, -2, "__self");

  return 1;
}

int foo_get_state(lua_State *L)
{
  assert(lua_gettop(L) == 1);

  luaL_checktype(L, 1, LUA_TTABLE);

  lua_getfield(L, 1, "__self");

  auto *__obj = static_cast<Foo *>(lua_touserdata(L, -1));
  lua_pushinteger(L, __obj->get_state());

  return 1;
}

luaL_Reg const foo_functions[] = {
  {"new", foo_new},
  {"get_state", foo_get_state},
  {nullptr, nullptr}
};

} // namespace

extern "C"
{

LUALIB_API int luaopen_foo(lua_State *L)
{
  luaL_newlib(L, foo_functions);
  lua_setglobal(L, "Foo");

  return 1;
}

} // extern "C"

foo_new 创建了一个 Lua 表,它将 C++ 的 Foo 对象作为 userdata 存储在其 __self 字段中。我在 Lua 中使用 local foo = Foo:new(1) 调用此函数。看起来它有效,但是随后调用 print(foo:get_state()) 并不返回 1,而是一些垃圾值。这是为什么,如何解决它?

点赞
用户12918181
用户12918181

以下有 2 个问题:

  • 你在 lua 函数 new 中分配内存 auto **__obj,但在 lua 函数 foo_get_state 中是作为 auto *__obj 访问的。
  • 内存泄漏 - 你缺少为 new Foo() 收集垃圾的功能。

在将元表与数据对象混合在一起是不好的,但在你的情况下应该像这样工作:

int foo_new(lua_State *L)
{
  assert(lua_gettop(L) == 2);
  luaL_checktype(L, 1, LUA_TTABLE);

  lua_newtable(L);
  lua_pushvalue(L, 1);
  lua_setmetatable(L, -2);
  lua_pushvalue(L, 1);
  lua_setfield(L, 1, "__index");

  int state = lua_tointeger(L, 2);

  auto *__obj = static_cast<Foo *>(lua_newuserdata(L, sizeof(Foo)));
  new(__obj) Foo(state);
  lua_setfield(L, -2, "__self");

  return 1;
}

int foo_get_state(lua_State *L)
{
  assert(lua_gettop(L) == 1);
  luaL_checktype(L, 1, LUA_TTABLE);

  lua_getfield(L, 1, "__self");
  auto *__obj = static_cast<Foo *>(lua_touserdata(L, -1));
  assert(__obj != nullptr);
  lua_pushinteger(L, __obj->get_state());
  return 1;
}

int foo_gc(lua_State *L)
{
  assert(lua_gettop(L) == 1);
  luaL_checktype(L, 1, LUA_TTABLE);

  lua_getfield(L, 1, "__self");
  auto *__obj = static_cast<Foo *>(lua_touserdata(L, -1));
  if (__obj)
    __obj->~Foo();

  lua_pushnil(L);
  lua_setfield(L, -3, "__self");
  return 0;
}

luaL_Reg const foo_functions[] = {
  {"new", foo_new},
  {"get_state", foo_get_state},
  {"__gc", foo_gc},
  {nullptr, nullptr}
};

如果你的工作涉及简单的数据而不是内存/句柄分配,那么 __gc 是不必要的,因为 lua 垃圾收集会释放分配的内存。

2021-01-30 17:30:52