Fork me on GitHub

lua中面向对象(class)实现探索(二)(转)

转自:https://blog.csdn.net/mywcyfl/article/details/37706247

说明:本文亦作为某章节出现在中山大学某实验室编撰的某教材中,本博客博主即该教程的编撰者,因此请不要因为看到本博客和该书中某章内容相同而认为这之间必有作假必有一方抄袭另一方。

云风的实现十分精妙但功能却有限,原因在于这样的实现无法做到一个功能,即在子类的函数中调用父类的同名函数(当然父类的同名函数中可能又调用了父类的父类的同名函数),你可能会说类不是保存了一个super指针吗?我不可以这样吗?

这样是行不通的,原因在于super变量保存在类中,而self只能访问到类的vtbl中的属性或方法。

你又或许会说,那我这样呢?

这样也是不行的,此时super访问到了,但是hello()函数访问不到,原因也很简单请自行分析。

         为了解决上述的问题,我们对这份实现做出几个修改。

         首先为了解决能在对象中通过self来访问super变量这个问题,我们将对class_type设置元表的操作提到class_type.super被定义赋值之前,也即这样做:

这样做的原因很简单,将对class_type设置元表的操作提前,那么之后在class_type中进行的定义super属性(也包括ctor和new属性)就会因为元表中的元方法而最后被在vtbl中创建出,从而实现了在对象中通过self来访问。

         这样做了之后,通过self可以访问super 变量了,是否就意味着可以在子类函数中调用父类的同名函数了呢?比如这样:

看起来似乎没问题,实际上还是行不通的,问题在于” : ”(冒号)这个语法糖。 : 的作用在于它几乎以代码替换的方式将 : 左边紧接着的变量作为第一个参数传入了函数,因此self.super:hello()这句代码被lua虚拟机解释后的结果类似于下面这句:

self.super.hello(super )

看出问题来了吗?hello()函数这里原本希望接受的第一个参数应该是上述代码里的self(即实例化后的对象),也即上述代码原本期望被lua虚拟机解释的结果应该类似下面这句:

         self.super.hello(self )

         相对正确的做法是不利用语法糖,手动填写函数的第一个参数,也即直接写成期望被lua虚拟机解释后的样子:

         self.super.hello(self )

         至此,问题似乎得到了完美的解决,但真的如此吗?我们来考虑如下的情况,假设我们有这样的一份代码:

--父类
base = class()
function base:ctor(val)
    print("base ctor")
    self._cnt = val or 0
end
function base:show()
    print("in baseshow")
end
 
--子类1
child1 = class(base)
function child1:ctor()
    print("child1 ctor")
end
function child1:show()
    self.super.show(self)
    print("in child1show ")
end
 
--子类2
child2 = class(child1)
function child2:ctor()
    print("child2 ctor")
end
function child2:show()
    self.super.show(self)
    print("in child2show")
end
 
function child2:create(...)
    local o = self.new(...)
    return o
end
 
o = child2:create()
o:show()

我们满怀期望的运行,但结果是:

很悲伤吧,lua vm一点面子都不给。它不给面子的原因在于当前的class实现在多层继承中多层调用super方法时产生溢出,也即上述测试代码中的child1:show()的第一行调用super方法本意是想调用base类中的同名方法,可是此时的self.super仍然为child1。因此在child1:show()反复调用自身,陷入不可饶恕的自递归深渊最终栈溢出。

 

三、Lua中类支持调用super方法的实现

         接着上文说,为了解决上面导致栈溢出的问题我们需要继续修改代码,在原有class函数中加入这么一段(这里只贴出代码段,最后会贴出最终的完整代码):

