【问题标题】:Return already existing objects from C++ to Lua将已经存在的对象从 C++ 返回到 Lua
【发布时间】:2013-02-10 03:06:04
【问题描述】:

在 C++ 中,假设我有一个类可以创建类似二叉树的结构,我使用它是这样的:

CTreeRoot* root = new CTreeRoot(/* whatever */);
CNode* leftNode = root->getLeftNode();
CNode* rightNode = root->getRightNOde();

leftNode->doSomething();
rightNode->doSomething();

// etc

并假设左右节点有自己的左右节点(因此是二叉树)。现在我想把它暴露给 Lua(not 使用 luabind)所以我可以做同样的事情:

local root = treeroot.new(/* whatever */)
local left = root:getLeftNode()
local right = root:getRightNode()

left:doSomething();
right:doSomething(); 

我已经完成了大部分工作。但是,对于 getLeftNode() 和 getRightNode() 方法,我很确定我做错了。以下是我在 C++ 中实现 getLeftNode() 的方式,例如:

int MyLua::TreeRootGetLeftNode(luaState* L)
{
    CTreeRoot* root = (CTreeRoot*)luaL_checkudata(L, 1, "MyLua.treeroot");
    CNode* leftNode = root->getLeftNode();

    if (leftNode != NULL)
    {
        int size = sizeof(CNode);

        // create a new copy of the CNode object inplace of the memory Lua
        // allocated for us
        new ((CNode*)lua_newuserdata(L,size)) CNode((const CNode&)*leftNode);
        lua_setmetatable(L, "MyLua.treenode");
    }
    else
    {
        lua_pushnil(L);
    }

    return 1;
}

我将 userdata 转换回 CTreeRoot 对象,调用 getLeftNode(),确保它存在,然后(这里是“错误的部分”)创建另一个 userdata 数据对象,并使用复制构造函数复制我想要返回的对象。

这是这种情况的“标准做法”吗?似乎您希望避免创建对象的另一个副本,因为您真正想要的只是对已经存在的对象的引用。

听起来这将是 lightuserdata 的理想场所,因为我不想创建新对象,我很乐意返回已经存在的对象。但是,问题是 lightuserdata 没有元表,所以一旦我把它们拿回来,这些对象对我来说就没有用了。基本上,我想做这样的事情:

int MyLua::TreeRootGetLeftNode(luaState* L)
{
    CTreeRoot* root = (CTreeRoot*)luaL_checkudata(L, 1, "MyLua.treeroot");
    CNode* leftNode = root->getLeftNode();

    if (leftNode != NULL)
    {
        // "WRONG" CODE BUT SHOWS WHAT I WISH I COULD DO
        lua_pushlightuserdata(L, (void*)leftNode);
        lua_setmetatable(L, "MyLua.treenode");
    }
    else
    {
        lua_pushnil(L);
    }

    return 1;
}

有人可以告诉我如何让我的MyLua::TreeRootGetLeftNode 方法将已经存在的对象的副本返回给 Lua,以便我可以将该对象用作 Lua 中的“对象”吗?

【问题讨论】:

    标签: c++ lua


    【解决方案1】:

    这里可以执行两个级别的内存优化。在您的第一个功能但效率低下的解决方案中,当用户调用getLeftNode() 时,它必须创建CNode 的副本以便将其存储在 Lua 用户数据中。此外,每次用户在同一棵树上重复调用getLeftNode() 时,它都会不断创建新的用户数据来表示CNode,即使它之前已经创建过。

    在第一级优化中,你可以memoize这个调用,这样每次用户请求同一个子树时,你可以简单地返回最初创建的用户数据,而不是复制和构造另一个userdata 来表示相同的东西。不过,有 3 种方法可以解决此问题,具体取决于您是要修改 Lua 接口、更改 C++ 实现还是硬着头皮。

    • MyLua.treenode 用户数据当前包含CNode 对象的实际数据。然而,这很不幸,因为这意味着无论何时创建CNode 对象,都必须使用placement new 将其存储到Lua 分配的内存中。可能更好的是在MyLua.treenode 的用户数据中简单地存储一个指针(CNode*)。这确实需要您修改 MyLua.treenode 的 Lua 接口,以便它现在将其数据视为指向 CNode 对象的指针。

    • 如果您希望将CNode 数据直接存储在MyLua.treenode 用户数据中,那么您必须确保在创建CTreeRoot 时,它将使用placement new 来构造CNode s 来自 Lua 每次分配的内存(或者你可以使用 C++ 标准库中使用的allocator pattern?)。但是,这不太优雅,因为您的 CNode 实现现在依赖于 Lua 运行时,我不建议这样做。

    • 如果上述解决方案都不合适,那么您只需在返回子节点时进行复制,尽管您仍然可以提高同一节点上重复调用的效率 em> 通过跟踪您之前是否创建过相同的用户数据(例如,使用 Lua 表)。

    在第二级优化中,您可以通过将复制的子树作为对原始树片段的弱引用来进一步节省内存。这样,每当您复制子树时,您只是在创建指向原始树的一部分的指针。或者,如果您希望子树即使在原始树被破坏后仍然存在,您可以使用强引用,但是您将不得不进入引用计数的血腥细节。在这两种情况下,这种优化纯粹是在 C++ 级别上,与 Lua 接口无关,从您的代码来看,我假设您已经在使用弱引用 (CNode*)。

    注意:除了在内部实现中使用之外,最好避免使用轻量级用户数据。原因是轻量级用户数据本质上等同于 C 指针,因此几乎可以指向任何东西。如果您将轻量级用户数据暴露给 Lua 代码,您将不知道轻量级用户数据可能来自哪里或它包含什么类型的数据,使用它会带来安全风险(以及导致程序段错误的可能性)。使用轻量级用户数据的适当方式是将其用作存储在 Lua 注册表中的 Lua 查找表的索引,可用于实现前面提到的记忆。

    【讨论】:

      猜你喜欢
      • 2013-01-15
      • 2012-09-12
      • 1970-01-01
      • 2016-07-05
      • 1970-01-01
      • 2019-12-23
      • 2021-07-15
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多