lua绑定C++对象系列三——进阶模型

在系列第二篇文章lua绑定C++对象基础模型中,代码处理上较为麻烦。针对Student.setAge()或者Student.getAge(), 必须包装两个set_age()和get_age()的函数,首先取到对象student的实例指针,再进行调用,整个代码书写较为复杂。一旦这样需要绑定的类很多时,代码量巨大,能否省掉set_age()和get_age()这种多余的包装?

 

如果要减少包装,能否在一个地方把要注册的C++方法统一起来,抽象一个call_func的公共接口出来,所有的C++成员函数都注册成call_func函数,通过call_func的不同参数来区分调用不同的成员函数。

修改代码如下r_oo2.cpp

  1 #include <iostream>
  2 #include <cstring>
  3 #include <stdlib.h>
  4 extern "C" {
  5 #include <lua.h>
  6 #include <lualib.h>
  7 #include <lauxlib.h>
  8 }
  9 #include "comm.h"
 10 #include "luna.h"
 11 #include "lunar.h"
 12 
 13 using namespace std;
 14 
 15 class Shape;
 16 typedef int (Shape::*ShapePF)(lua_State* L);
 17 
 18 struct method
 19 {
 20     const char *name;
 21     ShapePF pf;
 22 };
 23 
 24 class Shape{
 25     public:
 26         Shape(int i, int j):x(i),y(j){}
 27         int setX(lua_State* L){
 28             int val = lua_tointeger(L, -1);
 29             x = val;
 30             return 0;
 31         }
 32         int setY(lua_State* L){
 33             int val = lua_tointeger(L, -1);
 34             y = val;
 35             return 0;
 36         }
 37         int getX(lua_State* L){
 38             lua_pushinteger(L, x);
 39             return 1;
 40         }
 41         int getY(lua_State* L){
 42             lua_pushinteger(L, y);
 43             return 1;
 44         }
 45         int print(lua_State* L){
 46             cout<<"x: " << x << " y: " << y << endl;
 47             return 0;
 48         }
 49         int autoDelete(lua_State* L){
 50             cout<<"auto gc:" << x <<", "<< y << endl;
 51             return 0;
 52         }
 53     public:
 54         static char name[32];
 55         static method methods[100];
 56     public:
 57         int x = 10;
 58         int y = 11;
 59 };
 60 
 61 char Shape::name[] = "Shape";
 62 method Shape::methods[] = {
 63     {"setX", &Shape::setX},
 64     {"setY", &Shape::setY},
 65     {"getX", &Shape::getX},
 66     {"getY", &Shape::getY},
 67     {"print", &Shape::print},
 68     {"delete", &Shape::autoDelete},
 69     {NULL, NULL},
 70 };
 71 
 72 int delete_shape(lua_State *L)
 73 {
 74     Shape** p = (Shape**)lua_touserdata(L, 1);
 75     (*p)->autoDelete(L);
 76     return 0;
 77 }
 78 
 79 int call_shape(lua_State *L)
 80 {
 81     Shape** p = (Shape**)lua_touserdata(L, 1);
 82     lua_remove(L, 1);
 83 
 84     method* v = (method*)lua_topointer(L, lua_upvalueindex(1));
 85     cout<<"call_shape:"<<v->name<<endl;
 86 
 87 
 88     return ((*p)->*(v->pf))(L);
 89 }
 90 
 91 int n_call_shape(lua_State *L)
 92 {
 93     method* v = (method*)lua_topointer(L, lua_upvalueindex(1));
 94     cout<<"n_call_shape:"<<v->name<<endl;
 95 
 96     Shape* p = (Shape*)lua_topointer(L, lua_upvalueindex(2));
 97 
 98 
 99     return ((p)->*(v->pf))(L);
100 }
101 
102 int create_shape(lua_State *L)
103 {
104     lua_remove(L, 1);
105     int x = lua_tointeger(L, 1);
106     int y = lua_tointeger(L, 2);
107     Shape** p = (Shape**)lua_newuserdata(L, sizeof(Shape*));
108     *p = new Shape(x, y);
109 
110     luaL_getmetatable(L, "MetaShape");
111     lua_setmetatable(L, -2);
112 
113     luaL_getmetatable(L, "MetaShape");
114     for (method* l = Shape::methods; l->name; l++)
115     {
116         char s[32] = "n_";
117         lua_pushlightuserdata(L,(void*)l);
118         lua_pushlightuserdata(L,(void*)*p);
119         lua_pushcclosure(L, n_call_shape, 2); 
120         lua_setfield(L, -2, strcat(s, l->name));
121     }   
122 
123     lua_pop(L, 1); 
124 
125     return 1;
126 }
127 
128 int lua_openShape(lua_State *L) 
129 {
130     //原表Shape
131     if (luaL_newmetatable(L, "MetaShape"))
132     {   
133         //注册Shape到全局
134         lua_newtable(L);
135         lua_pushvalue(L, -1);
136         lua_setglobal(L, "Shape");
137 
138         //设置Shape的原表,主要是__call,使其看起来更像C++初始化
139         lua_newtable(L);
140         lua_pushcfunction(L, create_shape);
141         lua_setfield(L, -2, "__call");
142         lua_setmetatable(L, -2);
143         lua_pop(L, 1); //把全局Shape pop 出去,这时stack只有MetaShape
144 
145 
146         //设置Shape的方法列表
147         for(method* l = Shape::methods; l->name; l++)
148         {   
149             lua_pushlightuserdata(L,(void*)l);
150             lua_pushcclosure(L, call_shape, 1); 
151             lua_setfield(L, -2, l->name);
152         }   
153 
154         //设置MetaShape指向自己
155         lua_pushvalue(L, -1);
156         lua_setfield(L, -2, "__index");
157         lua_pushcfunction(L, delete_shape);
158         lua_setfield(L, -2, "__gc");
159     }   
160 }
161 
162 int main(int argc, char* argv[])
163 {
164     cout<<"hello world"<<endl;
165     lua_State *L = luaL_newstate();
166     luaL_openlibs(L);
167     luaL_dofile(L, “tree.lua”);
168 
169     lua_openShape(L);
170     print_stack(L);
171 
172     luaL_dofile(L, "r_oo2.lua");
173     print_stack(L);
174     lua_settop(L, 0); 
175     cout << endl;
176 }

