在 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 中?
最佳答案
这里可以执行两个级别的内存优化。在您的第一个功能但效率低下的解决方案中,当用户调用 getLeftNode()
时,它必须创建 CNode
的拷贝以便将其存储在 Lua 用户数据中.此外,每次用户在同一棵树上重复调用 getLeftNode()
时,它将继续创建新的用户数据来表示 CNode
,即使它之前已创建。
在第一级优化中,可以memoize这个调用,这样每次用户请求同一个子树时,都可以简单的返回原来创建的userdata,而不用复制构造另一个userdata 来表示相同的东西。不过,有 3 种方法可以解决这个问题,具体取决于您是想修改 Lua 接口(interface)、更改 C++ 实现,还是硬着头皮。
MyLua.treenode
用户数据当前包含一个CNode
对象的实际数据。然而,这是不幸的,因为这意味着每当你创建一个CNode
对象时,你必须使用 placement new 将它存储到创建时立即由 Lua 分配的内存中。可能更好的方法是在MyLua.treenode
的用户数据中简单地存储一个指针(CNode*
)。这确实需要您修改MyLua.treenode
的 Lua 接口(interface),以便它现在将其数据视为指向CNode
对象的指针。如果您希望将
CNode
数据直接存储在MyLua.treenode
用户数据中,那么您必须确保在创建您的CTreeRoot
,它会使用 placement new 每次从 Lua 分配的内存中构造CNode
(或者你可以使用 C++ 标准库中使用的 allocator pattern?)。然而,这不太优雅,因为您的CNode
实现现在取决于 Lua 运行时,我不推荐这样做。如果以上两种方案都不合适,那么每次返回子节点都只需要复制一份,不过对于在同一节点上重复调用还是可以提高效率的 em> 通过跟踪您之前是否创建了相同的用户数据(例如,使用 Lua 表)。
在第二级优化中,您可以通过使复制的子树成为对原始树的片段的弱引用 来进一步节省内存。这样,无论何时复制子树,都只是创建一个指向原始树的一部分的指针。或者,如果您希望您的子树即使在原始树被销毁后仍然存在,您也可以使用强引用,但是您将不得不进入引用计数的血淋淋的细节。在任何一种情况下,这种优化都纯粹是在 C++ 级别上进行的,与 Lua 接口(interface)无关,从您的代码来看,我假设您已经在使用弱引用 (CNode*
)。
注意:除非在内部实现中使用,否则最好避免使用轻型用户数据。原因是轻型用户数据本质上等同于 C 指针,因此可以指向任何东西。如果您将轻量级用户数据暴露给 Lua 代码,您将不知道轻量级用户数据可能来自哪里或它包含什么类型的数据,使用它会带来安全风险(以及程序段错误的可能性)。 适当使用轻型用户数据的方法是将其用作存储在 Lua 注册表中的 Lua 查找表的索引,它可用于实现前面提到的内存。
关于c++ - 将已存在的对象从 C++ 返回到 Lua,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/14788319/