• SLua 中继承 C# 类接口 Slua.Class 的一个 Bug。


      由于目前要把大量的代码移植到 lua 中(真是够虐心的),面向对象肯定少不了,项目的代码都是这么设计的,于是就测试 Slua.Class 接口来扩展 C# 的类,发现有点问题,给作者提交了一个 Issue 和 一个 Pull Request,作者也很快确认并 Merge 了。

      问题是这样:当使用 Slua.Class 继承出来的类,实例化出来的所有实例都指向了最后一个实例,导致访问属性都是一样的。比如使用 main.txt 中得一段代码修改测试:

    -- test inherite class
    local mv = My2(1, 2, 3)
    local mv_2 = My2(4, 5, 6)      -- I add for test.
    mv:Normalize()
    mv_2:Normalize()       -- I add for test.
    print("mv norm:", mv.x, mv.y, mv.z)      -- I modified for test.
    print("mv_2 norm:", mv_2.x, mv_2.y, mv_2.z)      -- I add for test.
    mv:Set(10, 20, 30)      -- I modified for test.
    mv_2:Set(40, 50, 60)      -- I add for test.
    print("mv:", mv.x, mv.y, mv.z)      -- I add for test.
    print("mv_2:", mv_2.x, mv_2.y, mv_2.z)      -- I add for test.

    结果将输出如下:

    mv norm:    0.62469504755442    0.78086880944303    0.93704257133164
    mv_2 norm:    0.62469504755442    0.78086880944303    0.93704257133164
    mv:    40    50    60
    mv_2:    40    50    60

      在以上结果中,My2 的实例 my, my_2 构造的值是不同的,但输出相同的结果。看看 Slua.Class 的代码,在 Helper.cs 中:

    local getmetatable=getmetatable
    local function Class(base,static,instance)
        local mt = getmetatable(base)
        local class=static or {}
        setmetatable(class, 
            {
                __call=function(...)
                    local r = mt.__call(...)
                    local ret = instance or {}
                    ret.__base=r
                    local ret = setmetatable(ret,{
                        __index=function(t,k)
                            return r[k]
                        end,
                        __newindex=function(t,k,v)
                            r[k]=v
                        end,
                    })
                    return ret
                end,
            }
        )
        return class
    end
    return Class

      以上代码中,ret 是类的模板,用来为各个实例化对象提供方法和属性,不应该被构造时返回(而且上面每次构造都返回了相同的一个 ret),但是 ret 应该是大家 shaderd,构造返回的对象应该是一个新构造的对象,且 __index 为 ret,这样既能获取派生类的各种方法属性,又不会不小心修改 ret。

      同时,我做了如下的一些小修改:

    1. 可以直接使用派生类调用积累的静态成员方法,如基类 Base.ShowStatic(),那么派生类可以直接使用:Derived.ShowStatic();
    2. 增加了一个名为 ctor 的可选构造函数(这个借鉴了云风给出的 lua-oop 方案);
    3. 保持通过访问父类方法使用 __base,但注意不用使用这个来访问父类成员变量,因为当你第一次在派生类访问父类变量,会被复制到派生类,所以可能会访问到错误的数据,只有派生类的才是有效的。

      修改完的代码如下:

    local getmetatable = getmetatable
    local function Class(base,static,instance)
        local mt = getmetatable(base)
        local class = static or {}
        setmetatable(class, 
            {
                __index = base,
                __call = function(...)
                    local r = mt.__call(...)
                    local ret = instance or {}
                    local ins_ret = setmetatable(
                        {
                            __base = r,
                        },
                        {
                            __index = function(t, k)
                                local ret_field
                                ret_field = ret[k]
                                if nil == ret_field then
                                    ret_field = r[k]
                                end
                                t[k] = ret_field
                                return ret_field
                            end,
                        })
                    if ret.ctor then
                        ret.ctor(ins_ret, ...)
                    end
                    return ins_ret
                end,
            }
        )
        return class
    end
    return Class

      使用跟以前一样,但可以增加一个构造函数:

    MyVector3 = Slua.Class(Vector3,
    {
    },
    {
        -- This is optional.
        ctor = function(self)
            print("Do something...")
        end,
    })

      但是我觉得还是有点小问题,以上书写新的扩展类代码的时候不是太方便,不能分开单独写每个成员变量和函数,也可以墙纸分开,但命名上不太好看,于是我自己又做了如下修改:

    local getmetatable = getmetatable
    local function Class(base)
        local mt = getmetatable(base)
        local class = {}
        class.ctor = false
        setmetatable(class, 
            {
                __index = base,
                __call = function(...)
                    local r = mt.__call(...)
                    local ins_ret = {__base = r,}
                    setmetatable(ins_ret,
                        {
                            __index = function(t, k)
                                local ret_field
                                ret_field = rawget(class, k)
                                if nil == ret_field then
                                    ret_field = r[k]
                        if 'function' == type(ret_field) then
                                            class[k] = ret_field
                        else
                            ins_ret[k] = ret_field
                        end
                                end
                                return ret_field
                            end,
                        })
    
                    if class.ctor then
                        class.ctor(ins_ret, ...)
                    end
    
                    return ins_ret
                end,
            }
        )
        return class
    end
    return Class

    这样的话,我就可以更方便的定义类,符合以前的书写习惯,同时,优化一下,当访问派生类不存在的的父类成员时,之拷贝函数,不拷贝成员变量,以免浪费空间。这样我可以这样书写:

    MyVector3 = Slua.Class(Vector3)
    
    -- Constructor, optional.
    function MyVector3:ctor()
        print("Do something!")
    end
    
    -- Instance method.
    function MyVector3:Normaize()
        --Do your own normalize.
    end
    
    -- Static method.
    function MyVector3.PrintMyName
        print("MyVector3")
    end

      但作者说如果不是 bug,只是为了方便,最后这个不能修改,因为要考虑兼容性,已经有人这么用了,确实是这样,所以我就把这个提交到自己的另一个分支里,在自己的项目使用新方法。

     
     
  • 相关阅读:
    HDU 5492 Find a path
    codeforce gym 100548H The Problem to Make You Happy
    Topcoder SRM 144 Lottery
    codeforce 165E Compatible Numbers
    codeforce gym 100307H Hack Protection
    区间DP总结
    UESTC 1321 柱爷的恋爱 (区间DP)
    HDU 4283 You Are the One (区间DP)
    HDU 2476 String painter (区间DP)
    UESTC 426 Food Delivery (区间DP)
  • 原文地址:https://www.cnblogs.com/yaukey/p/4545093.html
Copyright © 2020-2023  润新知