がるの健忘録

エンジニアでゲーマーで講師で占い師なおいちゃんのブログです。

copyが深かったり浅かったり

御題的には、deep copy(ディープコピー)とshallow copy(シャローコピー)の話です。


んと。例えば文字列Aの内容を文字列Bにcopyする時は。大抵の言語において

  b = a;

で片がつくと思います。
お次。中にintegerとかしかないようなクラスの場合。つまり

class foo {

private int i;
}

なんてものの場合。iをcopyすれば事足ります。


さて。ここからが本題。
「クラス(に限らんのですが)の参照(ポインタ)を含むクラスはどうやってcopyされるべきなのでしょうか?」
とか書いてもイメージがつかみにくいと思われるので、ちと詳細を。
まず。今回のメインターゲットなクラスを以下のとおり定義します*1

class bar {

private int ij;
private foo * fobj;
}

ちょいとC++チックな書き方くさいですが。javaな方などは、「private foo fobj」などと適宜読み替えてくださいませ。
んで。
とりあえずbarと、barに突っ込むべくfooとを生成してみませう。

foo f = new foo;
f.set_i(10);
bar b = new bar;
b.set_ij(15);
b.set_fobj(f);

さて。この時点でどのようなデータが入ってるんでしょうか。概ねこんな感じになると思いますっていうかなると思ってください。
fの中のi:整数型として10
bの中のij:整数型として15
bの中のfobj:fの参照先として0xf00
ポイントはfobjです。ここに入ってるデータが「参照(ポインタ値)」であることを強く意識しておいてください。


barのcopyには、二種類の「異なる考え方」が存在します。大雑把にコードを示してみましょう。
いち。

bar c = new bar;
c.set_ij(b.get_ij());
c.set_fobj(b.get_fobj());

に。

bar c = new bar;
c.set_ij(b.get_ij());
foo g = new foo;
g.set_i(b.get_fobj().get_i());
c.set_fobj(g);

さて。違いがわかりますでしょうか?
いちのほうは「参照だろうがなんだろうがcopy」という割合に単純なロジックです。
一方でにのほうは。「参照だったら、そのインスタンスを別途作って同じ内容をcopyしてからそのインスタンスの参照をぶち込む」という、少々厄介なロジックです。


前者を浅いコピー(shallow copy/シャローコピー)、後者を深いコピー(deep copy/ディープコピー)と呼称します。
どっちがいい、というほど単純なものでもないのですが。ちゃんと意識しておかないと、割合に見つけるのが厄介なバグを生み出します(知ってる限りでは「deepのイメージでshallow」のほうが多いですねぇ)。
具体的には。例えば「値を変えたつもりがないのに勝手に変わってた」なんてのが顕著な例ですねぇ。
上述でいいますと。cをcopyしたのはよいのですが。その後で「bインスタンスのfobjの中身を操作しちゃった」せいで「cインスタンス内のfobjにも影響が出た」とか。
こゆときは「foo側にデバッグprintだのブレークポイントだのを仕込む」と見つけやすいのですが。


ふと調べ物をしてて。この辺の話を書いてあるPageが少ないので書いてみたのですが…うまく書けてない orz
多分そのうち推敲します & 突っ込みよろしくです。

*1:一応念のため。面倒だから定義してないけどsetter/getterは定義してあると思ってくださいませ。心眼で見ればきっと見えますw