IT博客汇
  • 首页
  • 精华
  • 技术
  • 设计
  • 资讯
  • 扯淡
  • 权利声明
  • 登录 注册

    lua metatable使用和源码分析(一)

    hoterran发表于 2012-02-04 09:22:27
    love 0

    lua metatable(以下简称元表)类似c++的operator overloads,可以对复合结构进行操作,在lua里最常见的就是对表的操作.举例来说,当两个表作加法操作的时候,Lua会检查表的元表中是否有”__add”事件是否对应一个函数(metamethod)。如果存在Lua会调用这个函数来执行一次表的加法操作.

    __index和__newindex是表常常要添加的事件,用于处理键值在表无法被查找到之后的处理.

    hoterran@~/Projects/lua$ cat meta_test.lua
    t = {}
    t.a = 1
    
    print(t.a)
    print(t.b)
    setmetatable(t, {__index = function(x) return "test" end})
    print(t.b)
    
    hoterran@~/Projects/lua$ lua meta_test.lua
    1
    nil
    test

    可见当键值”b”在表内无法查找到之后会调用__index事件对应的匿名函数,后面我们从源码角度分析这个例子.

    接下来源码角度分析各种类型的元表.
    对于基本类型的元表,每类类型只有一个元表,某类类型共享同一个元表,基本类型的元表未设置之前都为空.

    lstate.c
    ================
    struct global_State{
      ....
      struct Table *mt[NUM_TAGS];  /* metatables for basic types */
      TString *tmname[TM_N];  /* array with tag-method names */
      ....
    }

    mt[NUM_TAGS]用于存储lua各种类型的元表,NUM_TAGS就lua类型个数加一.

    mt[1] = {__len = func1, __index = func2 ….}   /*boolean*/
    mt[2] = {__len = func1, __index = func2 ….}   /*lightuserdata*/
    ....
    mt[NUM_TAGS]

    tmname[TM_N]用于存储元表事件的字符串,位置与TMS一一对应,这张表在lua启动时会初始化.

    ltm.c
    ==========
    void luaT_init (lua_State *L) {
        static const char *const luaT_eventname[] = { /* ORDER TM */
        "__index", "__newindex",
        "__gc", "__mode", "__eq",
        "__add", "__sub", "__mul", "__div", "__mod",
        "__pow", "__unm", "__len", "__lt", "__le",
        "__concat", "__call"
    };
    int i;
    for (i=0; i G(L)->tmname[i] = luaS_new(L, luaT_eventname[i]);
        luaS_fix(G(L)->tmname[i]); /* never collect these names */
    }
    }

    查找某个对象类型的元表直接通过对象类型查找元表

    (L)->mt[ttype(o)];

    查找元表的事件函数,先从字节码的TMS查找tmname到事件字符串,然后到元表里以事件字符串作为键值查找函数.

    luaH_getstr(mt, G(L)->tmname[event])

    对于表和userdata每个对象都有自己独有的元表,是对象私有的元表而不像基本类型是类型共享元表

    struct uv {
    …
    struct table metatable;
    ...
    }
    struct table {
    …
    struct table metatable;
    ...
    }

    lua程序里的setmetatable,getmetatable实际调用的是luaB_setmetatable/luaB_getmetatable两函数,以下是setmetatable的源码.可见在lua程序里只能为表设置元表

    lbaselib.c
    ================
    static int luaB_setmetatable (lua_State *L) {
      int t = lua_type(L, 2);
      luaL_checktype(L, 1, LUA_TTABLE);
      luaL_argcheck(L, t == LUA_TNIL || t == LUA_TTABLE, 2,
                        "nil or table expected");
      if (luaL_getmetafield(L, 1, "__metatable"))
        luaL_error(L, "cannot change a protected metatable");
      lua_settop(L, 2);
      lua_setmetatable(L, 1);
      return 1;
    }

    而在c程序里,我们就可以对userdata和一些基本类型进行设置元表.使用的函数是lua_setmetatable/lua_getmetatable.

    lapi.c
    ============
    LUA_API int lua_setmetatable (lua_State *L, int objindex) {
      TValue *obj;
      Table *mt;
      lua_lock(L);
      api_checknelems(L, 1);
      obj = index2adr(L, objindex);
      api_checkvalidindex(L, obj);
      if (ttisnil(L->top - 1))
        mt = NULL;
      else {
        api_check(L, ttistable(L->top - 1));
        mt = hvalue(L->top - 1);
      }
      switch (ttype(obj)) {
        case LUA_TTABLE: {
          hvalue(obj)->metatable = mt;
          if (mt)
            luaC_objbarriert(L, hvalue(obj), mt);
          break;
        }
        case LUA_TUSERDATA: {
          uvalue(obj)->metatable = mt;
          if (mt)
            luaC_objbarrier(L, rawuvalue(obj), mt);
          break;
        }
        default: {
          G(L)->mt[ttype(obj)] = mt;
          break;
        }
      }
      L->top--;
      lua_unlock(L);
      return 1;
    }

    根据类型到不同的位置来设置元表,查找元表简单就不例举了.

    以上是元表的基础知识,接下来讲介绍元表的执行过程.



沪ICP备19023445号-2号
友情链接