lua C++对象内存管理

1:tolua++如何管理对象的生命周期

一般情况下,当lua里对c++对象的引用变量可以被垃圾回收时,tolua++只是简单的释放userdata占用的4字节指针地址内存。但是也可以通过绑定或者代码指定的方式,让tolua++真正释放对象所占内存。

绑定的方式,是指在将c++类型构造函数使用tolua++导出到lua里时,tolua++会自动生成new_local方法。如果在lua代码里,用这个方法新建对象时,tolua++会调用tolua_register_gc方法,指明回收对象时回收对象内存。

在c++代码里,使用tolua_pushusertype_and_takeownership;在lua代码里,使用tolua.takeownership,都可以达到同样的目的。

对于这些指定由tolua++回收内存的对象,如果其类型的析构函数也通过tolua++导出了,则在回收内存时,会通过delete运算符,调用对象的析构函数。否则只会使用free方法回收。

tolua_register_gc方法,做的事情,是以对象指针为键,以对象metatable为值,将键值对存储在tolua_gc表里。在对象类型的metatable表的__gc方法里,tolua++会检查tolua_gc表是否包含以这个地址为键的表项。包含的话才会进行上述的内存回收工作。


2:userdata的回收

Lua 提供了一个自动的内存管理。这就是说你不需要关心创建新对象的分配内存操作,也不需要在这些对象不再需要时的主动释放内存。 Lua 通过运行一个垃圾收集器来自动管理内存,以此一遍又一遍的回收死掉的对象(这是指 Lua 中不再访问的到的对象)占用的内存。 Lua 中所有对象都被自动管理,包括: table, userdata、 函数、线程、和字符串。

Lua 实现了一个增量标记清除的收集器。它用两个数字来控制垃圾收集周期: garbage-collector pause 和 garbage-collector step multiplier 。

garbage-collector pause 控制了收集器在开始一个新的收集周期之前要等待多久。随着数字的增大就导致收集器工作工作的不那么主动。小于 1 的值意味着收集器在新的周期开始时不再等待。当值为 2 的时候意味着在总使用内存数量达到原来的两倍时再开启新的周期。

step multiplier 控制了收集器相对内存分配的速度。更大的数字将导致收集器工作的更主动的同时,也使每步收集的尺寸增加。小于 1 的值会使收集器工作的非常慢过在 C 中调用 lua_gc 或是在 Lua 中调用 collectgarbage 来改变这些数字。两者都接受百分比数值(因此传入参数 100 意味着实际值 1 )。通过这些函数,你也可以直接控制收集器(例如,停止或是重启)。 

使用 C API ,你可以给 userdata 设置一个垃圾收集的元方法。这个元方法也被称为结束子。结束子允许你用额外的资源管理器和 Lua 的内存管理器协同工作(比如关闭文件、网络连接、或是数据库连接,也可以说释放你自己的内存)。

一个 userdata 可被回收,若它的 Metatable 中有 __gc 这个域 ,垃圾收集器就不立即收回它。取而代之的是,Lua 把它们放到一个列表中。最收集结束后,Lua 针对列表中的每个 userdata 执行了下面这个函数的等价操作:

     function gc_event (udata)
       local h = metatable(udata).__gc 

if h then,可能导致收集器永远都结束不了当前周期。缺省值为 2 ,这意味着收集器将以内存分配器的两倍速运行。

你可以通

         h(udata)
       end
     end

 

在每个垃圾收集周期的结尾,每个在当前周期被收集起来的 userdata 的结束子会以它们构造时的逆序依次调用。也就是说,收集列表中,最后一个在程序中被创建的 userdata 的结束子会被第一个调用。

 

3:代码

TOLUA_API void tolua_cclass (lua_State* L, const char* lname, const char* name, const char* base, lua_CFunction col)
{
 //...
    push_collector(L, name, col);
  //...
    /* now we also need to store the collector table for the const
       instances of the class */
    push_collector(L, cname, col);
}


