0%

Lua源码笔记(4)--Table的元表实现细节

通过元表metatable细节分析,能够更好的理解metatable在整个lua的使用中的作用,通过原理更好的利用lua的特性;首先metatable的元表,系统会给一些默认的元表方法,其次玩家可以通过setmetatable的API设置元表,这样玩家就自定义table的运行时的一些行为。我的理解是lua的设计者尝试想通过metatable,来封装统一table的数据和行为,接近于面向对象的思路,但lua作为一门嵌入式的语言,是增强宿主语言的能力,因为力求实现和使用简洁又能实现统一。

1. tag methods

ltm.c是实现元方法的主要实现文件,tm是tag methods的缩写。
无论是fasttm宏,还是luaT_gettmbyobj,都会调用luaH_getstr函数来获取key的值

1.1 luaH_getstr函数分析
1
2
3
4
5
6
7
8
9
10
11
// 这里要说明的是key作为函数名字保存在table中
const TValue *luaH_getstr (Table *t, TString *key) {
Node *n = hashstr(t, key);
do { /* check whether `key' is somewhere in the chain */
// 如果key是字符串且字符串相等则返回值
if (ttisstring(gkey(n)) && rawtsvalue(gkey(n)) == key)
return gval(n); /* that's it */
else n = gnext(n);
} while (n);
return luaO_nilobject;
}
1.2 fasttm宏实际是调用luaT_gettm函数
1
2
3
4
5
6
// l为lua_State, et为元表,e为TMS枚举
#define fasttm(l,et,e) gfasttm(G(l), et, e)
// 先通过Table的flags标记位判断,如果该位存在,表示没有该元方法,直接返回NULL
// 否则才通过luaT_gettm去取
#define gfasttm(g,et,e) ((et) == NULL ? NULL : \
((et)->flags & (1u<<(e))) ? NULL : luaT_gettm(et, e, (g)->tmname[e]))
1.3 获得元方法函数luaT_gettm和luaT_gettmbyobj
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
const TValue *luaT_gettm (Table *events, TMS event, TString *ename) {
// events为table,ename为key得到tm
const TValue *tm = luaH_getstr(events, ename);
lua_assert(event <= TM_EQ);
// 这里需要注意的是如果没有这个元方法则会标记,优化细节
if (ttisnil(tm)) { /* no tag method? */
events->flags |= cast_byte(1u<<event); /* cache this fact */
return NULL;
}
else return tm;
}


const TValue *luaT_gettmbyobj (lua_State *L, const TValue *o, TMS event) {
Table *mt;
// 根据o的类型,重点是当是lua的table时使用的是metatable
switch (ttype(o)) {
case LUA_TTABLE:
mt = hvalue(o)->metatable;
break;
case LUA_TUSERDATA:
mt = uvalue(o)->metatable;
break;
default:
mt = G(L)->mt[ttype(o)];
}
// 这里要强调的是要得到table的元方法是以mt为table
// G(L)->tmname[event]的字符串为key得到这个元方法
return (mt ? luaH_getstr(mt, G(L)->tmname[event]) : luaO_nilobject);
}

2. 上述luaT_gettmbyobj中对G(L)->tmname[event]的限制范围

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
// 初始化L的时候就将这些系统默认的元方法名字写进了G(L)中
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<TM_N; i++) {
G(L)->tmname[i] = luaS_new(L, luaT_eventname[i]);
luaS_fix(G(L)->tmname[i]); /* never collect these names */
}
}

// ltm.h中元方法的枚举定义
// 其中最重要的就是TM_INDEX
typedef enum {
TM_INDEX,
TM_NEWINDEX,
TM_GC,
TM_MODE,
TM_EQ, /* last tag method with `fast' access */
TM_ADD,
TM_SUB,
TM_MUL,
TM_DIV,
TM_MOD,
TM_POW,
TM_UNM,
TM_LEN,
TM_LT,
TM_LE,
TM_CONCAT,
TM_CALL,
TM_N /* number of elements in the enum */
} TMS;

3. luaV_gettable函数分析

总结VM中使用luaTgettmbyobj或者luaT_gettm
** 注:luaV
前缀的一般是给VM调用的 **

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
// 这个函数统一了获取table的数据和调用table的函数
void luaV_gettable (lua_State *L, const TValue *t, TValue *key, StkId val) {
int loop;
for (loop = 0; loop < MAXTAGLOOP; loop++) {
const TValue *tm;
if (ttistable(t)) {
// t是一个Lua的Table
Table *h = hvalue(t);
// 调用luaH_get获得这个key的值
const TValue *res = luaH_get(h, key);
// res的值不为空或者不是tm(元方法)
if (!ttisnil(res) || (tm = fasttm(L, h->metatable, TM_INDEX)) == NULL) {
// 设置val并返回(获得数据)
setobj2s(L, val, res);
return;
}
}
// 不是Table时获得t的TM_INDEX元方法
// 在luaT_gettmbyobj中还有LUA_TUSERDATA类型(玩家自定义类型)
else if (ttisnil(tm = luaT_gettmbyobj(L, t, TM_INDEX)))
luaG_typeerror(L, t, "index");
// 当tm是一个函数时调用callTMres执行函数
if (ttisfunction(tm)) {
callTMres(L, val, tm, t, key);
return;
}
// 通过table的index继承了一些元方法时
// 向上查找元方法(有点继承的味道)
// 嵌套次数为MAXTAGLOOP
t = tm;
}
luaG_runerror(L, "loop in gettable");
}