如何解冻Ruby中的对象?

ndn*_*kov 35 ruby

在Ruby中,有Object#freeze阻止进一步修改对象:

class Kingdom
  attr_accessor :weather_conditions
end

arendelle = Kingdom.new
arendelle.frozen? # => false
arendelle.weather_conditions = 'in deep, deep, deep, deep snow'
arendelle.freeze
arendelle.frozen? # => true
arendelle.weather_conditions = 'sun is shining'
  # !> RuntimeError: can't modify frozen Kingdom

script = 'Do you want to build a snowman?'.freeze
script[/snowman/] = 'castle of ice'
  # !> RuntimeError: can't modify frozen String
Run Code Online (Sandbox Code Playgroud)

但是,没有Object#unfreeze.有没有办法解冻冻结的王国?

ndn*_*kov 54

是的,不是.没有任何直接使用标准API的方法.但是,通过对某些操作有所了解#freeze?,您可以解决它.注意:这里的所有内容都是MRI当前版本的实现细节,可能会有所变化.


CRuby中的对象存储在结构中RVALUE.
方便的是,结构中的第一件事是VALUE flags;.
所有的Object#freeze都设置了一个标志,称为FL_FREEZE,实际上等于RUBY_FL_FREEZE.RUBY_FL_FREEZE将基本上是标志中的第11位.
解冻对象所需要做的就是取消第11位.

要做到这一点,你可以使用Fiddle,这是标准库的一部分,让你修改C级语言:

require 'fiddle'

class Object
  def unfreeze
    Fiddle::Pointer.new(object_id * 2)[1] &= ~(1 << 3)
  end
end
Run Code Online (Sandbox Code Playgroud)

Ruby中的非立即值对象存储在address = theobject_id * 2.请注意,区分这一点非常重要,因此您会意识到这不会让您解冻Fixnum.例如.

由于我们想要改变第11位,我们必须使用第二个字节的第3位.因此我们用[1].访问第二个字节.

~(1 << 3)移动1三个位置然后反转结果.这样,掩码中唯一为零的位将是第三位,而所有其他位将是1.

最后,我们只使用按位和(&=)应用蒙版.


foo = 'A frozen string'.freeze
foo.frozen? # => true
foo.unfreeze
foo.frozen? # => false
foo[/ (?=frozen)/] = 'n un'
foo # => 'An unfrozen string'
Run Code Online (Sandbox Code Playgroud)

  • 弄乱Ruby的内部是很危险的.你应该在答案中解决这个问题. (7认同)
  • @Stefan,我在开头段落中提到过.我认为这是不言而喻的. (7认同)

Ste*_*fan 24

不,根据以下文件Object#freeze:

无法解冻冻结的物体.

冻结状态存储在对象内.呼叫freeze设置冻结状态,从而防止进一步修改.这包括对对象的冻结状态的修改.

关于您的示例,您可以改为分配一个新字符串:

script = 'Do you want to build a snowman?'
script.freeze

script = script.dup if script.frozen?
script[/snowman/] = 'castle of ice'
script #=> "Do you want to build a castle of ice?"
Run Code Online (Sandbox Code Playgroud)

Ruby 2.3的介绍String#+@,所以你可以写+str而不是str.dup if str.frozen?

  • 我特别喜欢你提到的字符串#+ @部分,因为这是相当新的,它似乎是一个很好用的提到它.我记得我注意到nobu在那里做了一些改变,然后通过更改日志,我不知道+ str. (4认同)

小智 5

frozen_object = %w[hello world].freeze
frozen_object.concat(['and universe']) # FrozenError (can't modify frozen Array)
frozen_object.dup.concat(['and universe']) # ['hello', 'world', 'and universe']
Run Code Online (Sandbox Code Playgroud)

  • 这不会修改对象,而是修改其副本。 (2认同)