关于lua:如何遍历使用luaL_ref和luaL_unref修改的表?

How to iterate over a table modified with luaL_ref and luaL_unref?

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

由于luaL_unref,对表进行迭代是一个问题。在Lua中,通常通过分配nil来"删除"表字段(因为未初始化的表字段的值为nil)。 next功能足够智能,可以跳过nil。我本来希望luaL_unrefnil分配给未引用的表字段,但是似乎分配了一个整数。该整数的值似乎没有记载。

考虑以下代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
/* 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.\
"
);

输出:

1
2
3
4
5
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吗?

编辑

首先,谢谢您的回复!

也许我问错了问题。

请允许我更具体一点。我有一个客户端用户数据,它需要管理许多订阅用户数据。订阅是通过客户端的订阅方法创建的。订阅通过客户端的unsubscribe方法删除。订阅用户数据基本上是实现细节,因此它们不会在客户端API中公开。相反,客户端API使用订阅引用,因此使用luaL_ref填充订阅表。

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

抓住了。我希望客户端自动取消订阅__gc上的所有剩余订阅(否则用户将收到段错误)。所以看来我需要遍历订阅。我真的在这里滥用API吗?有一个更好的方法吗?


luaL_refluaL_unref函数在lauxlib.c中定义。他们通过跟踪自由参考列表来工作,这些参考列表存储在正在操作的表中。这些功能相对较短,因此我将在此处包括它们。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
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_refluaX_unref来解决这个问题。它们的作用与luaL_refluaL_unref几乎相同,不同之处在于它们将免费的引用列表存储在单独的表中,其中l作为列表。 (对于我的项目,我将它们放在单独的源文件中,并根据需要包含它们。我不建议修改lauxlib.c。)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
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 */
  }
}

现在查看用法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
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.\
"
);

输出:

1
2
3
4
5
6
7
8
9
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.

为了"确保键返回的唯一性",luaL_ref必须维护已用键以及随后luaL_unref删除键的列表。该列表似乎从t[0]开始,一直持续到索引链指向nil。该列表与活动引用保存在同一表中。

如果您要继续按照Nicol的观察来"滥用API",并依赖于实现定义的行为,则可以按照此链接列表查看在迭代表时键是否为已删除的引用。为了避免依赖于实现定义的行为并提高性能,可以保留一个单独的已删除引用表,并在迭代该表时跳过它们,尽管您需要忽略t[0]处的列表头。

如果您确实需要迭代引用,则最好完全使用其他机制。您可以简单地将所有引用/值对放在单独的表中,并在删除引用时将单独表中的值设置为nil。然后,您可以简单地迭代单独的表。