在Ruby中保证比较对象身份的方法

17

在Ruby中,有没有一种可靠的方法来比较两个对象的标识(identity)?给定两个变量,如果这两个变量指向内存中完全相同的对象,则返回true。

对于大多数Ruby对象,equal?方法可以用来比较标识(identity):

f = g = Object.new
p f.equal? g  # => true

然而,这并不适用于所有对象。例如:

class F
  def ==(obj) false end
  def ===(obj) false end
  def eql?(obj) false end
  def equal?(obj) false end
  def object_id; self end
end

f = g = F.new
p f == g       # => false
p f === g      # => false
p f.eql? g     # => false
p f.equal? g   # => false
p f.object_id == g.object_id  # => false

有没有一种绝对可靠的方式,可以比较两个无法被伪造的对象?

这只是一个纯粹的思想问题。任何以“为什么”开头的问题的答案可能是“因为我好奇”。


计算机科学和数学中像 F 这样的东西叫什么?有一个词可以形容它。我想说“恶魔般的”,但不那么邪恶。 - David Grayson
1
我认为这个词是“错误的”。就像它不是你应该做的事情。 - Chuck
1
我想用一个以 "F" 开头的词... 除非你想编写一个 C 扩展,否则没有任何保证,你应该礼貌待人,但并没有阻止任何人成为反社会的病态人。 - mu is too short
你可以通过 object_id 不是整数这个事实来判断出有些不对劲。 - Andrew Grimm
1
@Andrew: def object_id; rand(10000) end. 当面对像 F 这样的病态情况时,你如何依赖于“查看对象是否等于自身”?真正的根本问题是我们不知道如何让 Ruby 判断两个对象是否相等,我们只能询问 这些对象 是否相等;如果我们不能信任对象的行为,那我们该怎么做呢? - mu is too short
显示剩余2条评论
3个回答

15
你可以获取一个未绑定版本的Object#object_id,将其绑定到相关对象上,然后查看它返回的信息。假设你有一个包含一个附加项的F类:
class F
  # ...
  def inspect; 'pancakes!' end # Just so we can tell what we have later.
end

然后:

>> f = F.new
>> f.object_id
=> pancakes!
>> unbound_object_id = Object.instance_method(:object_id)
>> unbound_object_id.bind(f).call
=> 2153000340
>> ObjectSpace._id2ref(2153000340).inspect
=> "pancakes!"

当然,如果有人打开Object并替换object_id,那么你就没什么好办法了,但是如果你能在任何其他内容加载之前获取unbound_object_id UnboundMethod,那么即使有人更改Object#object_id,你的unbound_object_id仍将是原始正确的id。因此,这种绕路方法为您提供了任何对象的可靠object_id(受上述警告的限制)。现在,您可以获取和比较对象id以获得可靠的比较。

太棒了,它可以工作!我只想贡献一个使用这种技术来比较两个变量身份的函数:def compare_by_identity(x, y); id = Object.instance_method :object_id; id.bind(x).call == id.bind(y).call; end - David Grayson

9

使用BasicObject#equal?方法。根据Ruby文档

与==不同,equal?方法不应被子类覆盖:它用于确定对象标识(即,a.equal?(b)当且仅当a和b是同一个对象时成立)。

需要更强的保证吗?据我所知,无法获得,BaseObject#object_idObjectSpace._id2refHash#compare_by_identity也可以被覆盖或者被猴子补丁修改(至少这是我的个人信念)。


1
如果你有足够的恶意态度,可以提供自己的“def equal?(other)false end”实现。 - mu is too short

3
< p> mu is too short 提供的答案非常好,但是你可以使用Hash类的特殊功能来完成另一种方法:

def compare_by_identity(x, y)
  h = {}.compare_by_identity
  h[x] = 1
  h[y] = 2
  h.keys.size == 1
end
< p > compare_by_identity功能已于Ruby 1.9.2中添加,因此此函数不适用于早期版本。我认为 mu is too short 的答案更好。 < /p >

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接