Lua 支持虛函數的解決方案
概述
lua本身沒有提供類似C++虛函數機制,調用的父類方法調用虛函數可能會出現問題。
問題分析
分析這段代碼和輸出
local Gun = {}
-- 示例,實際應用還要考慮構造,虛表等情況
function LuaClass(Class, Parent)
setmetatable(Class, {__index = Parent})
Class._Super = Parent
end
function Gun:Attack()
print("開始攻擊");
self:Load()
self:Fire()
end
function Gun:Load()
print("裝彈");
end
function Gun:Fire()
print("開槍");
end
Gun:Attack();
local Cannon = {}
LuaClass(Cannon, Gun)
function Cannon:Attack()
print("大炮開始攻擊")
self._Super:Attack()
end
function Cannon:Fire()
print("開炮")
end
print("-------------------------------------")
Cannon:Attack()
輸出:
紅線圈出的地方虛函數調用錯誤,應該打印”開炮”。
使用元表來面向對象時,要注意__index元方法的語義:
當你通過鍵來訪問 table 的時候,如果這個鍵沒有值,那麼Lua就會尋找該table的metatable(假定有metatable)中的__index 鍵。如果__index包含一個表格,Lua會在表格中查找相應的鍵
如果__index包含一個函數的話,Lua就會調用那個函數,table和鍵會作為參數傳遞給函數。
__index 元方法查看錶中元素是否存在,如果不存在,返回結果為 nil;如果存在則由 __index 返回結果
可知__index只是提供一種遞歸的查詢方式,其中並未包含虛函數的調用機制。
而
Gun:Attack() 等價於 Gun.Attack(self)
self._Super:Attack() 等價於 Gun.(Gun) 注意self._Super = Gun
所以調用父類Attack函數中,self的語義是Gun這張表,後面調用的就一直是Gun方法,所以最好調用的是Gun的Fire,而不是Cannon的Fire。
解決方案
使用指針指向調用函數的表,在調用父類的方法時,使父類的self的語義是調用者。
注意這種實現和C++的虛函數調用思路是不一樣的,細節請參考我的另一篇文章:
跳轉鏈接:c++虛函數表、多態
替換問題分析中的LuaClass方法
function LuaClass(Class, Parent)
local FindVal = function(InClass, Key)
local Raw = rawget(InClass, Key)
if nil ~= Raw then
return Raw, InClass
end
if nil ~= InClass.__Base then
return FindVal(InClass.__Base, Key)
end
end
Class.__Base = Parent
Class.__ClassPtr = Class
local Index = function(_, Key)
local Val, ClassPtr = FindVal(Parent, Key)
if nil == Val then
return
end
Class.__ClassPtr = ClassPtr
return Val
end
setmetatable(Class, {__index = Index})
local SuperIndex = function(_, Key)
return function(_, ...)
local OriClassPtr = Class.__ClassPtr
if nil == OriClassPtr.__Base then
return
end
local Val, ClassPtr = FindVal(OriClassPtr.__Base, Key)
if nil == Val then
return
end
Class.__ClassPtr = ClassPtr
local Ret = {Val(Class, ...)}
Class.__ClassPtr = OriClassPtr
return table.unpack(Ret)
end
end
Class._Super = setmetatable({}, {__index = SuperIndex})
end
輸出:
- 在__index元方法查詢的時候,標記當前調用方法所在的表。
- 在_Super的元表__index元方法查詢的時候,找到標記表的方法,使用Class表作為第一個參數self傳入。
備註
- 支持虛函數有性能開銷,可以在LuaClass加個參數控制是否支持虛函數。