将已存在的对象从C ++返回到Lua

时间:2013-02-09 12:48:17

标签: c++ lua

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

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

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

// etc

并假设左右节点有自己的左右节点(因此,二叉树)。现在我想把它暴露给Lua(使用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中的“对象”吗?

1 个答案:

答案 0 :(得分:2)

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

在第一级优化中,您可以记住此调用,这样每次用户请求相同的子树时,您只需返回最初创建的用户数据,而不是复制和构建另一个子树。 userdata代表同一件事。但是有三种方法,取决于你是想修改Lua接口,改变C ++实现,还是只是咬紧牙关。

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

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

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

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

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