在lua中有一种方法可以将upvalue绑定到userdata值而不是函数吗?

时间:2013-10-09 03:49:09

标签: c++ lua metatable

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

在一个理想的世界 - 以及我希望用这个问题发掘的东西 - 是否有办法将upvalue与值(例如userdata)相关联,而不通过upvalue将其与函数调用相关联?我希望有一个技巧可以让我继续使用基于闭包的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的“普通”metatable样式的情况类似,但我想保持封闭性并避免引入冒号语法。

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


更新(2013-10-10):基于@lhf's answer to use lua_setuservalue() and lua_getuservalue()我已经确定的协议允许我重复使用metatables:

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

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

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

2 个答案:

答案 0 :(得分:1)

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

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

答案 1 :(得分:0)

由于某些原因,我认为你不应该完全这样做。

  1. 如果你调用object.method(),并且尝试从创建的实例推断出对象,那么就会阻止你在给定的任何对象上传递函数指针的能力。
  2. 您对永远不会收集垃圾的对象进行循环引用(每个实例的函数都指向实例)。
  3. 从插槽1中获取对象,并检查其类型是否与您的用户数据匹配。 (luaL_checkudata)

    如果它不是一个对象和tostring被调用,例如,只输出它的一类对象名,而不是实例细节。它更有意义,如果对象报告它实际上是什么,可能会使调试更简单,而不是试图过于聪明和误导你。