SLua 中继承 C# 类接口 Slua.Class 的一个 Bug。
由于目前要把大量的代码移植到 lua 中(真是够虐心的),面向对象肯定少不了,项目的代码都是这么设计的,于是就测试 Slua.Class 接口来扩展 C# 的类,发现有点问题,给作者提交了一个 Issue 和 一个 Pull Request,作者也很快确认并 Merge 了。
问题是这样:当使用 Slua.Class 继承出来的类,实例化出来的所有实例都指向了最后一个实例,导致访问属性都是一样的。比如使用 main.txt 中得一段代码修改测试:
-- test inherite class
local mv = My2(, , )
local mv_2 = My2(, , ) -- I add for test.
mv:Normalize()
mv_2:Normalize() -- I add for test.
print("mv norm:", mv.x, mv.y, mv.z) -- I modified for test.
print("mv_2 norm:", mv_2.x, mv_2.y, mv_2.z) -- I add for test.
mv:Set(, , ) -- I modified for test.
mv_2:Set(, , ) -- I add for test.
print("mv:", mv.x, mv.y, mv.z) -- I add for test.
print("mv_2:", mv_2.x, mv_2.y, mv_2.z) -- I add for test.
结果将输出如下:
mv norm: 0.62469504755442 0.78086880944303 0.93704257133164
mv_2 norm: 0.62469504755442 0.78086880944303 0.93704257133164
mv: 40 50 60
mv_2: 40 50 60
在以上结果中,My2 的实例 my, my_2 构造的值是不同的,但输出相同的结果。看看 Slua.Class 的代码,在 Helper.cs 中:
local getmetatable=getmetatable
local function Class(base,static,instance)
local mt = getmetatable(base)
local class=static or {}
setmetatable(class,
{
__call=function(...)
local r = mt.__call(...)
local ret = instance or {}
ret.__base=r
local ret = setmetatable(ret,{
__index=function(t,k)
return r[k]
end,
__newindex=function(t,k,v)
r[k]=v
end,
})
return ret
end,
}
)
return class
end
return Class
以上代码中,ret 是类的模板,用来为各个实例化对象提供方法和属性,不应该被构造时返回(而且上面每次构造都返回了相同的一个 ret),但是 ret 应该是大家 shaderd,构造返回的对象应该是一个新构造的对象,且 __index 为 ret,这样既能获取派生类的各种方法属性,又不会不小心修改 ret。
同时,我做了如下的一些小修改:
- 可以直接使用派生类调用积累的静态成员方法,如基类 Base.ShowStatic(),那么派生类可以直接使用:Derived.ShowStatic();
- 增加了一个名为 ctor 的可选构造函数(这个借鉴了云风给出的 lua-oop 方案);
- 保持通过访问父类方法使用 __base,但注意不用使用这个来访问父类成员变量,因为当你第一次在派生类访问父类变量,会被复制到派生类,所以可能会访问到错误的数据,只有派生类的才是有效的。
修改完的代码如下:
local getmetatable = getmetatable
local function Class(base,static,instance)
local mt = getmetatable(base)
local class = static or {}
setmetatable(class,
{
__index = base,
__call = function(...)
local r = mt.__call(...)
local ret = instance or {}
local ins_ret = setmetatable(
{
__base = r,
},
{
__index = function(t, k)
local ret_field
ret_field = ret[k]
if nil == ret_field then
ret_field = r[k]
end
t[k] = ret_field
return ret_field
end,
})
if ret.ctor then
ret.ctor(ins_ret, ...)
end
return ins_ret
end,
}
)
return class
end
return Class
使用跟以前一样,但可以增加一个构造函数:
MyVector3 = Slua.Class(Vector3,
{
},
{
-- This is optional.
ctor = function(self)
print("Do something...")
end,
})
但是我觉得还是有点小问题,以上书写新的扩展类代码的时候不是太方便,不能分开单独写每个成员变量和函数,也可以墙纸分开,但命名上不太好看,于是我自己又做了如下修改:
local getmetatable = getmetatable
local function Class(base)
local mt = getmetatable(base)
local class = {}
class.ctor = false
setmetatable(class,
{
__index = base,
__call = function(...)
local r = mt.__call(...)
local ins_ret = {__base = r,}
setmetatable(ins_ret,
{
__index = function(t, k)
local ret_field
ret_field = rawget(class, k)
if nil == ret_field then
ret_field = r[k]
if 'function' == type(ret_field) then
class[k] = ret_field
else
ins_ret[k] = ret_field
end
end
return ret_field
end,
}) if class.ctor then
class.ctor(ins_ret, ...)
end return ins_ret
end,
}
)
return class
end
return Class
这样的话,我就可以更方便的定义类,符合以前的书写习惯,同时,优化一下,当访问派生类不存在的的父类成员时,之拷贝函数,不拷贝成员变量,以免浪费空间。这样我可以这样书写:
MyVector3 = Slua.Class(Vector3) -- Constructor, optional.
function MyVector3:ctor()
print("Do something!")
end -- Instance method.
function MyVector3:Normaize()
--Do your own normalize.
end -- Static method.
function MyVector3.PrintMyName
print("MyVector3")
end
但作者说如果不是 bug,只是为了方便,最后这个不能修改,因为要考虑兼容性,已经有人这么用了,确实是这样,所以我就把这个提交到自己的另一个分支里,在自己的项目使用新方法。
SLua 中继承 C# 类接口 Slua.Class 的一个 Bug。的更多相关文章
- Java中继承thread类与实现Runnable接口的区别
Java中线程的创建有两种方式: 1. 通过继承Thread类,重写Thread的run()方法,将线程运行的逻辑放在其中 2. 通过实现Runnable接口,实例化Thread类 在实际应用中, ...
- Java基础知识强化之多线程笔记05:Java中继承thread类 与 实现Runnable接口的区别
1. Java中线程的创建有两种方式: (1)通过继承Thread类,重写Thread的run()方法,将线程运行的逻辑放在其中. (2)通过实现Runnable接口,实例化Thread类. 2. ...
- [转] Java中继承thread类与实现Runnable接口的区别
Java中线程的创建有两种方式: 1. 通过继承Thread类,重写Thread的run()方法,将线程运行的逻辑放在其中 2. 通过实现Runnable接口,实例化Thread类 在实际应用中, ...
- 多线程——Java中继承Thread类与实现Runnable接口的区别
线程我只写过继承Thread类的,后来知道java多线程有三种方式,今天首先比较一下常用的继承Thread类和实现Runnable接口的区别. 按着Ctrl键进入Thread之后,发现Thread类也 ...
- java中继承thread类的其他类的start()方法与run()方法
java中继承thread或者实现runnable接口的类必须重写run()方法. 如果其执行了start()方法,其实就是启动了线程的run()方法. 注意:如果是实现runnable接口的类是没有 ...
- Java中继承,类的高级概念的知识点
1. 继承含义 在面向对象编程中,可以通过扩展一个已有的类,并继承该类的属性和行为,来创建一个新的类,这种方式称为继承(inheritance). 2. 继承的优点 A.代码的可重用性 B.子类可以扩 ...
- 使用myeclipse开发java,解决java中继承JFrame类出现The type JFrame is not accessible due to restriction的问题
在java中创建窗体,导入了java中的JFrame类,之后会出现错误: Access restriction: The type QName is not accessible due to res ...
- struts 中继承ActionSupport类
理论上Struts 2.0的Action无须实现任何接口或继承任何类型,但是,我们为了方便实现Action,大多数情况下都会继承 com.opensymphony.xwork2.ActionSuppo ...
- JDBC中重要的类/接口-Connection、DriverManager、ResultSet、Statement及常用方法
DriverManager(管理一组 JDBC 驱动程序的基本服务) 它的方法: getConnection(String url, String user, String password) 试图建 ...
随机推荐
- (poj)1679 The Unique MST 求最小生成树是否唯一 (求次小生成树与最小生成树是否一样)
Description Given a connected undirected graph, tell if its minimum spanning tree is unique. Definit ...
- php PHP_EOL 常量
换行符 unix系列用 \n windows系列用 \r\n mac用 \r PHP中可以用PHP_EOL来替代,以提高代码的源代码级可移植性 <?php echo PHP_EOL; //win ...
- 使用原生js与jQuery分别实现一个简单的tab页签
tab页签通常适用于空间有限而内容较多同时兼顾页面美观度不给用户一种信息过量视觉疲劳的情形.使用面非常广,下面我们用两种方法简单实现之. 首先,构建页面元素.页签的可点击部分我们通常用列表来承载,包括 ...
- js传带参数的函数
字符串: setTimeout('pageScroll(4)',100);
- 批量执行sql语句
基本使用 $sqls="sql语句1;sql语句2;sql语句n"; 或 $sqls="insert into xx;"; $sqls.="inse ...
- mongodb数据库操作--备份 还原 导出 导入
首先数据库备份: mongodump -h IP --port 端口 -u 用户名 -p 密码 -d 数据库 -o 文件存在路径 mongodump -h 127.0.0.1 -u admin -p ...
- 【高德地图API】如何解决坐标转换,坐标偏移?
http://bbs.amap.com/thread-18617-1-1.html#rd?sukey=cbbc36a2500a2e6c2b0b19115118ace519002ff3a52731f13 ...
- 网络流系列算法总结(bzoj 3438 1061)
网络流嘛,怎么看都是一堆逗逼题嘛,反正遇到还是都做不起嘛.... 网络流的模板非常简单,难点都在于建图,网络流的建图解决问题范围之广,下至A+B Problem,上至单纯形,线性规划.所以如果对于网络 ...
- MATLAB conv2卷积的实现
MATLAB conv2卷积的实现 二维卷积的算法原理比较简单,参考任意一本数字信号处理的书籍,而matlab的conv2函数的滤波有个形状参数,用下面的一张图很能说明问题: 这里给出一种最原始的实现 ...
- 重燃你的PHP安全分析之火
关于脚本安全这个话题好像永远没完没了,如果你经常到国外的各种各样的bugtraq上,你会发现有一半以上都和脚本相关,诸如SQL injection,XSS,Path Disclosure,Remote ...