在
Ruby中有
Object#freeze
,可以防止对对象的进一步修改:
class Kingdom attr_accessor :weather_conditions end arendelle = Kingdom.new arendelle.frozen? # => false arendelle.weather_conditions = 'in 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
但是,没有对象#解冻.有没有办法解冻冻结的王国?
解决方法
是和否没有任何使用标准API的直接方式.但是,有一些了解什么#freeze?是的,你可以解决它.注意:这里的一切都是MRI当前版本的实现细节,可能会有所变化.
CRuby中的对象存储在struct RVALUE
中.
方便的是,结构中的第一件事是VALUE标志.
所有对象#freeze都设置了一个标志,称为FL_FREEZE,实际上是equal to RUBY_FL_FREEZE
.RUBY_FL_FREEZE基本上是标志中的11th bit.
所有你要解冻的对象都要取消设置第11位.
为此,您可以使用Fiddle
,它是标准库的一部分,可让您修改C级语言:
require 'fiddle' class Object def unfreeze Fiddle::Pointer.new(object_id * 2)[1] &= ~(1 << 3) end end
非immediate value objects in Ruby存储在address =他们的object_id * 2上.请注意,重要的是进行区分,以便您知道这不会让您解冻Fixnums.
由于我们要改变第11位,所以我们必须使用第二个字节的第3位.因此,我们使用[1]访问第二个字节.
〜(1 <3)移动3个位置,然后使结果反转.这样,掩码中零位的唯一位将是第三个,所有其他将是一个. 最后,我们只用位和(& =)应用掩码.
foo = 'A frozen string'.freeze foo.frozen? # => true foo.unfreeze foo.frozen? # => false foo[/ (?=frozen)/] = 'n un' foo # => 'An unfrozen string'