4

我一直在使用 LuaPlus 来使用脚本语言公开模块的功能。为此,LuaPlus 真的很棒,但我坚持清理我的暴露对象,因为我不知道如何处理删除代表我的 c++ 对象的 lua 对象,所以我可以正确释放 c++ 资源。

我正在使用 lua 表和元表来表示 cpp 对象,将指向 cpp 对象的指针作为表的 lightuserdata 参数“__object”传递,所以我可以做类似的事情

function foo() 
  local p = MyCppObject:new()   //Create a cpp object and bind it to a table+metatable
  p:do_something()        //Correctly calls the cpp member function do_something
  ....
  (exits scope)           //No more references to p, so p is deleted.
 end

函数退出后(或一段时间后),我希望调用元表方法 "__gc",我在其中调用内部 cpp 对象的删除,但我没有看到我的 cpp 回调被调用全部。我尝试使用 lua 的函数collectgarbage强制垃圾收集,多次调用我的函数来强制 lua 收集我的对象,但我看不到我的回调正在执行。最重要的是,我看到调用 collectgarbage("count") 的结果有时会减少,因此某些内容会在某处被删除,但我不知道是什么。我检查了 lua 文档,但我看不出我做错了什么:(

任何评论表示赞赏!谢谢!


更新:添加了 c++ 代码端 + 添加了 Mud 指出的本地 + 我的测试示例

我创建了我的程序的这个小样本。LuaShell 对象只是状态的包装器 + 读取命令行并执行从 std::cin 读取的字符串的循环

#include <iostream>
#include "LuaPlus.h"
class Point
{
  private:
      int x_,y_;
  public:
  Point(): x_(0), y_(0){}
  Point(int a, int b): x_(a), y_(b){}

  ~Point() {std::cout << "Point "<< x_ << ","
                      << y_ << "being deleted" << std::endl;} 

  int x() const  { return x_;} 
  int y() const  { return y_;}     
};

LuaPlus::LuaObject metatable;

int new_point( LuaPlus::LuaState* state)
{
  LuaPlus::LuaStack args(state);
  //std::cout << "Creating point!!" << std::endl;
  float x = 0, y = 0;
  if ( args.Count() == 3)
  {
      if (args[2].IsNumber() && args[3].IsNumber())
      {
          x = args[2].GetFloat();
          y = args[3].GetFloat();
      }
  }

  Point* p = new Point(x,y);
  LuaPlus::LuaObject lua_obj = state->CreateTable();
  lua_obj.SetLightUserData("__object", p);
  lua_obj.SetMetaTable( metatable );

  return 1;
}

int my_gc_event( LuaPlus::LuaState* state) 
{
  std::cout << "Calling gc_event from lua" << std::endl;
  return 0;
}

int main()
{

  /* Creating the object that holds the Lua interpreter as well as 
   * the command line
   */ 
  LuaShell::Shell shell(true);
  LuaPlus::LuaObject globals = shell.get_state()->GetGlobals();

  metatable = globals.CreateTable("PointMetaTable");
  metatable.SetObject("__index", metatable);
  metatable.Register("new", new_point);
  metatable.Register("__gc",my_gc_event);
  metatable.RegisterObjectDirect("x", (Point*)0 ,&Point::x);
  metatable.RegisterObjectDirect("y", (Point*)0 ,&Point::y);
  globals.SetObject("Point", metatable);

  //Get into the read-command-line-until-quit loop.
  shell.run();
  return 0;
}

在 lua 方面,我运行它来测试。

? k,b = collectgarbage("count") print (k*1024) 
> 33761
? for it=1,1000000 do foo() end   
? k,b = collectgarbage("count") print (k*1024) 
> 75315
? collectgarbage()  
? k,b = collectgarbage("count") print (k*1024)
> 32363

如您所见,根据 lua 运行时有“一些”垃圾收集,但是当我看到我的进程的顶级报告时,内存只会上升,而不会下降。此外,我从来没有看到来自点析构函数的消息(预计因为我并没有真正调用它)或来自“my_gc_event”内部的消息(意外,因为我认为它在收集垃圾工作期间的某个时候被调用)。

再次感谢!

4

2 回答 2

2

考虑到正确的答案(Mud 的道具)被埋在评论中,我认为最好在此处添加最终代码和答案。

正如 Mud 所说,我的问题是尝试使用表格来公开对象。虽然这样可行,但 lua 5.1 运行时(由 luaplus 使用)不会为表调用垃圾收集器事件 (__gc),因此我不得不使用 luaplus 中的 BoxPointer 辅助函数。还添加了一个映射来跟踪暴露对象的类型,因为它们作为 void* 传递,稍后您需要正确转换它们以进行适当的清理。使用一些模板技巧,我最终得到了一个非常简单的清理系统。这是代码(主要功能保持不变,以及Point类定义)。

#include <iostream>
#include <map>
#include <boost/bind.hpp>
#include <boost/function.hpp>
#include "LuaPlus.h"
#include "Shell.h"


//Function used to delete an object created and exposes to lua
template<typename type_to_delete>
void delete_wrapper(void* ptr)
{
    type_to_delete* typed_ptr = static_cast<type_to_delete*>(ptr);
    delete typed_ptr;
}

//My object metatable
LuaPlus::LuaObject metatable;
//Table to save function pointers for the correct deletion of exposed objects
std::map<void*,boost::function<void(void*)> > g_object_type_deleter;

int new_point( LuaPlus::LuaState* state)
{
    LuaPlus::LuaStack args(state);
    float x = 0, y = 0;
    if ( args.Count() == 3)
    {
        if (args[2].IsNumber() && args[3].IsNumber())
        {
            x = args[2].GetFloat();
            y = args[3].GetFloat();        
        }
    }

    Point* p = new Point(x,y);
    //This used to be a table, but given the shortcoming with the gc, now using this version
    LuaPlus::LuaObject lua_obj = state->BoxPointer(p);
    lua_obj.SetMetaTable( metatable );

    //Save the callback to delete the object we just created.
    g_object_type_deleter[static_cast<void*> (p)] = boost::bind( delete_wrapper<Point> , _1);
    return 1;
}

int my_gc_event( LuaPlus::LuaState* state) 
{
    //get the pointer lua is trying to delete.
    void *p = state->UnBoxPointer(1);

    //look for the delete callback with the type information
    std::map<void*,boost::function<void(void*)> >::iterator it = g_object_type_deleter.find(p);
    if (it == g_object_type_deleter.end() )
    {
        std::cout << "receiving callback to destroy object that was not indexed !" << std::endl;
        return 0;
    }
    //Actually call the delete function passing the pointer as parameter
    it->second(it->first);
    return 0;
}

多谢你们!

于 2012-08-20T15:55:42.323 回答
1
function foo() 
  p = MyCppObject:new()   //Create a cpp object and bind it to a table+metatable
  p:do_something()        //Correctly calls the cpp member function do_something
  ....
  (exits scope)           //No more references to p, so p is deleted.
end

p是全局的,所以它在返回时不会离开范围。如果您希望将其词法范围限定为该函数,则foo需要使用关键字。local

lightuserdata 参数 [...] 我希望调用元表方法“__gc”

Lightuserdata 不会被垃圾收集。这可能与您的问题无关(我不知道 LuaBind 是如何工作的),但我认为值得一提。


回复评论:

我编写了以下测试,它创建了一个单字节、无操作的用户数据类型,其中只有一个__gc元表:

#include "lauxlib.h"

static int foo_gc (lua_State* L) { 
   puts("__gc called");
   return 0; 
}

static int foo_new (lua_State* L) { 
   lua_newuserdata(L, 1);
   luaL_getmetatable(L, "foo");
   lua_setmetatable(L, -2);
   return 1;
}

int __declspec(dllexport) __cdecl luaopen_luagc (lua_State* L) {
   // create foo type
   static const struct luaL_reg foo[] = {
      { "__gc",        foo_gc       },
      NULL, NULL
   };
   luaL_newmetatable(L, "foo");
   luaL_openlib(L, NULL, foo, 0);

   // create constructor
   lua_register(L, "foo", foo_new);
   return 0;
}

如果我然后运行以下测试:

require 'luagc'

for i=1,5 do
   foo()
end

collectgarbage("collect")

输出是:

__gc called
__gc called
__gc called
__gc called
__gc called
于 2012-08-10T16:31:32.450 回答