如上对比基础版本,最明显的区别有几点:

1、抽象公共的接口call_shape,把所有的需要注册的函数统一到Shape::methods进行预定义,这样所有的函数注册可以统一到call_shape里面。当然call_shape为了区分不同的函数调用,需要通过参数区分。所以student版本只需要lua_pushcfunction进行简单调用即可,而shape版本使用upvalue + lua_pushcclosure的方式(等于function + param绑定在一起)。元表结构如下:

 

2、这里多了一个全局Shape表,并且注册了全局Shape表的元方法__call = create_shape,这主要是为了local shape = Shape(100, 200);这种调用自动触发全局表Shape的元方法__call调用create_shape, 使其初始化更接近C++的初始化方法。不过这里只是一个演示作用,其实这种做法会理解起来稍微复杂一点,每次regist 一个C++类的时候,会创建一个元表MetaShape和全局表Shape。也可以像student类那样,使用luaL_register简单注册一个create_shape函数,显示调用创建类实例即可。

 

其中r_oo2.lua:

 1 do
 2 local shape = Shape(100, 200);
 3 print_tree(shape)
 4 print_metatable(shape)
 5 
 6 print(shape:getX(),shape:getY())
 7 shape:setX(101)
 8 shape:setY(201)
 9 shape:print();
10 print(shape:getX(), shape:getY())
11 end
12 collectgarbage("collect");
13 
14 do
15 print ""
16 print "new method......"
17 local shape1 = Shape(1000, 2000);
18 print(shape1.n_getX(), shape1.n_getY())
19 shape1.n_setX(1001)
20 shape1:n_setY(2001)
21 shape1.n_print();
22 print(shape1.n_getX(), shape1:n_getY())
23 end
24 collectgarbage("collect");

 

进一步优化:

因为每次在进行shape.setX 和 shape:setX调用时,后者会默认传入shape对象本身,在进行call_shape调用时需要首先取到自身实例对象再使用。我们这里同时注册了n_call_shape函数,在pushcclosure时,传入两个参数,实例对象指针+method地址,这样n_call_shape函数中,只需要通过upvalue就能取到实例对象。这样shape.n_setX(100)和shape:n_setX(100)虽然传入的第一个参数有区别,但n_call_shape可以同等对待,让业务层调用更加简单,无需关注过多细节。

 

缺点:

但要注意的是,因为n_call_shape使用的closure包含了实例对象指针本身,所以在regist的时候并不能pushcclosure,这时候实例对象还没有创建,只能推迟到create_shape调用时去注册成员方法。

这样做的弊端也很明显,我们知道一种C++类共享一个MetaShape是为了避免创建多份table,节省内存。如果使用call_shape的方式,所有实例共享一套注册函数。但使用call_shap_n的方式,因为闭包里面含有不同实例对象的地址,那么每个实例对象的注册函数都不相同,会相互覆盖。

 

运行结果如下:

==========Total:1==========
idx:-1 type:5(table) 0x8c68c0
===========================
MetaShape: 0x8c6a38
not a table
table: 0x8c68c0
__index table: 0x8c68c0+
delete  function: 0x8c4db0
setX    function: 0x8c4c90
n_setY  function: 0x8c6ce0
setY    function: 0x8c4d00
n_delete function: 0x8c6e20
n_print function: 0x8c6dd0
n_getY  function: 0x8c6d80
n_setX  function: 0x8c6c90
n_getX  function: 0x8c6d30
__gc    function: 0x402734
print   function: 0x8c4d70
__name  MetaShape
getX    function: 0x8c4e00
getY    function: 0x8c6900

call_shape:getX
call_shape:getY
100     200
call_shape:setX
call_shape:setY
call_shape:print
x: 101 y: 201
call_shape:getX
call_shape:getY
101     201
auto gc:101, 201

new method......
n_call_shape:getX
n_call_shape:getY
1000    2000
n_call_shape:setX
n_call_shape:setY
n_call_shape:print
x: 1001 y: 2001
n_call_shape:getX
n_call_shape:getY
1001    2001
auto gc:1001, 2001
==========Total:1==========
idx:-1 type:5(table) 0x8c68c0
===========================

通过运行结果:红色标记部分是MetaShape元表定义。同时也可以看到shape1类使用:或者.两种不同的符号进行引用也都正常。所以r_oo2.cpp尝试了两种不同的方式注册call_shape和call_shape_n,这里总结整体结构如下:

 

 

这里的的call_shape和n_call_shape只是为了增加对比性,他们的差异在于后者的upvalue多了一个对象指针地址,方便获取对象本身,这种差异性也就决定了元表成员方法设置的时机不同。前者在regist即可完成元表成员注册,后者必须在创建对象实例后再注册。在实际应用中,其实只需要根据需要选用一种模型即可。

posted @ 2018-10-15 15:50  liao0001  阅读(551)  评论(0编辑  收藏  举报