我有以下情况:
一个名为“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方法,它将正常工作。
答案 0 :(得分:2)
如果您想为多个相关的用户数据重复使用lua_CFunction
类型,你必须克服两个问题:
通常用于的luaL_checkudata()
函数
对userdata内存的类型安全访问仅检查一个特定的内存
userdata类型。因此,所有提及的方法都使用不同
用于检查lua_CFunction
。
"A.B.C"
也是有效的"A.B"
用户数据,并且a
有效"A"
userdata。检查只是比较一个问题
具有请求的userdata类型的真实类型名称的前缀
名称luaL_checkudata()
的函数
对于多个指定的类型名称,直到其中一个调用
成功。https://github.com/diegonehab/luasocket/blob/master/src/auxiliar.c (和 https://github.com/siffiejoe/lua-moon#moon_defcast 同样)在userdata中维护一组受支持的类型名称 元表。如果请求的类型不完全匹配,那就是 只是一个额外的表查找来检查类型是否 应该是兼容的。
这也是@ Rochet2 answer中使用的方法
问题,另外一点:它重新使用链式__index
元方法表使兼容类型集更易于管理。
即使userdata属于一组相关的userdata类型,也是如此 不同类型的内存布局通常略有不同 (否则使用不同的userdata类型没有多大意义 在所有)。所以第二个问题是得到一个兼容的指针 使用请求的userdata类型。
上面提到的大多数方法都忽略了这个问题,因为在
如果你小心如何定义你的话,你可以侥幸逃脱
相关的userdata类型。 C标准保证指向a
struct
与指向其第一个成员的指针具有相同的值。因此,如果
你有
struct A { ... };
struct B { A a; ... };
struct C { B b; ... };
指向struct C
的指针也自动成为指向的有效指针
struct B
和struct 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()
功能会为你做这个。给定的代码段仅适用于
两种情况:
Derived
userdata类型使用Base
metatable作为其
__index
元方法,即派生的userdata类型不
有自己的方法,因此没有自己的__index
。Base
对象本身。如果您添加更多级别(例如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_setfield
和lua_getfield
来设置和获取元数据中Base和Derived的键值。
还有其他多种方法可以做到这一点。