Lua Userdata 的元表 (Metatable)

在Lua C API编程上,经常有一些博客会说,必须使用luaL_newmetatableluaL_setmetatable来给userdata加元表。还说给userdata加的元表不是普通的table。于是本着不信邪的态度,我翻了一下源码并自己尝试写了一些测试。

普通的表能做userdata的元表么

对于这个问题,我觉得是可以的,因为Lua中只有这一种数据结构,不存在什么特别的表。

class A
{
public:
    A() { cout << "A ctor " << this << endl; }
    ~A() { cout << "A dtor " << this << endl; }
};

int close_A(lua_State* L)
{
    A* pa=static_cast<A*>(lua_touserdata(L, 1));
    cout << "Closing A: " << pa << endl;

    pa->~A();

    return 0;
}

int test(lua_State* L)
{
    auto ptr = new (lua_newuserdata(L, sizeof(A))) A;

    lua_newtable(L);
    lua_pushcfunction(L, close_A);
    lua_setfield(L, -2, "__gc");
    lua_setmetatable(L, -2);

    return 1;
}

int main()
{
    auto L = luaL_newstate();
    luaL_openlibs(L);
    lua_register(L, "test", test);
    if (luaL_loadstring(L, "t=test()")) {
        cout << "Failed to load string." << endl;
        cout << lua_tostring(L, -1) << endl;
    }
    else if (lua_pcall(L, 0, 1, NULL)) {
        cout << "Error: " << lua_tostring(L, -1) << endl;
    }
    lua_close(L);
    return 0;
}

运行结果

A ctor 001EF910
Closing A: 001EF910
A dtor 001EF910

可以看到,__gc元方法被正确的调用了,因此元表无论在Lua层还是在C层都跟普通的表没有区别。

luaL_*metatable做了什么

通过翻看Lua源码可以弄清楚这一点。在lauxlib.hlauxlib.c中:

#define luaL_getmetatable(L,n)	(lua_getfield(L, LUA_REGISTRYINDEX, (n)))

LUALIB_API int luaL_newmetatable (lua_State *L, const char *tname) {
  if (luaL_getmetatable(L, tname) != LUA_TNIL)  /* name already in use? */
    return 0;  /* leave previous value on top, but return 0 */
  lua_pop(L, 1);
  lua_createtable(L, 0, 2);  /* create metatable */
  lua_pushstring(L, tname);
  lua_setfield(L, -2, "__name");  /* metatable.__name = tname */
  lua_pushvalue(L, -1);
  lua_setfield(L, LUA_REGISTRYINDEX, tname);  /* registry.name = metatable */
  return 1;
}

LUALIB_API void luaL_setmetatable (lua_State *L, const char *tname) {
  luaL_getmetatable(L, tname);
  lua_setmetatable(L, -2);
}

LUALIB_API void *luaL_testudata (lua_State *L, int ud, const char *tname) {
  void *p = lua_touserdata(L, ud);
  if (p != NULL) {  /* value is a userdata? */
    if (lua_getmetatable(L, ud)) {  /* does it have a metatable? */
      luaL_getmetatable(L, tname);  /* get correct metatable */
      if (!lua_rawequal(L, -1, -2))  /* not the same? */
        p = NULL;  /* value is a userdata with wrong metatable */
      lua_pop(L, 2);  /* remove both metatables */
      return p;
    }
  }
  return NULL;  /* value is not a userdata with a metatable */
}

LUALIB_API void *luaL_checkudata (lua_State *L, int ud, const char *tname) {
  void *p = luaL_testudata(L, ud, tname);
  if (p == NULL) typeerror(L, ud, tname);
  return p;
}

从中不难看出,luaL_newmetatable先尝试获取与tname绑定的一个表,如果不为nil,则返回0,但此时该表已经获取并放在了栈上。如果为nil,则在栈上新建一个{__name=$tname}这样的表,然后复制一份,并通过lua_setfield设置为某个表中对应键为$tname的值,返回1。

luaL_setmetatable显然是先获取绑定的表,然后将表设置为原栈顶元素的元表。需要注意的是luaL_setmetatable不会自动新建元表,因此当获取一个不存在的表时,会引起逻辑上的错误。

luaL_getmetatable是一个很简单的宏,但这里的LUA_REGISTRYINDEX多少让人有些迷惑。其实Lua官方手册已经给出了解释. 大意就是,这是一个伪索引,对应着一个全局表 (不妨成为registry) ,其真正存储位置不在Lua的虚拟栈上,但是可以通过操作栈的函数来操作这个表。这个表只能通过C API来操作访问,Lua层无法访问到这个表。可以用来存储模块数据等。

所以其实luaL_*metatable只是将registry表作为参照,将新建的表存入到registry中。luaL_checkudata也不过是将userdata的元表和registry中存储的元表进行直接比较(rawequal)。

还有一点需要注意,在luaL_newmetatable中,新建的元表有__name。此键类似于其他语言的"类型",是Lua解释器用来显示信息所用的元属性。在Lua层中就可以尝试. 例如:

mt={__name="JustName"}
t={}
setmetatable(t,mt)
print(type(t),t)

运行结果是:

table   JustName: 0000000000779e10
Lua中,Userdata是一种特殊类型的变量,可以存储C/C++编写的数据结构。由于Userdata是由C/C++编写的,因此不能直接在Lua中进行遍历,但是可以通过添加元方法来实现遍历操作。 在C/C++中,可以通过向Userdata添加元表(Metatable)来定义遍历操作。首先,需要在C/C++中编写一个函数,用于获取Userdata中的数据结构。然后,将该函数与__ipairs元方法关联,以实现Userdata的遍历。 以下是一个示例的C代码片段,在该代码中定义了一个用于遍历Userdata的元方法: ```c typedef struct { int data[10]; int length; } UserData; int get_data(lua_State *L) { UserData *ud = (UserData *)lua_touserdata(L, 1); int index = luaL_checkinteger(L, 2); if (index >= 1 && index <= ud->length) { lua_pushinteger(L, ud->data[index - 1]); return 1; } return 0; } int userdata_pairs(lua_State *L) { lua_pushcfunction(L, get_data); lua_pushvalue(L, 1); lua_pushinteger(L, 0); return 3; } int luaopen_userdata(lua_State *L) { // 创建userdata类型 luaL_newmetatable(L, "userdata"); // 设置__pairs元方法 lua_pushcfunction(L, userdata_pairs); lua_setfield(L, -2, "__pairs"); lua_pushvalue(L, -1); lua_setfield(L, -2, "__index"); return 1; } ``` 在Lua中,使用上述定义的Userdata类型和元方法进行遍历操作的示例代码如下: ```lua local userdata = require("userdata") local ud = userdata.new() ud:push(1) ud:push(2) ud:push(3) ud:push(4) ud:push(5) for i, v in ipairs(ud) do print(i, v) end ``` 输出结果: ``` 1 1 2 2 3 3 4 4 5 5 ``` 上述示例中,通过添加__pairs元方法,用户可以使用ipairs遍历Userdata中的元素。在每次迭代时,调用get_data函数获取userdata中指定位置的元素。 需要注意的是,Userdata的具体实现和元方法的定义根据实际需求可能会有所不同,上述示例仅供参考。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值