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

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM