ブログからの転記です。
slack上でのcrystal-jaコミュニティで、いろんな議論(大体私からは不明点を聞くだけ)してるのですが、 ちょっとcyrstalで気になってたのが、シャローコピー、ディープコピーはどうするのか、というもの。
例えば、以下のコードはrubyだと
class ClassA
attr_accessor :a
end
clsa = ClassA.new
clsa.a = 1
clsb = clsa.clone
clsb.a = 2
p clsb.a #=> 1
p clsa.a #=> 2
となります。 では、crystalではどうなるかというと
(同上)
p clsb.a #=> 2
p clsa.a #=> 2
となってしまいます。(attr_accessorはpropertyで読み替え)
どうもcyrstalでのやり方では、
や
を見る限りだと、「子クラスで override して使う前提」みたいです。 (@pine613さんの見立てでは) なので、ディープコピーの各クラスの実装例だと、以下のような感じになります。(同@pine613さんのコード)
class ClassA
property :a
def self.new
i = self.new
yield i
i
end
def clone
ClassA.new { |i|
i.a = self.a
}
end
end
因みにですが、crystalではStructクラスも用意されています。
classクラスとの違いは
を見ると
- 構造体に対して new を実行するとヒープではなくスタック領域が割り当てられる
- クラスが参照渡しであるのに対して、構造体は値渡しである
- 構造体は暗黙的に Struct を継承し、Struct は Value を継承している。一方クラスは Reference を継承する
とあります。オブジェクト間で値をやり取りするようなことがあるなら、こっちを使うべき、という設計みたいですね。
@makenowjustさん曰く
「それと、 record
マクロを使って定義した構造体には clone
メソッドが定義されてます」
とのことで、実際に調べてみると以下のようになっています。
上記情報に従って書き直すと
struct ClassA
property :a
end
record ClassA
classA = ClassA.new
classA.a = 1
classB = classA.clone
classB.a = 2
p classA.a #=> 1
p classB.a #=> 2
となり期待する動作になりました。