分享

LuaTinker:清晰简单的lua的封装.及其中的陷阱

 quasiceo 2013-12-06
分类: C++ LUA 2008-08-06 00:35 2162人阅读 评论(1) 收藏 举报

Lua有很多封装.其中的利弊已经有不少介绍.只是提一下luaplus.本来期望值蛮高的.但后来发现文档质量不高.sample编译不过去.不过调试功能做的比较强..
最后选择了LuaTinker.一个韩国人写的.2个文件.5个sample.简单清晰.赞一个.~
 
首先试验了几个基本功能.都没什么问题.

当时比较疑惑的几个问题是.在lua中可以使用指针么?可以使用c++中的数据结构么?.

lua中的函数可以以指针为参数么?

结论是可以的.不过跟强大的luabind比起来.luatinker没有对导出枚举提供支持.

但其他方面功能并不差.

比如c++中有类A

 

  1. class A
  2. {
  3. public:
  4.        A()    {}
  5.        void test();

  6. private
  7.        A( const A& )    {}  
  8. }

 

 

lua中定义 

但如果以对象引用为参数的时候发现对象被复制了

 

.. 

  1. A& ra = a; 
  2. lua_tinker::call<void>(L, "lua_func", ra); 

 

这里的ra没有像引用一样工作.而是以值传递的方式进入的函数


 
这就奇怪了..于是开始读代码

 
代码就不贴了.不过很显然的是.LuaTinker里有写一堆处理引用的偏特化.也就是表明应该是考虑过引用的
.
那问题出在哪了呢
..
 
LuaTinker
里调用一个lua里的函数的call是这样定义的
 
 

  1. template<typename RVal, typename T1> 
  2. RVal call(lua_State* L, const char* name, T1 arg)
  3. {
  4.       lua_pushcclosure(L, on_error, 0);
  5.       int errfunc = lua_gettop(L); 
  6.       lua_pushstring(L, name);
  7.       lua_gettable(L, LUA_GLOBALSINDEX);
  8.       if(lua_isfunction(L,-1))
  9.       {
  10.            push(L, arg);
  11.            if(lua_pcall(L, 1, 1, errfunc) != 0)
  12.            { 
  13.                lua_pop(L,1);
  14.            }
  15.       }      
  16.       else
  17.       {
  18.            print_error(L, "lua_tinker::call() attempt to call global `%s' (not a function)", name);
  19.       }
  20.       lua_remove(L, -2);
  21.       return pop<RVal>(L); 
  22.  } 


 

 

 


这时会有编译错误.是说A的拷贝构造函数是私有的.也就是说自动推导出的T

A.
跟调用 Fun( a )效果是一样的
.
 
但显示指定参数


就没有问题

..
 
那么..在模板参数推导的时候.为什么引用类型会被转换成对象类型呢
?..
翻阅了c++标准以及c++primer.原来在参数自动推导中.会进行类型的隐式转换

在决定模板参数类型前,编译器执行下列隐式类型转换:

   
左值变换

   
修饰字转换

   
派生类到基类的转换

于是..引用类型.被转换为了一个左值.进一步讲.自动推导的模板函数的参数必须是值传递
.
关于转换细节.请参阅以上两本书
.
 
..看来我又踩到了c++的陷阱.以前一直没注意..写的代码可能也会有类似问题
..
 
那是否可以做个改动以避免这个陷阱呢.首先想到的是将call函数以一个类模板封装起来.因为类模板是必须要指定类型的

像这样


然后实现中显示指定LuaTinkercall的参数类型.后来发现LuaTinkercall的实现中有个push函数.也有同样的问题

.
 
push
的实现是

 
 
lua2type这里就开始对引用做处理了.而且都是类模板.不会有自动推导上的错误

.
于是把

push(L, arg);
改为

push<T1>(L, arg);
试验.成功

 
但感觉不是很方便,因为这只完成了一个参数的函数的支持.类似的还要写支持2参数.3参数的
..
LuaTinker
里只写到3参数.不够就自己写吧
..
 
于是想能不能写个类把参数封装一下.想起lokiTypelist概念很好.于是想能不能定义一个

Valuelist. 这样在使用的时候,只要保证模板参数T2也是一个ValueList类型的,就实现了递归定义可以保存任意数量的数据。同时和Typelist一样,也需要一个结束类型标志。因此定义下面的结构作为结束标志

