如何遍历使用luaL_ref和luaL_unref修改的表?

我使用Lua的C API来扩展Lua。在我的模块中,我想要使用luaL_ref填充一个表,并使用luaL_unref删除字段。我还希望能够遍历此表,希望使用lua_next

因为luaL_unref的原因,遍历表是一个问题。在Lua中,“删除”表字段通常是通过赋值为nil来完成的(因为未初始化的表字段会计算为nil)。next函数足够智能以跳过nil。我本来以为luaL_unref会将未引用的表字段分配给nil,但它似乎分配了一个整数。这个整数的值似乎没有被记录在案。

请考虑以下代码:

/* tableDump 打印表: */
/* 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_refluaL_unref吗?


编辑

首先,感谢您的回复!

也许我问错了问题。

让我更具体一些。我有一个客户端userdata,需要管理许多订阅userdata。通过客户端的subscribe方法创建订阅。通过客户端的unsubscribe方法删除订阅。订阅userdatas基本上是一种实现细节,因此它们不会在客户端API中暴露。相反,客户端API使用订阅引用,因此使用luaL_ref填充订阅表。

ref = client:sub(channel, func)
cleint:unsub(ref)

这就是问题所在。我希望客户端在__gc上自动取消所有剩余的订阅(否则用户将获得段错误)。因此,似乎我需要遍历订阅。我真的在滥用API吗?还有其他更好的方法吗?

点赞
用户33252
用户33252

为了“确保返回的关键字的唯一性”,luaL_ref必须维护一个已使用并随后被luaL_unref删除的键的列表。看起来,这个列表从t[0]开始,一直延续到索引的链路指向一个nil。这个列表保存在与活动参考相同的表中。

如果你想像Nicol观察到的那样继续“滥用API”,并且依赖于实现定义的行为,你可以沿着这个链接列表查看一个键是否为已删除的参考,并在迭代表时进行跳过。或者为了避免依赖于实现定义的行为并且具有更高的性能,你可以保留一个单独的已删除参考表,在迭代表时跳过它们,虽然你需要忽略t[0]处的列表头。

如果你真的需要迭代引用,你可能最好使用完全不同的机制。你可以简单地将所有的参考/值对放在一个单独的表中,并在删除参考时将单独表中的值设置为nil。然后你可以简单地迭代单独的表。

2012-10-07 19:41:08