4

我已经实现了自己的班级系统,但遇到了麻烦__tostring;我怀疑其他元方法也会发生类似的问题,但我没有尝试过。

(简单绕道:每个类都有一个__classDict属性,包含所有方法。它被用作类实例' __index。同时,__classDict's__index是超类' __classDict,因此会自动查找超类中的方法。)

我想在所有情况下都有一个“默认的 tostring”行为。但它没有用:“tostring”行为没有通过子类正确“传播”。

我已经完成了这个测试来说明我的问题:

mt1 = {__tostring=function(x) return x.name or "no name" end }
mt2 = {}
setmetatable(mt2, {__index=mt1})
x = {name='x'}
y = {name='y'}
setmetatable(x, mt1)
setmetatable(y, mt2)
print(x) -- prints "x"
print(mt2.__tostring(y)) -- prints "y"
print(y) -- prints "table: 0x9e84c18" !!

我宁愿最后一行打印“y”。

Lua 的“to_String”行为必须使用等价于

rawget(instance.class.__classDict, '__tostring')

而不是做相当于

instance.class.__classDict.__tostring

我怀疑所有元方法都会发生同样的情况;rawget- 使用等效操作。

我想我可以做的一件事是在我进行子类化时复制所有元方法(上面示例中的等价物会做mt2.__tostring = mt1.__tostring),但这有点不雅。

有没有人为这种问题打过仗?你的解决方案在哪里?

4

4 回答 4

4

我怀疑所有元方法都会发生同样的情况;使用与 rawget 等效的操作。

那是对的。来自lua手册:

... 应该读作rawget(getmetatable(obj) or {}, event). 也就是说,对元方法的访问不会调用其他元方法,对没有元表的对象的访问也不会失败(它只会导致 nil)。

通常,每个类都有自己的元表,您将所有对函数的引用复制到其中。也就是说,做mt2.__tostring = mt1.__tosting

于 2010-07-21T14:16:32.207 回答
1

感谢 daurnimator 的评论,我想我找到了一种方法让元方法按照__index我的意愿“跟随”。它浓缩在这个功能上:

local metamethods = {
  '__add', '__sub', '__mul', '__div', '__mod', '__pow', '__unm', '__concat', 
  '__len', '__eq', '__lt', '__le', '__call', '__gc', '__tostring', '__newindex'
}

function setindirectmetatable(t, mt) 
  for _,m in ipairs(metamethods) do
    rawset(mt, m, rawget(mt,m) or function(...)
      local supermt = getmetatable(mt) or {}
      local index = supermt.__index
      if(type(index)=='function') then return index(t,m)(...) end
      if(type(index)=='table') then return index[m](...) end
      return nil
    end)
  end

  return setmetatable(t, mt)
end

我希望它足够简单。当一个新的元表被设置时,它会用所有的元方法初始化它(不替换现有的)。这些元方法准备将请求“传递”到“父元表”。

这是我能找到的最简单的解决方案。好吧,我实际上找到了一个使用更少字符且速度更快的解决方案,但它涉及黑魔法(它涉及在自己的体内取消引用自身的元表函数)并且它的可读性远低于这个。

如果有人找到一个更短、更简单的功能,我很乐意给他答案。

用法很简单:当你希望它“上升”时替换setmetatable为:setindirectmetatable

mt1 = {__tostring=function(x) return x.name or "no name" end }
mt2 = {}
setmetatable(mt2, {__index=mt1})
x = {name='x'}
y = {name='y'}
setmetatable(x, mt1)
setindirectmetatable(y, mt2) -- only change in code
print(x) -- prints "x"
print(mt2.__tostring(y)) -- prints "y"
print(y) -- prints "y"

一点警告:setindirectmetatable在 mt2 上创建元方法。更改该行为以便制作副本并且 mt2 保持不变,应该是微不足道的。但是让它们默认设置实际上对我来说更好。

于 2010-07-26T17:38:59.533 回答
0

根据我使用 Lua 5.1 的经验,使用 rawget() 在元表中查找元方法,这就是为什么您必须将对该函数的引用复制到您创建的每个类表中。

于 2013-02-14T11:15:39.180 回答
-1

请参阅Lua 用户 Wiki 上的继承教程。

于 2010-07-19T16:35:47.050 回答