概述

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加个参数控制是否支持虚函数。

Lua 支持虚函数的解决方案的更多相关文章

  1. LUA使用虚函数与使用回调函数

    ------------------虚函数overload-------------------------- --回调:寻路中格子坐标改变 CHero.OnSearchToCellsChange = ...

  2. C++中的虚函数总结

    一.什么是虚函数.纯虚函数.抽象基类 虚函数:在某基类中声明为 virtual 并在一个或多个派生类中被重新定 义的成员函数. 纯虚函数:是一种特殊的虚函数,使用virtual关键字,并且在其后面加上 ...

  3. Lua中如何实现类似gdb的断点调试—07支持通过函数名称添加断点

    我们之前已经支持了通过函数来添加断点,并且已经支持了行号的检查和自动修正.但是通过函数来添加断点有一些限制,如果在当前的位置无法访问目标函数,那我们就无法对其添加断点. 于是,本篇我们将扩展断点设置的 ...

  4. Lua 字符串查找函数 string.find(s, pattern [, init [, plain]] )【转】

    函数原型 string.find(s, pattern [, init [, plain]] ) s: 源字符串 pattern: 待搜索模式串 init: 可选, 起始位置 plain: 我没用过 ...

  5. C++虚函数和函数指针一起使用

    C++虚函数和函数指针一起使用,写起来有点麻烦. 下面贴出一份示例代码,可作参考.(需要支持C++11编译) #include <stdio.h> #include <list> ...

  6. EC笔记,第二部分:9.不在构造、析构函数中调用虚函数

    9.不在构造.析构函数中调用虚函数 1.在构造函数和析构函数中调用虚函数会产生什么结果呢? #; } 上述程序会产生什么样的输出呢? 你一定会以为会输出: cls2 make cls2 delete ...

  7. C++之虚函数和多态

    干货较多-需要自己深思理解: C++支持两种多态性: 1.编译时多态性(静态绑定-早绑定) 在程序编译阶段即可以确定下来的多态性 通过使用 重载机制(重载函数)实现 (模板)http://blog.c ...

  8. windows 下 gvim/vim lua支持问题,neocomplete等插件支持

    此文是按照知乎 https://www.zhihu.com/question/29333426 "windows下vim的lua支持问题?" 一文汇总的解决方案. 题主提供了不错的 ...

  9. C++学习基础十二——纯虚函数与抽象类

    一.C++中纯虚函数与抽象类: 1.含有一个或多个纯虚函数的类成为抽象类,注意此处是纯虚函数,而不是虚函数. 2.如果一个子类继承抽象类,则必须实现父类中的纯虚函数,否则该类也为抽象类. 3.如果一个 ...

随机推荐

  1. 交警也觉得妙——Python 识别车牌

    车牌识别在高速公路中有着广泛的应用,比如我们常见的电子收费(ETC)系统和交通违章车辆的检测,除此之外像小区或地下 车库门禁也会用到,基本上凡是需要对车辆进行身份检测的地方都会用到. 一些背景: 车牌 ...

  2. Maven配置【详细】

    参考网址:https://www.jianshu.com/p/f2f52a062d5b

  3. # NC20860 兔子的区间密码

    NC20860 兔子的区间密码 题目 题目描述 有一只可爱的兔子被困在了密室了,密室里有两个数字,还有一行字: 只有解开密码,才能够出去. 可爱的兔子摸索了好久,发现密室里的两个数字是表示的是一个区间 ...

  4. java反序列化漏洞专项

    背景条件:java的框架,java的应用程序,使用序列化,反序列化操作非常多.在漏洞挖掘中,代码审计中,安全研究中,反序列化漏洞是个重点项,不可忽视.尤其是java应用程序的rce,10个里面有7个是 ...

  5. 【RocketMQ】消息的刷盘机制

    刷盘策略 CommitLog的asyncPutMessage方法中可以看到在写入消息之后,调用了submitFlushRequest方法执行刷盘策略: public class CommitLog { ...

  6. 数论之欧几里德gcd

    序:这篇博客我最开始学的时候写的,后来又学了一遍,自我感觉这篇好像有问题,扩展欧几里得建议走这边 首先先说,欧几里德一共有俩,欧几里德和扩展欧几里德,前者非常简单,后者直接变态(因为我太菜) gcd ...

  7. SP96 SHOP-Shopping 题解

    \(To\) \(SP96\) 这是一道比较简单的 \(bfs\) ,初学者可以锻炼一下自己理解题意和改代码的能力. 题目中有几个细节: \(n\) 和 \(m\) 的输入顺序,应该先输入 \(m\) ...

  8. dijkstra最短路算法(堆优化)

    这个算法不能处理负边情况,有负边,请转到Floyd算法或SPFA算法(SPFA不能处理负环,但能判断负环) SPFA(SLF优化):https://www.cnblogs.com/yifan0305/ ...

  9. 无意苦争春,一任群芳妒!M1 Mac book(Apple Silicon)能否支撑全栈工程师的日常?(Python3/虚拟机/Docker/Redis)

    原文转载自「刘悦的技术博客」https://v3u.cn/a_id_187 就像大航海时代里突然诞生的航空母舰一样,苹果把玩着手心里远超时代的M1芯片,微笑着对Intel说:"不好意思,虽然 ...

  10. Luogu5104 红包发红包 (期望)

    曾几何时有人说概率期望easy... 显然,期望是\(\frac{w}{2^n}\) #include <iostream> #include <cstdio> #includ ...