我正在使用Lua的C API来扩展Lua。在我的模块中,我想使用luaL_ref
填充表格,并使用luaL_unref
删除字段。我还希望能够遍历此表,希望使用lua_next
。
由于luaL_unref
,对表进行迭代是一个问题。在Lua中,通常通过分配nil
来“删除”表字段(因为未初始化的表字段评估为nil
)。 next
功能非常智能,可以跳过nil
。我希望luaL_unref
将nil
分配给未引用的表字段,但它似乎分配了一个整数。这个整数的值似乎没有记录。
请考虑以下代码:
/* tableDump prints a table: */
/* key: value, key: value, ... */
lua_newtable(L);
lua_pushboolean(L, 0);
int ref1 = luaL_ref(L, -2);
lua_pushinteger(L, 7);
int ref2 = luaL_ref(L, -2);
lua_pushstring(L, "test");
int ref3 = luaL_ref(L, -2);
tableDump(L, -1);
luaL_unref(L, -1, ref1);
tableDump(L, -1);
luaL_unref(L, -1, ref3);
tableDump(L, -1);
luaL_unref(L, -1, ref2);
tableDump(L, -1);
printf("done.\n");
输出:
1: false, 2: 7, 3: `test',
3: `test', 2: 7, 0: 1,
3: 1, 2: 7, 0: 3,
3: 1, 2: 3, 0: 2,
done.
这里发生了什么?我怎么能解决这个问题?是否有一些技巧来迭代引用并忽略未引用的?我是否必须停止使用luaL_ref
和luaL_unref
?
修改
首先,谢谢您的回复!
也许我问过错误的问题。
请允许我更具体一点。我有一个客户端userdata需要管理许多订阅userdatas。订阅由客户端的订阅方法创建。订阅将通过客户端的取消订阅方法删除。订阅userdatas基本上是一个实现细节,因此它们不会在客户端API中公开。相反,客户端API使用订阅引用,因此使用luaL_ref
来填充订阅表。
ref = client:sub(channel, func)
cleint:unsub(ref)
这是一个问题。我希望客户端自动取消订阅__gc上的所有剩余订阅(否则用户将获得段错误)。所以我似乎需要迭代订阅。我真的在这里滥用API吗?有更好的方法吗?
答案 0 :(得分:2)
为了“确保其返回的密钥的唯一性”,luaL_ref
必须维护已删除密钥和随后luaL_unref
个密钥的列表。该列表似乎从t[0]
开始,一直持续到索引链导致nil
。此列表与活动引用保存在同一个表中。
如果您想继续“滥用API”,正如Nicol所观察到的那样,并依赖于实现定义的行为,您可以按照此链接列表查看在迭代表时密钥是否为已删除的引用。或者为了避免依赖于实现定义的行为,并且性能更高,您可以保留一个单独的已删除引用表,并在迭代表时跳过它们,尽管您需要忽略t[0]
处的列表头。
如果你真的需要迭代引用,你可能最好完全使用不同的机制。您可以简单地将所有引用/值对放在单独的表中,并在删除引用时将单独表中的值设置为nil
。然后你可以简单地迭代单独的表。
答案 1 :(得分:2)
luaL_ref
和luaL_unref
函数在lauxlib.c
中定义。它们通过跟踪免费参考列表来工作,这些列表存储在他们正在操作的表中。这些功能相对较短,所以我将它们包含在这里。
LUALIB_API int luaL_ref (lua_State *L, int t) {
int ref;
t = abs_index(L, t);
if (lua_isnil(L, -1)) {
lua_pop(L, 1); /* remove from stack */
return LUA_REFNIL; /* 'nil' has a unique fixed reference */
}
lua_rawgeti(L, t, FREELIST_REF); /* get first free element */
ref = (int)lua_tointeger(L, -1); /* ref = t[FREELIST_REF] */
lua_pop(L, 1); /* remove it from stack */
if (ref != 0) { /* any free element? */
lua_rawgeti(L, t, ref); /* remove it from list */
lua_rawseti(L, t, FREELIST_REF); /* (t[FREELIST_REF] = t[ref]) */
}
else { /* no free elements */
ref = (int)lua_objlen(L, t);
ref++; /* create new reference */
}
lua_rawseti(L, t, ref);
return ref;
}
LUALIB_API void luaL_unref (lua_State *L, int t, int ref) {
if (ref >= 0) {
t = abs_index(L, t);
lua_rawgeti(L, t, FREELIST_REF);
lua_rawseti(L, t, ref); /* t[ref] = t[FREELIST_REF] */
lua_pushinteger(L, ref);
lua_rawseti(L, t, FREELIST_REF); /* t[FREELIST_REF] = ref */
}
}
这些功能非常聪明,因为它们不需要额外的存储空间(除了它们正在运行的表格)。但是,有时不希望将空闲引用列表存储在与引用值相同的表中,例如在需要迭代引用值时。
我写了luaX_ref
和luaX_unref
来解决这个问题。它们的工作方式与luaL_ref
和luaL_unref
几乎相同,只是它们将其免费参考列表存储在单独的表中,l
用于列表。 (对于我的项目,我将它们放在一个单独的源文件中并根据需要包含它们。我不建议修改lauxlib.c
。)
static int abs_index(lua_State * L, int i){
return i > 0 || i <= LUA_REGISTRYINDEX ? i : lua_gettop(L) + i + 1;
}
LUALIB_API int luaX_ref(lua_State *L, int t, int l){
int ref;
t = abs_index(L, t);
l = abs_index(L, l);
if(lua_isnil(L, -1)){
lua_pop(L, 1); /* remove from stack */
return LUA_REFNIL; /* 'nil' has a unique fixed reference */
}
lua_rawgeti(L, l, FREELIST_REF); /* get first free element */
ref = (int) lua_tointeger(L, -1); /* ref = l[FREELIST_REF] */
lua_pop(L, 1); /* remove it from stack */
if(ref != 0){ /* any free element? */
lua_rawgeti(L, l, ref); /* remove it from list */
lua_rawseti(L, l, FREELIST_REF); /* (l[FREELIST_REF] = l[ref]) */
}else{ /* no free elements */
ref = (int)lua_objlen(L, l);
ref++; /* create new reference */
}
lua_pushboolean(L, 1);
lua_rawseti(L, l, ref); /* l[ref] = true */
lua_rawseti(L, t, ref); /* t[ref] = value */
return ref;
}
LUALIB_API void luaX_unref(lua_State *L, int t, int l, int ref){
if(ref >= 0){
t = abs_index(L, t);
l = abs_index(L, l);
lua_rawgeti(L, l, FREELIST_REF);
lua_rawseti(L, l, ref); /* l[ref] = l[FREELIST_REF] */
lua_pushinteger(L, ref);
lua_rawseti(L, l, FREELIST_REF); /* l[FREELIST_REF] = ref */
lua_pushnil(L);
lua_rawseti(L, t, ref); /* t[ref] = nil */
}
}
现在看看用法:
lua_newtable(L); /* 1 */
lua_newtable(L); /* 2 */
lua_pushboolean(L, 0);
int ref1 = luaX_ref(L, 1, 2);
lua_pushinteger(L, 7);
int ref2 = luaX_ref(L, 1, 2);
lua_pushstring(L, "test");
int ref3 = luaX_ref(L, 1, 2);
tableDump(L, 1);
tableDump(L, 2);
luaX_unref(L, 1, 2, ref1);
tableDump(L, 1);
tableDump(L, 2);
luaX_unref(L, 1, 2, ref3);
tableDump(L, 1);
tableDump(L, 2);
luaX_unref(L, 1, 2, ref2);
tableDump(L, 1);
tableDump(L, 2);
printf("done.\n");
输出:
1: false, 2: 7, 3: `test',
1: true, 2: true, 3: true,
2: 7, 3: `test',
3: true, 2: true, 0: 1,
2: 7,
3: 1, 2: true, 0: 3,
3: 1, 2: 3, 0: 2,
done.