zoukankan      html  css  js  c++  java
  • 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即可完成元表成员注册,后者必须在创建对象实例后再注册。在实际应用中,其实只需要根据需要选用一种模型即可。

  • 相关阅读:
    oracle用户被锁死
    windows远程桌面智能调整大小
    批量ping测试脚本
    信息的组织和提取方法
    BeautifulSoup
    requests模块学习
    Telerik Fiddler 应用方法
    js 时间格式换成 把字符串(yyyymmdd)转换成日期格式(yyyy-mm-dd)记录
    vuedraggable 拖拽 应用 不同列表之间的拖拽
    vue项目图片上传 vant van-uploader 图片上传
  • 原文地址:https://www.cnblogs.com/liao0001/p/9791362.html
Copyright © 2011-2022 走看看