【问题标题】:Lua userdata object managementLua用户数据对象管理
【发布时间】:2012-06-26 00:35:12
【问题描述】:

我正在尝试将 Lua 类对象推入堆栈。多个函数可以返回指向该对象的指针。

换句话说:我需要推送 userdata 值,同时仍然能够在它们上使用 '=='、'~=' 等,因此如果 userdata 指针是相同的 C++ 对象,则它必须相同。

-- this should push the object onto the stack
local firstObject = GetClassObject();
firstObject:doSomething();

firstObject 将由 lua 脚本存储,稍后在代码中我需要再次执行此操作:

-- the c++ class pointer has not changed here
-- so I would like to push the same userdata pointer as in the first call...
local object = GetClassObject();

-- if I would not do this the following here would fail... :C
if object == firstObject then
...

我的 Push 函数基本上应该检查某处是否已经存在相同的 C++ 类指针,如果是,则推送相关的 userdata 指针(无论我如何推送它,对象应该以 1:1 相同的方式工作)

如果不是,它应该创建一个新的用户数据(将其压入堆栈)并将其内容设置为类对象。

这是我的代码:

template <typename T>
void Push( const T &tObject )
{
    lua_State *L = GetLuaState();

    // Here i need to check if such a C++ object (the same tObject)
    // already exists!
    //
    // If so i want to push the associated userdata.


    // Object didn't exist yet -> we need a new userdata
    void *pUserData = lua_newuserdata( L, sizeof( tObject ) );
    *reinterpret_cast<T*>( pUserData ) = tObject;
}

template <typename T>
void Push( const T &tObject, const char *pszTable )
{
    Push( tObject );
    lua_State *L = GetLuaState();
    luaL_getmetatable( L, pszTable );
    lua_setmetatable( L, -2 );
}

template <typename T>
T& Get( int nIndex )
{
    T *pUserData = reinterpret_cast<T*>( lua_touserdata( GetLuaState(), nIndex ) );
    if( pUserData == nullptr )
        throw std::exception( "Invalid userdata!" );

    return *pUserData;
}

template <typename T>
T& Get( int nIndex, const char *pszTable )
{
    T *pUserData = reinterpret_cast<T*>( LuaToUData( nIndex, pszTable ) );
    if( pUserData == nullptr )
        throw std::exception( "Invalid userdata!" );

    return *pUserData;
}

LuaToUData 是我自己编写的函数,它不会引发 lua 错误:

void* LuaToUData( int nIndex, const char *pszTable )
{
    void *pUserData = lua_touserdata( g_luaState, nIndex );
    if( pUserData != nullptr )
    {
        if( lua_getmetatable( g_luaState, nIndex ) != 0 )
        {
            lua_getfield( g_luaState, LUA_REGISTRYINDEX, pszTable );
            bool bEqual = ( lua_rawequal( g_luaState, -1, -2 ) == 1 );
            lua_pop( g_luaState, 2 );

            if( bEqual )
                return pUserData;
        }
    }

    return nullptr;
}

【问题讨论】:

  • 我发现很难准确地确定您在这里尝试做什么。您是否只是在创建 lua 用户数据实例后尝试缓存它?
  • 我正在尝试推送 userdata 值,同时仍然可以在它们上使用 '=='、'~=' 等,因此如果它是相同的 C++ 对象,则 userdata 指针必须相同.
  • Lua 用户数据对象在内部通过引用进行比较。从同一个底层指针创建的两个 userdata 实例应该比较相等。你是说== 在这种情况下不起作用吗?您是否覆盖了__eq 元表条目?
  • 在这种情况下,'==' 检查失败。不,我没有覆盖 __eq...
  • 它是否真的比较用户数据内容本身并检查它是否相等?

标签: c++ class lua lua-userdata


【解决方案1】:

没错,在 Lua 中,相同的用户数据 的任意两个实例都保证是相等的。但是,当您像这样对 C++ 类实例进行装箱时,每个装箱的实例都会被放入一个新的用户数据中,这意味着它们不能直接进行比较。

您需要做的是为您的对象定义一个__eq 元方法。它可能看起来有点像这样:

int l_compare_things(lua_State* l)
{
    MyClass* a = reinterpret_cast<MyClass*>(lua_touserdata(L, 1));
    MyClass* b = reinterpret_cast<MyClass*>(lua_touserdata(L, 2));

    lua_pushboolean(L, (*a) == (*b));

    return 1;
}

