5

在下面的示例userdata中,创建了一个类型的值,并使用调用MyType的元函数创建了一个表。该代码创建了一个基于闭包的 lua OOP。我对提供的示例的抱怨是,似乎只有一种方法可以通过 upvalues 与方法调用关联。就其本身而言,这不是问题,除非我想跨实例共享相同的元表。__tostringLI_MyType__tostringuserdata

在一个理想的世界中——以及我希望通过这个问题挖掘出的东西——有没有办法将一个上值与一个值(例如userdata)相关联,而不用通过一个上值将它与函数调用相关联?我希望有一个技巧可以让我继续使用基于闭包的 lua OOP跨实例共享相同的元表。我并不乐观,但我想我会问一下是否有人有建议或不明显的技巧。

using FuncArray = std::vector<const ::luaL_Reg>;
static const FuncArray funcs = {
  { "__tostring", LI_MyType__tostring },
};

int LC_MyType_newInstance(lua_State* L) {
  auto userdata = static_cast<MyType*>(lua_newuserdata(L, sizeof(MyType)));
  new(userdata) MyType();

  // Create the metatable
  lua_createtable(L, 0, funcs.size());     // |userdata|table|
  lua_pushvalue(L, -2);                    // |userdata|table|userdata|
  luaL_setfuncs(L, funcs.data(), 1);       // |userdata|table|
  lua_setmetatable(L, -2);                 // |userdata|
  return 1;
}

int LI_MyType__tostring(lua_State* L) {
  // NOTE: Blindly assume that upvalue 1 is my userdata
  const auto n = lua_upvalueindex(1);
  lua_pushvalue(L, n);                     // |userdata|
  auto myTypeInst = static_cast<MyType*>(lua_touserdata(L, -1));
  lua_pushstring(L, myTypeInst->str());    // |userdata|string|
  return 1;                                // |userdata|string|
}

我希望有一种方法可以执行类似的操作(这是伪代码!):

// Assume that arg 1 is userdata
int LI_MyType__tostring(lua_State* L) {
  const int stackPosition = -1;
  const int upvalueIndex = 1;
  const auto n = lua_get_USERDATA_upvalue(L, stackPosition, upvalueIndex);
  lua_pushvalue(L, n);                     // |userdata|
  auto myTypeInst = static_cast<MyType*>(lua_touserdata(L, -1));
  lua_pushstring(L, myTypeInst->str());    // |userdata|string|
  return 1;                                // |userdata|string|
}

我知道这类似于 OOP 的“正常”元表样式的情况,但我想保持基于闭包并避免引入冒号语法。

问这个问题的另一种方法是,有没有办法userdata在使用基于闭包的 OOP 时跨实例共享元表?从脚本方面使用 lua 的语法,我认为这是不可能的,但我希望在 C 方面可以做一些事情。


更新(2013 年 10 月 10 日):基于@lhf 的使用答案lua_setuservalue()lua_getuservalue()我确定的允许我重用元表的协议是这样的:

  1. 使用 . 注册单个元表对象luaL_newmetatable()。现在可以跨userdata实例共享此元表,因为注册元表时不使用上值。
  2. 创建一个userdata值 ( lua_newuserdata())。
  3. 将正确的元表分配给userdata值 ( lua_setmetatable())。
  4. 创建并使用一个上值填充实例方法调用/属性表,即userdata.
  5. 使用lua_setuservalue()onuserdata存储对每个实例属性/方法表的引用。
  6. 更改各种元方法(例如__index)以使用userdata的用户值表。

作为结果:

  • 元方法中从不使用 upvalues
  • upvalues 仅用于值的实例方法中
  • 给定类的每个实例只有一个额外的表

仍然不可能逃避为每个用户数据创建方法/属性表,但这种开销是名义上的。如果在不使用的情况下以某种方式obj.myMethod()传递会很好,但这正是这样做的,因为这是不可能的另一种方式(除非你确实使用了上值)。objfunction myMethod()::

4

2 回答 2

2

lua_setuservalue似乎正是您所需要的。当然也有lua_getuservalue

(我跳过 C++ 代码并回答标题中的问题。)

于 2013-10-09T12:20:19.250 回答
0

出于几个原因,我认为您不应该尝试完全这样做。

  1. 如果您调用 object.method(),并且您尝试从创建的实例中推断出对象,那么您将阻止您在给定的任何对象上传递函数指针的能力。
  2. 您有对永远不会被垃圾收集的对象的循环引用(每个实例的函数都指向该实例)。

只需从插槽 1 获取对象,并检查其类型是否与您的用户数据匹配。(luaL_checkudata)

例如,如果它不是对象并且调用了 tostring,则只需输出它的类对象名称,而不是实例详细信息。它更有意义,如果对象报告它实际上是什么,它可能会使调试更简单,而不是试图太聪明和误导你。

于 2013-10-09T08:34:48.440 回答