关于C#:在lua中,有没有一种方法可以将upvalue绑定到userdata值而不是函数?

In lua is there a way to bind an upvalue to a userdata value instead of a function?

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

在理想的世界中-我希望通过这个问题来揭示-有没有一种方法可以将upvalue与值(例如userdata)相关联,而无需通过upvalue将其与函数调用相关联?我希望有一个技巧可以让我继续使用基于闭包的lua OOP并在实例之间共享相同的元表。我不乐观,但我想问一下有人是否有建议或不明显的技巧。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
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|
}

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

1
2
3
4
5
6
7
8
9
10
// 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的" normal "元表样式的情况,但是我想保持事物基于闭包并且避免引入冒号语法。

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

更新(2013-10-10):基于@lhf \\使用lua_setuservalue()lua_getuservalue()的答案,我已经确定的允许我重用元表的协议是:

  • 使用luaL_newmetatable()注册单个元表对象。现在可以在userdata实例之间共享该元表,因为在注册该元表时不使用任何升值。
  • 创建一个userdata值(lua_newuserdata())。
  • 将正确的元表分配给userdata值(lua_setmetatable())。
  • 用一个升值userdata创建并填充实例方法调用/属性表。
  • userdata上使用lua_setuservalue()来存储对每个实例属性/方法表的引用。
  • 更改各种元方法(例如__index)以使用userdata \\的uservalue表。
  • 因此:

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

    仍然无法避免为每个用户数据创建方法/属性表,但是这种开销是正常的。如果obj.myMethod()在不使用:的情况下以某种方式将obj传递给function myMethod()会很好,但这正是:的工作,因为这不可能通过另一种方式进行(除非您确实使用了的值)。


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

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


    出于某些原因,我认为您不应该尝试完全做到这一点。

  • 如果调用object.method(),并尝试从创建的实例中推断出该对象,则将阻止在给定对象上传递行为的函数指针。
  • 您具有对对象的循环引用,这些对象将永远不会收集垃圾(每个实例的函数都指向该实例)。
  • 只需从插槽1中获取对象,并检查其类型是否与您的userdata相匹配。 (luaL_checkudata)

    例如,如果它不是对象并且调用了tostring,则只需输出其一个对象名称类,而不是实例详细信息即可。这更有意义,并且如果对象报告其实际状态,则可能会使调试更简单,而不是试图变得太聪明和误导您。