这假定MyClass 具有某种operator== 覆盖。您可以将此函数设置为与 MyClass 用户数据项关联的元表中的 __eq 元方法。您似乎已经涵盖了元表处理,所以我不会在这里打扰。

现在,下一个问题:您将整个类实例装箱为 lua 完整的用户数据项。您可能不想一遍又一遍地推送相同的东西并用完所有可用的内存......如果您只是推送指针,这不是一个问题,但您没有这样做。所以......您将需要一些独特的方式来识别您的 C++ 类的每个实例。这是一个带有字符串的示例:

class MyClass
{
private:
    std::string _id;
public:
    MyClass(const std::string& id) : _id(id) {}

    const std::string& get_id() { return _id; }

    // setters and operator= overrides not included.
};

void l_push_thing(lua_State* L, const MyClass& thing)
{
    // try to get our instance by ID from the registry table:
    lua_getfield(L, LUA_REGISTRYINDEX, thing.id());

    // if so, return, leaving it at the top of the stack.
    if (lua_isuserdata(L, -1))
        return;

    void *ud = lua_newuserdata(L, sizeof(MyClass));                       
    *reinterpret_cast<MyClass*>(ud) = thing; 
    // set up the metatable, etc

    // duplicate the userdata reference:
    lua_pushvalue(L, -1);

    // push our new userdata into the registry. pops the duplicate from the stack
    lua_setfield(L, LUA_REGISTRYINDEX, thing.get_id());
}

(注意:我没有编译或测试过这个例子。E&OE!)

这会将与某个特定MyClass 实例关联的用户数据留在堆栈顶部。您需要采取自己的步骤来“取消注册”类实例;在这种情况下,注册表中存在对每个实例的硬引用,因此用户数据不会被垃圾收集,直到您销毁该引用。您可以考虑在这里使用弱/ephemeron 表。

【讨论】:

  • 谢谢你,遗憾的是我不知道弱/ ephemeron 表的工作原理是什么?
  • 我尝试在 std::map 中映射所有用户数据及其值,并添加自己的 __gc 方法,但没有成功。我将完整的用户数据指针推送为轻用户数据,这不起作用:c
  • @user1478081 一次一步。在您的基本推送系统正常工作之前,不要担心弱牌桌!我不确定你想用std::map 完成什么;你能使用我上面的代码吗?
  • 是的,一切正常,但正如您所说,它会导致内存泄漏。请看下面,看看我是如何尝试修复它的 :)
  • @user1478081:至少这是一个好消息!你用的是哪个版本的lua?
【解决方案2】:

弱表是这样工作的吗?

void Push( const T &tObject )
{
    std::ostringstream o;
    o << tObject;
    std::string sIdentifier = o.str();
    const char *pszIdentifier = sIdentifier.c_str();

    lua_State *L = GetLuaState();
    luaL_getmetatable( L, "lua_userdata" );
    if( !lua_istable( L, -1 ) )
    {
        // create new weak table
        luaL_newmetatable( L, "lua_userdata" );
        lua_pushstring( L, "v" );
        lua_setfield( L, -2, "__mode" );
    }

    lua_getfield( L, -1, pszIdentifier );
    if( lua_isuserdata( L, -1 ) == TRUE )
        return lua_remove( L, -2 );

    lua_pop( L, 1 ); // didnt exist yet - getfield is nil -> need to pop that
    void *pUserData = lua_newuserdata( L, sizeof( UINT64 ) );
    *reinterpret_cast<UINT64*>( pUserData ) = UINT64( tObject );

    lua_pushvalue( L, -1 );
    lua_setfield( L, -3, pszIdentifier );
    lua_remove( L, -2 );
}

【讨论】:

  • 请不要忘记缓存在弱表中的用户数据在第一个用户数据垃圾收集阶段之后可能仍然存在并且需要额外检查,哪种形式取决于指向类实例的实际指针如何存储在 udata 块中.简单地检查非零值会错过这种情况。如果 udata 包含单个指针,则必须针对 NULL 检查它,因为这可能是 __gc 元方法应该做的。否则,您会将复活但已死亡的对象返回给解释器。
猜你喜欢
  • 2015-11-09
  • 2012-08-25
  • 2015-01-13
  • 2013-09-10
  • 1970-01-01
  • 1970-01-01
  • 2012-02-16
  • 2015-11-21
  • 1970-01-01
相关资源
最近更新 更多