class_type.super = function(self, f, ...)
        assert(self and self.__type == 'object', string.format("'self' must be a object when call super(self, '%s', ...)", tostring(f)))
 
        local originBase = self.__base
        --find the first f function that differ from self[f] in the inheritance chain
        local s     = originBase
        local base  = s.__base
        while base and s[f] == base[f] do
            s = base
            base = base.__base
        end
        
        assert(base and base[f], string.format("base class or function cannot be found when call .super(self, '%s', ...)", tostring(f)))
        --now base[f] is differ from self[f], but f in base also maybe inherited from base's baseClass
        while base.__base and base[f] == base.__base[f] do
            base = base.__base
        end
 
        -- If the base also has a baseclass, temporarily set :super to call that baseClass' methods
        -- this is to avoid stack overflow
        if base.__base then
            self.__base = base
        end
 
        --now, call the super function
        local result = base[f](self, ...)
 
        --set back
        if base.__base then
            self.__base = originBase
        end
 
        return result
    end

上述代码主要做了两个事:

1.      子类对象中调用super的f方法时,将沿着继承链表(super chain)自上而下在众父类中寻找第一个与自身f方法不同地址的类,那么这个类中的f方法就是需要寻找的。也即下面这段:

while base.__base and base[f] == base.__base[f] do
            base = base.__base
        end

1.      找到同名不同地址的f方法后,判断当前类是否还有基类。如果有,则临时性的改变继承关系使得如果在该f方法中亦调用了super中的f方法,不至于栈溢出。

加入这个super函数后,子类对象中调用父类同名函数的方式改为:

                  self:super(‘funcName’, …)

         也即之前的测试代码中的如下两个函数:

function child1:show()
    self.super.show(self)
    print("in child1show ")
end
 
function child2:show()
    self.super.show(self)
    print("in child2show ")
end

分别改为:

function child1:show()
    self:super('show')
    print("in child1show ")
end
 
function child2:show()
    self:super('show')
    print("in child2show")
end

点击运行,看到:

没错,这正是我们想看到的。

最后贴一下完整的代码:

-- Internal register
local _class={}
 
function class(base)
    local class_type={}
 
    class_type.__type   = 'class'
    class_type.ctor     = false
    
    local vtbl = {}
    _class[class_type] = vtbl
    setmetatable(class_type,{__newindex = vtbl, __index = vtbl})
 
    if base then
        setmetatable(vtbl,{__index=
            function(t,k)
                local ret=_class[base][k]
                vtbl[k]=ret
                return ret
            end
        })
    end
    
    class_type.__base   = base
    class_type.new      = function(...)
        --create a object, dependent on .__createFunc
        local obj= {}
        obj.__base  = class_type
        obj.__type  = 'object'
        do
            local create
            create = function(c, ...)
                if c.__base then
                    create(c.__base, ...)
                end
                if c.ctor then
                    c.ctor(obj, ...)
                end
            end
 
            create(class_type,...)
        end
 
        setmetatable(obj,{ __index = _class[class_type] })
        return obj
    end
 
    class_type.super = function(self, f, ...)
        assert(self and self.__type == 'object', string.format("'self' must be a object when call super(self, '%s', ...)", tostring(f)))
 
        local originBase = self.__base
        --find the first f function that differ from self[f] in the inheritance chain
        local s     = originBase
        local base  = s.__base
        while base and s[f] == base[f] do
            s = base
            base = base.__base
        end
        
        assert(base and base[f], string.format("base class or function cannot be found when call .super(self, '%s', ...)", tostring(f)))
        --now base[f] is differ from self[f], but f in base also maybe inherited from base's baseClass
        while base.__base and base[f] == base.__base[f] do
            base = base.__base
        end
 
        -- If the base also has a baseclass, temporarily set :super to call that baseClass' methods
        -- this is to avoid stack overflow
        if base.__base then
            self.__base = base
        end
 
        --now, call the super function
        local result = base[f](self, ...)
 
        --set back
        if base.__base then
            self.__base = originBase
        end
 
        return result
    end
 
    return class_type
end

 

posted on 2018-07-23 15:30  pengyingh  阅读(5179)  评论(0编辑  收藏  举报

导航