たけ(tk)です。

[ruby-list:45431] cloneの挙動について にて 
Akira Hayakawa <ruby / i-mail.jp> さん 曰く:

> .cloneをしないと、私が"Brad Pitt"になってしまって、べ、べつに嬉しくなんかないんだからねっ!

ブラッドピットに変身できますけど・・。

a = Man.new(Name.new("Akira"))
p a.get_name
#p a.get_name.name = "Brad Pitt"
p a.get_name.name.replace "Brad Pitt"
p a.get_name    #=> #<Name:0x13220fc @name="Brad Pitt">

−−−−

どういう結果を望んでいるのかよくわからないので、一般論として述べておきま
す。

一言で言えば、あるオブジェクト(a)でcloneすると、レシーバのオブジェクト(a)
自体は別のオブジェクトに複製されて別のオブジェクトになる(b)が、その属性
(name_obj)は複製されない。元のオブジェクト(a)と新しいオブジェクト(b)とは
属性(name_obj)を共有する状態になる。

その場合でも、一方のオブジェクト(b)の属性代入(name_obj=)で属性のオブジェ
クトを入れ換えれば、共有状態が解消されるので問題を生じない。

しかし、属性の内部状態を(属性のオブジェクトの同一性を保ったままで)変更
すると、他の共有者の状態に影響する。

* 他の共有者(b)の属性(name_obj)の属性(name_str)に代入すれと、属性
(name_obj)の内部状態が変る。しかし属性(name_obj)のオブジェクトの同一性は
保たれるので、共有者(a、b)による属性(name_obj)の共有状態は解消されない。
よって、他の共有者(a)に影響することになる。

−−−−

class NameObj
  attr_accessor :name_str

  def initialize(name_str)
    @name_str = name_str
  end
end

class Man
  attr_accessor :name_obj

  def initialize(name_obj)
    @name_obj = name_obj
  end
end

# cloneしても属性のオブジェクト(name_obj)は共有している。
# 他の共有者(b)で属性のオブジェクト(b.name_obj)の内部状態を変更すれば、共有者(a)の状態も変る。

a = Man.new(NameObj.new("Akira"))
b = a.clone
b.name_obj.name_str = "Brad Pitt"
p a    #=> #<Man:0x1320e28 @name_obj=#<NameObj:0x1320e3c @name_str="Brad Pitt">>
p b    #=> #<Man:0x1320e14 @name_obj=#<NameObj:0x1320e3c @name_str="Brad Pitt">>
  #       ↑               ↑
  # Manのオブジェクトは異なるが、NameObjのオブジェクトは同じ。もちろんname_strのオブジェクトも同じ。
p a.name_obj.name_str.object_id  #=> 10028800
p b.name_obj.name_str.object_id  #=> 10028800

# 共有者(b)の属性代入(name_obj=)であれば、属性のオブジェクトが入れ代わるので、
# 他の共有者(a)の状態には影響が無い。

a = Man.new(NameObj.new("Akira"))
b = a.clone
b.name_obj = NameObj.new("Brad Pitt")
p a    #=> #<Man:0x13207ac @name_obj=#<NameObj:0x13207c0 @name_str="Akira">>
p b    #=> #<Man:0x1320798 @name_obj=#<NameObj:0x1320770 @name_str="Brad Pitt">>
  #       ↑               ↑
  # Manのオブジェクトは異なる(clone)、NameObjのオブジェクトも異なる(属性代入)。

−−−−

class NameObj
  attr_accessor :name_str

  def initialize(name_str)
    @name_str = name_str
  end
##    # NameObj#clone で name_str のクローンを作る
##  def clone
##    new_obj = super
##    new_obj.name_str = @name_str.clone
##    new_obj
##  end
end

class Man
##  attr_writer :name_obj
  attr_accessor :name_obj

  def initialize(name_obj)
    @name_obj = name_obj
  end
  
    # Man#clone で name_obj のクローンを作る
  def clone
    new_obj = super
    new_obj.name_obj = @name_obj.clone
    new_obj
  end
end

# Man#clone で name_obj のクローンを作れば
# name_obj のオブジェクトも変る。

a = Man.new(NameObj.new("Akira"))
b = a.clone
b.name_obj.name_str = "Brad Pitt"
p a    #=> #<Man:0x1320950 @name_obj=#<NameObj:0x1320964 @name_str="Akira">>
p b    #=> #<Man:0x1320928 @name_obj=#<NameObj:0x1320914 @name_str="Brad Pitt">>
  #       ↑               ↑
  # Manのオブジェクトは異なる、NameObjのオブジェクトも異なる。name_strのオブジェクトは??
  
# これ(↑)で、うまくいくようにみえるのだが・・・
# 実は、二つの NameObj(a.name_obj と b.name_obj)とは属性(name_str)のオ
ブジェクトを共有している。ので、
# name_str を破壊的に変更すると・・

a = Man.new(NameObj.new("Akira"))
b = a.clone
b.name_obj.name_str.replace "Brad Pitt"
p a    #=> #<Man:0x1320824 @name_obj=#<NameObj:0x1320838 @name_str="Brad Pitt">>
p b    #=> #<Man:0x13207fc @name_obj=#<NameObj:0x13207e8 @name_str="Brad Pitt">>
  #       ↑               ↑
  # Manのオブジェクトは異なり、NameObjのオブジェクトも異なるが。name_strのオブジェクトは同じ。
p a.name_obj.name_str.object_id  #=> 10028070
p b.name_obj.name_str.object_id  #=> 10028070

take_tk = kumagai hidetake