:
然后函数的参数类型就为ValueList并为ValueList< class T1,null_type >写个特化版本就可以了

.
Typelist
技术在C++ 设计新思维中有详细的介绍.就不多说了
.
 
但这样定义起ValueList很麻烦..又不能写一个模板函数来帮助解决(因为一旦通过模板函数推导.又会牵扯到引用类型的问题
)
 
于是又想.能不能写个引用的封装.这样这个封装了引用的对象被复制也无所谓


后来想想.如果把RefHolder传入lua并使用.那就要注册这个类的某个特化版本.也就是说.如果想对类A的引用进行封装.那就要注册RefHolderlua..而且为了支持重载操作符.肯定要做更多工作

..
后来看了下luabind.如果希望传入引用.是要用Boost.Ref包装一下..那又要做很多处理
..
 
折腾了一番..最后发现...算了.我在写的时候注意点.代码里再加上些注释跟说明.以此来避免使用引用时出现bug.会简单些..

 

还有一次在使用lua_tinker::table的时候程序发生了异常.

lua_tinker::table_obj是用来操作lua中的表的类.维护了lua中堆栈的状态.

lua_tinker::table是对table_obj的一个封装.通过table可以对lua中的表进行访问和操作.发生异常后看了一下堆栈.发现是lua_tinker::table中的指向table_obj的指针出现了异常.

于是检查代码.发现table_obj维护了一个引用计数.当此计数为0时则被删除.而这个引用计数在构造函数里初始化的时候为0..当时觉得很奇怪..按照对智能指针的理解.引用计数应该初始化为1吧..后来发现在table构造的时候先new出table_obj对象.然后手动对此对象增加一个引用计数.这样也没什么问题..但仔细一看.table有三个构造函数.其中有一个只构造了table_obj对象但没有增加此对象的引用计数.

  1. lua_tinker::table::table(lua_State* L)
  2. {
  3.     lua_newtable(L);

  4.     m_obj = new table_obj(L, lua_gettop(L));

  5.     m_obj->inc_ref();
  6. }

  7. lua_tinker::table::table(lua_State* L, const char* name)
  8. {
  9.     enum_stack(L);

  10.     lua_pushstring(L, name);
  11.     lua_gettable(L, LUA_GLOBALSINDEX);

  12.     if(lua_istable(L, -1) == 0)
  13.     {
  14.         lua_pop(L, 1);

  15.         lua_newtable(L);
  16.         lua_pushstring(L, name);
  17.         lua_pushvalue(L, -2);
  18.         lua_settable(L, LUA_GLOBALSINDEX);
  19.     }

  20.     m_obj = new table_obj(L, lua_gettop(L));
  21. }

  22. lua_tinker::table::table(lua_State* L, int index)
  23. {
  24.     if(index < 0)
  25.     {
  26.         index = lua_gettop(L) + index + 1;
  27.     }

  28.     m_obj = new table_obj(L, index);

  29.     m_obj->inc_ref();
  30. }

 

感觉很奇怪..也许是粗心写漏了吧..作者又是韩国人.没法发信交流..

先自己改了..此问题解决..

 

如果哪位仁兄对此有研究.希望多多交流

 

.~

 

参考资料: http://blog.csdn.net/taodm/archive/2003/05/18/15766.aspx
 

跟踪了一下.发现当arg为类A的对象的引用时.推导出的arg的类型为A.而不是A&..
但如果显示指定参数类型的话就没问题了
..
 
这么讲可能不好理解..简单的描述一下

一个函数模板.定义如下
 
  1. templateclass T >
  2. void Fun( T t)
  3. {
  4. }

调用函数

 

更多 0
查看评论
1楼 code3d 2011-11-09 10:23发表 [回复]
你好,我也在使用lua_tinker,刚开始用,不是很熟悉。
有这么一个问题请教下,就是输出给lua使用的c++函数,需要传递一个数组参数(如int* array),我想在lua脚本中使用基于table的数组(如{1,2,3})作为参数,而传递到c++里时,我发现lua_tinker::table只有通过get(string)这样的方式使用;这样没有键的table就取不到值了。您可有使用经验不?

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多