简体   繁体   English

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

[英]Return already existing objects from C++ to Lua

In C++, say I have a class that creates a binary tree like structure and I use it something like this: 在C ++中,假设我有一个创建类似于结构的二叉树的类,并且使用了类似的方法:

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

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

// etc

And assume that the left and right nodes have their own left and right nodes (hence, a binary tree). 并假设左右节点具有自己的左右节点(因此为二叉树)。 Now I want to expose this to Lua ( not using luabind) so I can do the same kind of thing: 现在,我想将其公开给Lua( 使用luabind),这样我可以做同样的事情:

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

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

I've gotten most of it to work. 我已经大部分工作了。 However, for the getLeftNode() and getRightNode() methods, I'm pretty sure I'm doing it "wrong". 但是,对于getLeftNode()和getRightNode()方法,我很确定自己做错了。 Here is how I'm implementing getLeftNode() in C++ for example: 例如,这是我在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;
}

I cast the userdata back to a CTreeRoot object, call getLeftNode(), make sure it exists and then (here's the "wrong part") I create another userdata data object with a copy constructor copying the object I want to return. 我将userdata投射回CTreeRoot对象,调用getLeftNode(),确保它存在,然后(这里是“错误的部分”)我创建了另一个Userdata数据对象,该对象带有复制构造函数,该对象复制了要返回的对象。

Is this "standard practice" for this type of scenario? 这是针对这种情况的“标准做法”吗? It seems like you would want to avoid creating another copy of the object since what you really want is just a reference to an already existing object. 似乎您要避免创建该对象的另一个副本,因为您真正想要的只是对已存在对象的引用。

It sounds like this would be the perfect place for lightuserdata since I don't want to create a new object, I would be happy returning the object that already exists. 听起来这将是lightuserdata的理想场所,因为我不想创建一个新的对象,我很乐意返回已经存在的对象。 The problem, though, is that lightuserdata has no meta table so the objects would be useless to me once I got them back. 但是,问题在于lightuserdata没有元表,因此一旦我将它们取回,这些对象对我来说将毫无用处。 Basically, I want to do something like: 基本上,我想做类似的事情:

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;
}

Can someone please tell me how I can have my MyLua::TreeRootGetLeftNode method return to Lua a copy of the object that already exists in such a way that I can use that object as an 'object' in Lua? 有人可以告诉我如何让MyLua::TreeRootGetLeftNode方法向Lua返回已经存在的对象的副本,以便可以将该对象用作Lua中的“对象”吗?

There are two levels of memory optimization that can be performed here. 可以在此处执行两个级别的内存优化。 In your first functional-but-inefficient solution, when the user calls getLeftNode() , it has to create a copy of the CNode in order to store it in the Lua userdata. 在第一个功能低效的解决方案中,当用户调用getLeftNode() ,它必须创建CNode的副本才能将其存储在Lua用户数据中。 Furthermore, each time the user calls getLeftNode() repeatedly on the same tree, it will keep creating new userdata to represent the CNode even though it has have been created previously. 此外,每次用户在同一棵树上重复调用getLeftNode() ,即使它先前已经创建过,它也会继续创建新的用户数据来表示CNode

In the first level of optimization, you can memoize this call so that each time the user requests for the same subtree, you can simply return the userdata that was originally created instead of copying and constructing another userdata to represent the same thing. 在第一级优化中,您可以记住此调用,以便每次用户请求相同的子树时,都可以简单地返回最初创建的用户数据,而不必复制和构造另一个代表相同内容的用户数据。 There are 3 approaches to this though, depending on whether you want to modify the Lua interface, alter the C++ implementation, or just bite the bullet. 但是,根据您是要修改Lua接口,更改C ++实现还是只是硬着头皮,有3种方法可以做到这一点。

  • The MyLua.treenode userdata currently contains the actual data of a CNode object. MyLua.treenode数据当前包含CNode对象的实际数据。 This is unfortunate, however, because that means whenever you create a CNode object, you have to use placement new to store it into the memory allocated by Lua immediately upon creation. 但是,这很不幸,因为这意味着每当创建CNode对象时,都必须使用new放置将其存储在创建后立即存储到Lua分配的内存中。 What is probably better is to simply store a pointer instead ( CNode* ) in the userdata for MyLua.treenode . 什么是可能是更好的是简单地存储指针代替( CNode* )在用户数据的MyLua.treenode This does require you to modify the Lua interface for MyLua.treenode so that it will now consider its data as a pointer to a CNode object. 这确实需要您修改MyLua.treenode的Lua接口,以便它现在将其数据视为指向CNode对象的指针。

  • If you would rather store the CNode data in the MyLua.treenode userdata directly, then you will have to make sure that when you create your CTreeRoot , it would use placement new to construct CNode s from the memory allocated by Lua each time (or perhaps you can use the allocator pattern used in the C++ standard library?). 如果你宁愿存储CNode数据MyLua.treenode直接用户数据,那么你将不得不以确保当您创建CTreeRoot ,它将使用新的布局来构建CNode从Lua中,每次分配的内存秒(或者可能您可以使用C ++标准库中使用的分配器模式吗?)。 This is less elegant however as your CNode implementation now depends on the Lua runtime, and I don't recommend this. 但是,这不太优雅,因为您的CNode实现现在取决于Lua运行时,我不建议这样做。

  • If neither of the above solutions are appropriate, then you'll just have to make a copy whenever you return a subnode, although you can still improve the efficiency for repeated calls on the same node by keeping track of whether you have created the same userdata before (using a Lua table, for example). 如果以上两种解决方案都不适合,那么尽管您仍可以通过跟踪是否已创建相同的用户数据来提高在同一节点上重复调用的效率,但是只要在返回子节点时就必须进行复制。之前(例如,使用Lua表)。

In the second level of optimization, you can further save memory by making your copied subtree to be a weak reference to a fragment of the original tree. 在第二级优化中,可以通过将复制的子树作为对原始树的片段的弱引用来进一步节省内存。 This way, whenever you copy a subtree, you are merely creating a pointer to a part of the original tree. 这样,无论何时复制子树,您都只是在创建指向原始树的一部分的指针。 Or, you can use a strong reference if you want your subtree to persist even after the original tree is destroyed, but then you'll have to go into the gory details of reference-counting. 或者,如果您希望子树即使在原始树被破坏后仍然保留,也可以使用强引用,但是您必须深入研究引用计数的细节。 In either case, this optimization is purely on the C++ level and is not related to the Lua interface, and judging from your code I assume you are already using weak references ( CNode* ). 无论哪种情况,这种优化纯粹是在C ++级别上,并且与Lua接口无关,并且从您的代码来看,我认为您已经在使用弱引用( CNode* )。

Note: Light userdata are probably best avoided except for use in the internal implementation. 注意:最好避免使用轻量级用户数据,除非在内部实现中使用。 The reason being that light userdata are essentially equivalent to C pointers and thus can point to just about anything. 原因是轻的用户数据本质上等效于C指针,因此几乎可以指向任何东西。 If you expose light userdata to Lua code, you will have no idea where the light userdata may have come from or what type of data it contains, using it poses a security risk (as well as the possibility to segfault your program). 如果将轻量用户数据公开给Lua代码,您将不知道轻量用户数据可能来自何处或包含何种类型的数据,使用它会带来安全风险(以及对程序进行段错误处理的可能性)。 An appropriate way to use light user data would be to use it as an index of a Lua lookup table stored in the Lua registry, which can be used to implement the memoization that was mentioned earlier. 使用轻量用户数据的一种适当方法是将其用作存储在Lua注册表中的Lua查找表的索引,该索引可用于实现前面提到的备注。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM