Metatable
Metatable 让 C 可以为 userdata 和 table 定义自定义行为(运算符重载、面向对象等)。
常用元方法
| 元方法 | 触发时机 |
|---|---|
__index | 访问不存在的字段 |
__newindex | 给不存在的字段赋值 |
__gc | GC 回收对象时 |
__len | 使用 # 运算符 |
__tostring | tostring() 或 print() |
__eq, __lt, __le | 比较运算符 |
__add, __sub, __mul, __div 等 | 算术运算符 |
__call | 将对象当作函数调用 |
在 C 中设置 Metatable
为 Userdata 设置(最常见)
// 注册 metatable 到注册表
void register_my_meta(lua_State *L) {
luaL_newmetatable(L, "MyType"); // 若不存在则创建
lua_pushcfunction(L, my_tostring);
lua_setfield(L, -2, "__tostring");
lua_pushcfunction(L, my_add);
lua_setfield(L, -2, "__add");
lua_pop(L, 1); // 弹出 metatable
}
// 创建对象时
MyType *obj = lua_newuserdatauv(L, sizeof(MyType), 0);
luaL_getmetatable(L, "MyType");
lua_setmetatable(L, -2);
__index 的两种模式
模式 A:__index 是一个 table
lua_newtable(L); // 方法表
lua_pushcfunction(L, method1);
lua_setfield(L, -2, "method1");
lua_pushcfunction(L, method2);
lua_setfield(L, -2, "method2");
luaL_newmetatable(L, "MyType");
lua_pushvalue(L, -2); // 复制方法表
lua_setfield(L, -2, "__index"); // mt.__index = methods
lua_pop(L, 2);
这是实现面向对象风格 obj:method() 最简洁的方式。
模式 B:__index 是一个函数
适合需要动态计算字段,或字段名不确定的情况。
__gc 元方法(析构)
当 userdata 被 GC 回收时,如果其 metatable 有 __gc 字段,Lua 会调用它。
这是释放 C 资源(如文件句柄、网络连接)的正确位置:
static int mystruct_gc(lua_State *L) {
MyStruct *obj = (MyStruct *)luaL_checkudata(L, 1, "MyStructMeta");
printf("MyStruct %p collected\n", (void *)obj);
return 0;
}
类型安全检查
用 luaL_checkudata 确保参数是指定类型的 userdata:
MyStruct *obj = (MyStruct *)luaL_checkudata(L, 1, "MyStructMeta");
如果类型不匹配,会自动抛出 Lua 错误。
注册表(Registry)
luaL_newmetatable 实际上是在 Lua Registry 中创建一个以字符串为 key 的 table。
Registry 是一个全局的、对 C 代码可见的 table,Lua 脚本无法直接访问
(除非用 debug 库)。它是 C 模块间共享数据的常用机制。
// 手动读写 registry
lua_pushstring(L, "my_key");
lua_gettable(L, LUA_REGISTRYINDEX); // 读取 registry["my_key"]
lua_pushstring(L, "my_key");
lua_pushinteger(L, 42);
lua_settable(L, LUA_REGISTRYINDEX); // registry["my_key"] = 42
完整代码
#include <assert.h>
#include <lauxlib.h>
#include <lua.h>
#include <lualib.h>
#include <stdio.h>
#include <string.h>
#define VEC2_MT "Vec2Meta"
typedef struct {
double x, y;
} Vec2;
static Vec2 *check_vec2(lua_State *L, int idx) {
return (Vec2 *)luaL_checkudata(L, idx, VEC2_MT);
}
static int vec2_tostring(lua_State *L) {
Vec2 *v = check_vec2(L, 1);
lua_pushfstring(L, "Vec2(%f, %f)", v->x, v->y);
return 1;
}
static int vec2_add(lua_State *L) {
Vec2 *a = check_vec2(L, 1);
Vec2 *b = check_vec2(L, 2);
Vec2 *res = (Vec2 *)lua_newuserdatauv(L, sizeof(Vec2), 0);
res->x = a->x + b->x;
res->y = a->y + b->y;
luaL_getmetatable(L, VEC2_MT);
lua_setmetatable(L, -2);
return 1;
}
static int vec2_len(lua_State *L) {
Vec2 *v = check_vec2(L, 1);
lua_pushnumber(L, v->x * v->x + v->y * v->y);
return 1;
}
static int vec2_eq(lua_State *L) {
Vec2 *a = check_vec2(L, 1);
Vec2 *b = check_vec2(L, 2);
lua_pushboolean(L, a->x == b->x && a->y == b->y);
return 1;
}
static int vec2_new(lua_State *L) {
double x = luaL_optnumber(L, 1, 0);
double y = luaL_optnumber(L, 2, 0);
Vec2 *v = (Vec2 *)lua_newuserdatauv(L, sizeof(Vec2), 0);
v->x = x;
v->y = y;
luaL_getmetatable(L, VEC2_MT);
lua_setmetatable(L, -2);
return 1;
}
static int vec2_index(lua_State *L) {
Vec2 *v = check_vec2(L, 1);
const char *key = luaL_checkstring(L, 2);
if (strcmp(key, "x") == 0) {
lua_pushnumber(L, v->x);
return 1;
}
if (strcmp(key, "y") == 0) {
lua_pushnumber(L, v->y);
return 1;
}
luaL_getmetatable(L, VEC2_MT);
lua_pushvalue(L, 2);
lua_rawget(L, -2);
return 1;
}
int luaopen_vec2(lua_State *L) {
luaL_newmetatable(L, VEC2_MT);
lua_pushcfunction(L, vec2_tostring);
lua_setfield(L, -2, "__tostring");
lua_pushcfunction(L, vec2_add);
lua_setfield(L, -2, "__add");
lua_pushcfunction(L, vec2_len);
lua_setfield(L, -2, "__len");
lua_pushcfunction(L, vec2_eq);
lua_setfield(L, -2, "__eq");
lua_pushcfunction(L, vec2_index);
lua_setfield(L, -2, "__index");
lua_pop(L, 1);
lua_newtable(L);
lua_pushcfunction(L, vec2_new);
lua_setfield(L, -2, "new");
return 1;
}
int main(void) {
lua_State *L = luaL_newstate();
luaL_openlibs(L);
luaopen_vec2(L);
lua_setglobal(L, "vec2");
const char *script = "local vec2 = vec2\n"
"local a = vec2.new(1, 2)\n"
"local b = vec2.new(3, 4)\n"
"local c = a + b\n"
"print('a =', a)\n"
"print('b =', b)\n"
"print('a + b =', c)\n"
"print('a.x =', a.x)\n"
"print('#a (len_sq) =', #a)\n"
"print('a == b ?', a == b)\n"
"local a2 = vec2.new(1, 2)\n"
"print('a == a2 ?', a == a2)\n";
luaL_dostring(L, script);
lua_close(L);
puts("08-metatable: ok");
return 0;
}