注册类的时候,向metatable中写入.collector域。
TOLUA_API void tolua_classevents (lua_State* L)
{
//..
    lua_pushstring(L,"__gc");
    lua_pushstring(L, "tolua_gc_event");
    lua_rawget(L, LUA_REGISTRYINDEX);
    lua_rawset(L,-3);
}
_R.tolua_gc_event = closure{ func:class_gc_event, upvalue:上述两个表格 }, 这是挂在每个类对应的metatable上的__gc方法。
tolua_gc_event就是一个闭包,下面看看它是如何创建的:

tolua_open(L)
  /* create gc_event closure */
        lua_pushstring(L, "tolua_gc_event");
        lua_pushstring(L, "tolua_gc");
        lua_rawget(L, LUA_REGISTRYINDEX);
        lua_pushstring(L, "tolua_super");
        lua_rawget(L, LUA_REGISTRYINDEX);
        lua_pushcclosure(L, class_gc_event, 2);
        lua_rawset(L, LUA_REGISTRYINDEX);
里面有tolua_gc和tolua_super两个表作为upvalue,class_gc_event作为gc的执行函数。
所以在注册类的时候__gc的元方法就是一个闭包tolua_gc_event。
在tolua管理对象的生命周期讲过,如果一个userdata会被垃圾回收,那么会调用__gc元方法。
*/
TOLUA_API int class_gc_event (lua_State* L)  一个闭包函数,还有两个upvalue。
{
    void* u = *((void**)lua_touserdata(L,1));
    int top;
    lua_pushvalue(L, lua_upvalueindex(1));--->tolua_gc表
    lua_pushlightuserdata(L,u);
    lua_rawget(L,-2);            /* stack: gc umt    */ 获取tolua_gc表中userdata的metatable,gc[userdata] = metatable
    lua_getmetatable(L,1);       /* stack: gc umt mt */
    /*fprintf(stderr, "checking type\n");*/
    top = lua_gettop(L);
    if (tolua_fast_isa(L,top,top-1, lua_upvalueindex(2))) /* make sure we collect correct type */
    {
        /*fprintf(stderr, "Found type!\n");*/
        /* get gc function */
        lua_pushliteral(L,".collector");  检查.collector域是否是一个函数
        lua_rawget(L,-2);           /* stack: gc umt mt collector */
        if (lua_isfunction(L,-1)) {
            /*fprintf(stderr, "Found .collector!\n");*/
        }
        else {
            lua_pop(L,1);
            /*fprintf(stderr, "Using default cleanup\n");*/
            lua_pushcfunction(L,tolua_default_collect);
        }
        lua_pushvalue(L,1);         /* stack: gc umt mt collector u */
        lua_call(L,1,0);  调用注册类的时候写入.collector域的函数。
        lua_pushlightuserdata(L,u); /* stack: gc umt mt u */
        lua_pushnil(L);             /* stack: gc umt mt u nil */
        lua_rawset(L,-5);           /* stack: gc umt mt */ 将userdata从tolua_gc表中移除
    }
    lua_pop(L,3);
    return 0;
}

tolua_gc表:tolua_gc表中, 以C++指针为键, 值为metatable, 通过class_gc_event进行自动释放
TOLUA_API int tolua_register_gc (lua_State* L, int lo)
{
    int success = 1;
    void *value = *(void **)lua_touserdata(L,lo);
    lua_pushstring(L,"tolua_gc");
    lua_rawget(L,LUA_REGISTRYINDEX);
    lua_pushlightuserdata(L,value);
    lua_rawget(L,-2);
    if (!lua_isnil(L,-1)) /* make sure that object is not already owned */
        success = 0;
    else
    {
        lua_pushlightuserdata(L,value);
        lua_getmetatable(L,lo);
        lua_rawset(L,-4);
    }
    lua_pop(L,2);
    return success;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值