Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Metatable

Metatable 让 C 可以为 userdata 和 table 定义自定义行为(运算符重载、面向对象等)。

常用元方法

元方法触发时机
__index访问不存在的字段
__newindex给不存在的字段赋值
__gcGC 回收对象时
__len使用 # 运算符
__tostringtostring()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;
}