C语言 如何在泛型迭代器函数中进行清理,当它被打破时

wljmcqd8  于 12个月前  发布在  其他
关注(0)|答案(2)|浏览(73)

我在C中实现了一个泛型for迭代器函数。返回泛型for循环的迭代器函数的函数首先使用lua_newuserdata()为迭代器函数分配状态信息,如下所示:

struct mystate *s = (struct mystate *) lua_newuserdata(L, sizeof(struct mystate));
s->firstcall = 1;

然后我把指针作为一个向上值推到我的C闭包,像这样:

lua_pushvalue(L, lua_gettop(L));
lua_pushcclosure(L, iteratorfunction, 1);

我的迭代器函数然后从第一个上值检索指针,并对其进行一些分配,如下所示:

static int iteratorfunction(lua_State *L)
{
    struct mystate *s = (struct mystate *) lua_touserdata(L, lua_upvalueindex(1));

    if(s->firstcall) {
       s->file = fopen(...);
       s->data = malloc(...);
       ...
       s->firstcall = 0;
    }

    ...
}

我的问题是如果脚本使用break在通用for循环完成之前退出它,我应该如何确保s->files->data被正确释放?在这种情况下,我的iteratorfunction不能做清理,因为它没有被调用所有的方式。相反,泛型for循环在迭代器函数完成之前就退出了。
准确地说,如果脚本使用break在完成之前退出我的泛型for循环,我如何确保fclose()s->file上被调用,free()s->data上被调用?
我看过Lua源代码,io.lines似乎使用了元表和垃圾收集器来确保文件句柄被关闭,但我真的不明白这是如何工作的。它看起来相当复杂,我不确定我是否应该以类似的方式这样做,或者是否有一个更容易的解决方案。
请注意,我仍然在Lua 5.0上,所以任何解决方案的建议都应该记住这一点。谢谢你,谢谢

jjhzyzn0

jjhzyzn01#

为了回答我自己的问题,我现在使用Egor建议的终结器(__gc元方法)。在代码中,它看起来像这样:
首先,我们需要创建一个元表,我们可以使用它的__gc来进行清理:

#define PRIVATEHANDLE "PRIVATE*"

luaL_newmetatable(L, PRIVATEHANDLE);
lua_pushliteral(L, "__index");
lua_pushvalue(L, -2);
lua_rawset(L, -3);  

lua_pushstring(L, "__gc");
lua_pushcclosure(L, iteratorfunction_gc, 0);
lua_settable(L, -3);

lua_pop(h, 1);

然后我们需要将用户数据与元表关联起来,这样一旦Lua决定删除用户数据,我们的__gc方法就会被调用,因此我们这样做:

struct mystate *s = (struct mystate *) lua_newuserdata(L, sizeof(struct mystate));
memset(s, 0, sizeof(struct mystate));

luaL_getmetatable(L, PRIVATEHANDLE);
lua_setmetatable(L, -2);

最后,我们需要实现iteratorfunction_gc来进行实际的清理。这可能看起来像这样:

static int iteratorfunction_gc(lua_State *L)
{
    struct mystate *s = (struct mystate *) luaL_checkudata(L, 1, FILEHANDLE);

    if(s->file) fclose(s->file);
    if(s->data) free(s->data);

    ...additional cleanup here...

    return 0;
}

测试了它,这做的工作真的很好。问题解决了不知道为什么人们试图关闭这个问题。

bz4sfanl

bz4sfanl2#

不幸的是,Lua没有提供一种在自定义迭代器中进行清理的方法。然而,有一个非常简单的替代方案:请改用函数样式的循环。
例如,而不是这个结构:

for i, line in mylines() do
    if line == "" then break end
    print(line)
end

你可以这样实现它:

mylines(function(i, line)
    if line == "" then return true end
    print(line)
end)

在这种情况下,mylines()可以很容易地检测到函数何时返回true,从而知道何时取消循环并执行清理。
实际上我更喜欢函数式的循环,因为它们也允许返回其他值来指示循环内部的状态,所以这是一种有效的双向通信方式。对于自定义迭代器,您需要采取一些更简单的方法,例如传递状态表或调用终结器函数。

for i, line, finish in mylines() do
    if line == "" then
        finish()
        break
    else
        print(line)
    end
end

函数式循环的唯一缺点是,你不能在循环上下文中从 parent 函数返回。但根据我的经验,这是很少需要的。

相关问题