##元表及面向对象的实现 ###元表的实现原理 Lua在初始化时,首先会调用函数luaT_init初始化Lua中定义的几种元方法对应的字符串,这些都是全局共用的,在初始化完毕之后只可读不可写,也不能回收:
(ltm.c)
30 void luaT_init (lua_State *L) {
31 static const char *const luaT_eventname[] = { /* ORDER TM */
32 "__index", "__newindex",
33 "__gc", "__mode", "__eq",
34 "__add", "__sub", "__mul", "__div", "__mod",
35 "__pow", "__unm", "__len", "__lt", "__le",
36 "__concat", "__call"
37 };
38 int i;
39 for (i=0; i<TM_N; i++) {
40 G(L)->tmname[i] = luaS_new(L, luaT_eventname[i]);
41 luaS_fix(G(L)->tmname[i]); /* never collect these names */
42 }
43 }
在这里,将遍历前面定义的枚举类型TMS,将每一个类型对应的字符串赋值给global_State结构体中的tmname,同时调用函数luaS_fix,将这些字符串设置为不可回收的,因为在这个系统运行的过程中,这些字符串会一直被使用到,至于如何让它们变成不可回收的,后面GC的部分将做分析.
(ltm.c)
61 const TValue *luaT_gettmbyobj (lua_State *L, const TValue *o, TMS event) {
62 Table *mt;
63 switch (ttype(o)) {
64 case LUA_TTABLE:
65 mt = hvalue(o)->metatable;
66 break;
67 case LUA_TUSERDATA:
68 mt = uvalue(o)->metatable;
69 break;
70 default:
71 mt = G(L)->mt[ttype(o)];
72 }
73 return (mt ? luaH_getstr(mt, G(L)->tmname[event]) : luaO_nilobject);
74 }
根据不同的数据类型,返回元方法的函数,只有在数据类型为Table以及udata的时候,才能拿到对象的metatable表,其他时候是到global_State结构体的成员mt来获取的,但是这个对于其他的数据类型而言,一直是空值.
(ltm.h)
41 #define gfasttm(g,et,e) ((et) == NULL ? NULL : \
42 ((et)->flags & (1u<<(e))) ? NULL : luaT_gettm(et, e, (g)->tmname[e]))
43
44 #define fasttm(l,et,e) gfasttm(G(l), et, e)
(ltm.c)
50 const TValue *luaT_gettm (Table *events, TMS event, TString *ename) {
51 const TValue *tm = luaH_getstr(events, ename);
52 lua_assert(event <= TM_EQ);
53 if (ttisnil(tm)) { /* no tag method? */
54 events->flags |= cast_byte(1u<<event); /* cache this fact */
55 return NULL;
56 }
57 else return tm;
58 }
这里,真正做查找工作的,是函数luaT_gettm,但是外面直接调用的,却是宏fasttm和宏gfasttm,两者的区别在于一个使用的参数是lua_State指针,一个使用的参数是global_State指针.这里会做一个优化,当第一次查找表中的某个元方法并且没有找到时,会将Table中的flags成员对应的位做置位操作,这样下一次再来查找该表中同样的元方法时如果该位已经为1,那么直接返回NULL即可.
(lvm.c)
108 void luaV_gettable (lua_State *L, const TValue *t, TValue *key, StkId val) {
109 int loop;
110 for (loop = 0; loop < MAXTAGLOOP; loop++) {
111 const TValue *tm;
112 if (ttistable(t)) { /* `t' is a table? */
113 Table *h = hvalue(t);
114 const TValue *res = luaH_get(h, key); /* do a primitive get */
115 if (!ttisnil(res) || /* result is no nil? */
116 (tm = fasttm(L, h->metatable, TM_INDEX)) == NULL) { /* or no TM? */
117 setobj2s(L, val, res);
118 return;
119 }
120 /* else will try the tag method */
121 }
122 else if (ttisnil(tm = luaT_gettmbyobj(L, t, TM_INDEX)))
123 luaG_typeerror(L, t, "index");
124 if (ttisfunction(tm)) {
125 callTMres(L, val, tm, t, key);
126 return;
127 }
128 t = tm; /* else repeat with `tm' */
129 }
130 luaG_runerror(L, "loop in gettable");
131 }
这个函数的逻辑,是一个逐层根据该对象的metable表中的"__index"来查找的过程:
1.如果t是一个Table,尝试根据key在该表中查找数据,在找到了非空数据,或者该表的metatable中"__index"为空的情况下,都返回查找的结果.此时,在不返回的情况只有可能是在原表中查找的数据为空同时原表的metatable存在"__index"成员,而且此时该成员已经赋值给了tm.
2.来到这种情况,说明前面判断t不是一个Table,于是调用luaT_gettmbyobj函数,尝试拿到这个数据的metatable["__index"],如果返回空,那么报错返回.
3.此时,tm不是一个空值,于是判断前面拿到的tm是不是一个函数,如果是,那么通过callTMres函数来调用它,调用之后返回.
4.来到这里,说明前面得到的tm,既不是一个空值,也不是一个函数,而是前面第一种情况得到的t->metatable["__index"],将这个值赋值做为下一个循环中处理的t,继续前面的操作.
5.如果这个前面的这个循环处理,层次过多,超过了MAXTAGLOOP,那么就终止循环报错返回.
有了luaV_gettable的操作,对应的就会有luaV_settable,原理与luaV_gettable差不多,区别在于:
1. luaV_settable是查找元表中的"__newindex"成员,同时,由于set操作导致的表的成员发生了增加,需要调用luaC_barriert对该Table做屏障操作,具体的原理在后面GC部分将做说明.
2. 当元表中的"__newindex"成员是函数的情况下,调用的是callTM函数.
###在Lua中实现面向对象 有了前面的基础,理解如何在Lua中实现面向对象就不难了,这里已经没有需要分析的Lua解释器代码,仅需要看一个具体的例子:
(base.lua)
module( "base", package.seeall )
function new( )
local obj = {}
setmetatable( obj, { __index = base } )
return obj
end
(test.lua)
module( "test", package.seeall )
setmetatable( test, {__index = base} )
function new( )
local obj = {}
setmetatable( obj, { __index = test } )
return obj
end
在这个例子里,提供了两个Lua模块,其中base模块可以认为是基类模块,而test是继承自base的子类. 在base.lua中,使用"package.seeall"的模块定义,让该模块中的全部方法对外暴露;而在test中,在最开始调用setmetatable将base模块的Table直接赋值做为test模块的metatable.后面每次调用test的new函数创建对象时,都在返回之前设置该对象的metatable,这样根据前面的分析,每次查找该对象的某个成员,如果在test模块中没有定义,会往上根据metatable的"__index"成员到base模块中查找.
这就是Lua中实现面向对象的基本原理及演示.
但是,由前面的分析可知,查找基类的成员,需要一层一层的往上查找,这个过程还是有性能上的损耗的.所以另一种实现面向对象的方式,是直接将基类的成员深拷贝给子类,在云风的博客中就是使用类似的方案:
local _class={}
function class(super)
local class_type={}
class_type.ctor=false
class_type.super=super
class_type.new=function(...)
local obj={}
do
local create
create = function(c,...)
if c.super then
create(c.super,...)
end
if c.ctor then
c.ctor(obj,...)
end
end
create(class_type,...)
end
setmetatable(obj,{ __index=_class[class_type] })
return obj
end
local vtbl={}
_class[class_type]=vtbl
setmetatable(class_type,{__newindex=
function(t,k,v)
vtbl[k]=v
end
})
if super then
setmetatable(vtbl,{__index=
function(t,k)
local ret=_class[super][k]
vtbl[k]=ret
return ret
end
})
end
return class_type
end
来看看怎么使用,首先是基类的定义:
base_type=class() -- 定义一个基类 base_type
function base_type:ctor(x) -- 定义 base_type 的构造函数
print("base_type ctor")
self.x=x
end
function base_type:print_x() -- 定义一个成员函数 base_type:print_x
print(self.x)
end
function base_type:hello() -- 定义另一个成员函数 base_type:hello
print("hello base_type")
end
其次根据该基类定义一个继承自base_type的子类:
test=class(base_type) -- 定义一个类 test 继承于 base_type
function test:ctor() -- 定义 test 的构造函数
print("test ctor")
end
function test:hello() -- 重载 base_type:hello 为 test:hello
print("hello test")
end
使用方式:
a=test.new(1) -- 输出两行,base_type ctor 和 test ctor 。这个对象被正确的构造了。
a:print_x() -- 输出 1 ,这个是基类 base_type 中的成员函数。
a:hello() -- 输出 hello test ,这个函数被重载了。