Lua 支持虚函数的解决方案

概述

lua的__index元方法本身没有提供类似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() 

输出:
Lua 支持虚函数的解决方案
红线圈出的地方虚函数调用错误,应该打印"开炮"。
使用元表来面向对象时,要注意__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 

输出:
Lua 支持虚函数的解决方案

  • 在__index元方法查询的时候,标记当前调用方法所在的表。
  • 在_Super的元表__index元方法查询的时候,找到标记表的方法,使用Class表作为第一个参数self传入。

备注

  • 支持虚函数有性能开销,可以在LuaClass加个参数控制是否支持虚函数。
发表评论

评论已关闭。

相关文章