在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 userdata中.此外,每次用户在同一个树上重复调用getLeftNode()时,它将继续创建新的userdata来表示CNode,即使它之前已经创建过.
在第一级优化中,您可以记住此调用,以便每次用户请求相同的子树时,您只需返回最初创建的用户数据而不是复制并构造另一个用户数据来表示相同的事物.但是有三种方法,取决于你是想修改Lua接口,改变C实现,还是只是咬紧牙关.
> MyLua.treenode userdata当前包含CNode对象的实际数据.然而,这很不幸,因为这意味着每当您创建一个CNode对象时,您必须使用placement new将其存储到Lua在创建时立即分配的内存中.可能更好的是简单地在MyLua.treenode的userdata中存储指针(CNode *).这确实需要您修改MyLua.treenode的Lua接口,以便它现在将其数据视为指向CNode对象的指针.
>如果您希望直接将CNode数据存储在MyLua.treenode用户数据中,那么您必须确保在创建CTreeRoot时,它将使用placement new来每次从Lua分配的内存中构建CNode(或者可能你可以使用C标准库中使用的allocator pattern吗?).这不太优雅,但是因为您的CNode实现现在依赖于Lua运行时,我不建议这样做.
>如果上述解决方案都不合适,那么只要你返回一个子节点就必须复制一份,尽管你仍然可以通过跟踪你是否创建了相同的节点来提高重复调用的效率userdata之前(例如,使用Lua表).
在第二级优化中,您可以通过使复制的子树成为对原始树的片段的弱引用来进一步节省内存.这样,无论何时复制子树,您只需创建指向原始树的一部分的指针.或者,如果您希望子树在原始树被破坏后仍然存在,则可以使用强引用,但是您必须进入引用计数的血腥细节.在任何一种情况下,这种优化纯粹是在C级别上并且与Lua接口无关,并且根据您的代码判断我假设您已经在使用弱引用(CNode *).
注意:除了在内部实现中使用外,最好避免使用轻量级用户数据.原因是轻用户数据基本上等同于C指针,因此几乎可以指向任何东西.如果您将轻用户数据暴露给Lua代码,您将不知道轻用户数据可能来自何处或它包含哪种类型的数据,使用它会带来安全风险(以及可能会使程序发生段错误).使用轻量级用户数据的适当方法是将其用作Lua注册表中存储的Lua查找表的索引,该表可用于实现前面提到的memoization.