【问题标题】:In lua is there a way to bind an upvalue to a userdata value instead of a function?在 lua 中有没有办法将 upvalue 绑定到 userdata 值而不是函数?
【发布时间】:2013-10-16 06:20:12
【问题描述】:

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

在一个理想的世界中——以及我希望通过这个问题挖掘出的东西——有没有一种方法可以将一个上值与一个值(例如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 的“正常”元表样式的情况,但我希望保持基于闭包并避免引入冒号语法。

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


更新 (2013-10-10):基于@lhf's answer to use lua_setuservalue() and lua_getuservalue(),我确定的允许我重用元表的协议是这样的:

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

因此:

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

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

【问题讨论】:

  • 我没有遵循 lua_get_USERDATA_upvalue 应该做的事情。 LI_MyType__tostring 调用时,您的 udata 位于堆栈顶部,该 udata 的上值是否仅指其自身? (假设目前您可以将 upvalue 与 udata 相关联)
  • 您是否尝试在不使用udata_obj:method() 的情况下让udata_obj.method()udata_obj.method(udata_obj) 一样使用以下lua 语法?
  • 如果您希望MyType 的所有实例共享同一个元表,为什么不在创建时将它们与同一个元表关联呢?您可以使用luaL_newmetatableluaL_getmetatable 将注册表中的mt 松鼠并再次检索它。
  • @greatwolf 您的第二条评论是正确的。我希望能够在不使用udata_obj:method() 的情况下拨打udata_obj.method()。前者依赖于upvalues,后者是传递udata_obj 作为第一个参数的语法糖。 .method() 的一个副产品是每个实例都需要自己的元表和自己的闭包。有没有办法在 C 方面让 .method() 表现得像 :method()?如果一个 upvalue 可以绑定到一个值,而不是一个闭包/函数调用,我会很好。

标签: c++ lua metatable


【解决方案1】:

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

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

【讨论】:

  • 太棒了!我需要考虑如何使用这两个功能,但这似乎正是我想要的。在阅读 5.2 发行说明时,我一定对这些功能感到震惊。谢谢!
  • 添加了一个更新,解释了我如何使用这个答案。再次感谢!
【解决方案2】:

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

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

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

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

【讨论】:

  • 1) Lua 用于将消息传递给对象。 lua 中没有真正的工作。 2)对象之间没有循环引用,GC按预期工作。 luaL_checkudata() 仅适用于命名元表,但在闭包示例中,注册表中没有存储与给定元表关联的名称(例如 tname)。
  • 那么老实说,我会考虑命名元表。在这方面,我更喜欢事情是确定的。大多数时候你会没事的,但是如果你开始传递函数指针,并且不能确定它属于哪个对象,那么你将在调试它时做噩梦。
  • 我曾经这样做过,但使用 Google 的 glog 并在相关代码路径中撒上 DLOG_IF(INFO, trace_lua_calls) &lt;&lt; "My obj in func ... " &lt;&lt; lua_touserdata(L, n); 之类的东西并匹配内存地址会更容易。
猜你喜欢
  • 2012-08-31
  • 2010-09-13
  • 2023-02-09
  • 2020-10-05
  • 1970-01-01
  • 2019-12-25
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多