【发布时间】: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(),我确定的允许我重用元表的协议是这样的:
- 使用
luaL_newmetatable()注册单个元表对象。这个元表现在可以在userdata实例之间共享,因为注册元表时没有使用上值。 - 创建一个
userdata值(lua_newuserdata())。 - 将正确的元表分配给
userdata值 (lua_setmetatable())。 - 使用一个上值
userdata创建并填充实例方法调用/属性表。 - 在
userdata上使用lua_setuservalue()来存储对每个实例属性/方法表的引用。 - 更改各种元方法(例如
__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_newmetatable和luaL_getmetatable将注册表中的mt 松鼠并再次检索它。 -
@greatwolf 您的第二条评论是正确的。我希望能够在不使用
udata_obj:method()的情况下拨打udata_obj.method()。前者依赖于upvalues,后者是传递udata_obj作为第一个参数的语法糖。.method()的一个副产品是每个实例都需要自己的元表和自己的闭包。有没有办法在 C 方面让.method()表现得像:method()?如果一个 upvalue 可以绑定到一个值,而不是一个闭包/函数调用,我会很好。