luaL_checkudata继承检查

时间:2016-03-17 18:46:49

标签: c++ lua

我有以下情况:

一个名为“Base”的类和名为“Derived”的第二个类,它派生自“Base”。我使用metatables来支持Lua中的继承。但是我需要一个解决方案来检查传递的userdata是否继承自指定的类。

static int BaseGetMyVar(lua_State *state)
{
    Base* base = *(Base**)luaL_checkudata(state, 1, "Base");
    //Base* base = *(Base**)lua_touserdata(state, 1);
    lua_pushinteger(state, base->GetMyVar());
    return 1;
}

当我将“Derived”udata传递给此方法时,我得到:

HelloWorld.lua:17: calling 'getMyVar' on bad self (Base expected, got userd
ata)

如何检查继承?所以我可以传递Base或Derived。

我找到的当前解决方案:

http://lua-users.org/lists/lua-l/2005-06/msg00000.html

http://lua-users.org/lists/lua-l/2007-04/msg00324.html

https://github.com/diegonehab/luasocket/blob/master/src/auxiliar.c

siffiejoe的提案(谢谢)

https://github.com/siffiejoe/lua-moon#moon_defcast

偶然发现的另一种解决方案:

http://lua-users.org/lists/lua-l/2013-06/msg00492.html

我自己的解决方案(我喜欢拥有的东西)

void* luaL_checkclass(lua_State *L, const char *classname, int objidx)
{
    if(!luaL_getmetafield(L, objidx, "__index"))
        return NULL;
    lua_getfield(L, -1, "__name");
    if(lua_type(L, -1) != LUA_TSTRING)
        return NULL;
    char const* basename = lua_tostring(L, -1);
    lua_pop(L, 2);
    if(strcmp(basename, classname) != 0)
        luaL_typeerror(L, objidx, classname);
    return lua_touserdata(L, objidx);
}

我提出了自己的想法。这要求metatable具有字段__name(Lua 5.3实现了这一点,但您仍然可以在以前的版本中自行添加此字段)。

更详细:

在Lua中注册的每个metatable都拥有__index字段,该字段要么被分配给self,要么它有一个基类,它被分配给基类。 __index上的表拥有__name字段,这是类的名称...我们可以比较名称。

如果我们现在将Derived对象传递给Base方法,它将正常工作。

2 个答案:

答案 0 :(得分:2)

如果您想为多个相关的用户数据重复使用lua_CFunction 类型,你必须克服两个问题:

接受多个用户数据类型

通常用于的luaL_checkudata()函数 对userdata内存的类型安全访问仅检查一个特定的内存 userdata类型。因此,所有提及的方法都使用不同 用于检查lua_CFunction

的参数的函数

获取指向userdata内存的正确指针

即使userdata属于一组相关的userdata类型,也是如此 不同类型的内存布局通常略有不同 (否则使用不同的userdata类型没有多大意义 在所有)。所以第二个问题是得到一个兼容的指针 使用请求的userdata类型。

上面提到的大多数方法都忽略了这个问题,因为在 如果你小心如何定义你的话,你可以侥幸逃脱 相关的userdata类型。 C标准保证指向a struct与指向其第一个成员的指针具有相同的值。因此,如果 你有

 struct A { ... };
 struct B { A a; ... };
 struct C { B b; ... };

指向struct C的指针也自动成为指向的有效指针 struct Bstruct A

但是,如果您不能保证这种特定的内存布局,或者如果 您使用C ++进行多重继承或虚拟继承,或者只是 一个不幸的虚拟方法选择(见this SO answer), 你可能需要调整指针值才能使它们有效 指向相关userdata类型的指针。

https://github.com/siffiejoe/lua-moon#moon_defcast 存储可用于调整的简单回调函数 指针值到所需的类型。例如。 class B : public A ... 如果return (void*)(A*)(B*)p;确实是p,您会使用void 指向应传递给函数的B对象的指针 期望指向void对象的A指针。

问题中建议的“自己”解决方案

在问题的编辑中添加的方法忽略了问题2.它 尝试解决问题1(识别兼容的用户数据类型) 通过将userdata类型名称存储在metatable中(也可以使用 作为__index metamethod)。在Lua 5.3 luaL_newmetatable() 功能会为你做这个。给定的代码段仅适用于 两种情况:

  1. Derived userdata类型使用Base metatable作为其 __index元方法,即派生的userdata类型 有自己的方法,因此没有自己的__index
  2. 对于Base对象本身。
  3. 如果您添加更多级别(例如MoreDerived类型),或者您需要 特定于Derived类型的方法,这种简单的方法不会 工作了。你必须走__index metamethods链 向上(假设使用链式__index处理继承 metamethods)直到找到您要查找的类型名称。

答案 1 :(得分:0)

我能想到的一个简单方法可能是表格如下:

Base = {Base = true}
Base.__index = Base
Derived = setmetatable({Derived = true}, Base)
Derived.__index = Derived

obj = setmetatable({}, Derived)

现在检查obj是否继承Base很简单。你刚做obj.Base 如果继承了Base,则返回true。在C语言中,您可以使用方法lua_setfieldlua_getfield来设置和获取元数据中Base和Derived的键值。

还有其他多种方法可以做到这一点。