通过 __index 进行元方法查找?

我实现了自己的类系统,但是我在__tostring方面遇到了困难。 我怀疑其他元方法也可能发生类似的问题,但我还没有尝试过。

(简要介绍一下:每个类都有一个__classDict属性,用于存储所有方法。它被用作类实例的__index。同时,__classDict的__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),但这有点不优雅。

有没有人遇到过这种问题?你们的解决方案是什么?

原文链接 https://stackoverflow.com/questions/3276098

点赞
stackoverflow用户33252
stackoverflow用户33252

请在 Lua 用户维基上查看继承教程

2010-07-19 16:35:47
stackoverflow用户282536
stackoverflow用户282536

我怀疑所有元方法都会发生同样的事情;会使用相当于 rawget 的操作。

这是正确的。 根据 Lua 手册说明:

…应该被理解为 rawget(getmetatable(obj) or {}, event) 。也就是说,对元方法的访问不会调用其他元方法,对没有元表的对象的访问不会失败 (仅仅返回 nil)。

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

2010-07-21 14:16:32
stackoverflow用户312586
stackoverflow用户312586

感谢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

我希望这很明显。当设置一个新的元表时,它初始化了所有的元方法(不会替换现有的元方法)。这些元方法被准备好“传递”请求到“父元表”。

这是我能找到的最简单的解决方案。嗯,实际上我找到了一个使用更少字符且更快的解决方案,但它涉及黑魔法(它涉及在自己的主体内取消引用元表函数),并且比这个解决方案可读性差得多。

如果有人发现一个更简短、更简单的函数,做同样的事情,我会很乐意给他答案。

使用很简单:当你想要“向上”时,用setindirectmetatable替换setmetatable

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) -- 仅在代码中更改
print(x) -- 打印 "x"
print(mt2.__tostring(y)) -- 打印 "y"
print(y) -- 打印 "y"

一个小小的警告:setindirectmetatable在mt2上创建了元方法。改变这个行为,使它创建一个副本,mt2保持不变,应该是轻而易举的。但是默认让它们设置是更好的。

2010-07-26 17:38:59
stackoverflow用户2026122
stackoverflow用户2026122

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

2013-02-14 11:15:39