by ryou

インスタンスのシャローコピー、ディープコピー

PHPを今まで泥縄式にしか勉強していなかったため、体系的に勉強するために「独習PHP」を読んでいるのですが、インスタンスの複製関係で覚えておかないと厄介なバグを引き起こしそうな点があったのでメモとして残しておきます。

概要

インスタンスを保持しているインスタンスをcloneで複製したい場合、そのクラスに__cloneを実装しておかないと保持しているインスタンスは参照渡しをされてしまう。

このように、インスタンスを複製する際に保持しているインスタンスに関しては参照渡しをすることを「シャローコピー」、保持しているインスタンスに関しても値渡しをすることを「ディープコピー」というらしい。

※配列がインスタンスを保持している場合も同様にシャローコピーのため、この場合に関してもディープコピーの必要がある場合は、明示的にforeachで回してcloneする等する必要がある。

詳細

前提知識

インスタンスは、「===」で比較された場合、同じインスタンスを参照している時のみtrueを返す。

インスタンスの代入は参照渡し

$takashi = new Person('takashi');
$masashi = $takashi;
var_dump($masashi === $takashi); # bool(true)

値渡しをしたい場合はcloneを使用する

$takashi = new Person('takashi');
$masashi = clone $takashi; # cloneで値渡し
var_dump($masashi === $takashi); # bool(false)

インスタンスがインスタンスを保持している場合、cloneを使用してもそれは参照渡し

<?php

class Person {
    public $name;

    function __construct($name)
    {
        $this->name = $name;
    }
}

class PersonList {
    public $list = array();
}

$list = new PersonList();

$list->list[] = new Person('takashi');
$list->list[] = new Person('yasue');

$tmp = clone $list;

var_dump($tmp->list[0] === $list->list[0]); # bool(true)

インスタンスが保持するインスタンスも値渡しをしたい場合は、そのクラスの__clone関数で明示的に値渡しをする必要がある

<?php

class Person {
    public $name;

    function __construct($name)
    {
        $this->name = $name;
    }
}

class PersonList {
    public $list = array();

    // この関数を追加
    function __clone()
    {
        // foreachはデフォルトで値渡しのため
        // $personに&をつけて参照渡しする必要がある
        foreach ($this->list as &$person) {
            $person = clone $person;
        }
    }
}

$list = new PersonList();

$list->list[] = new Person('takashi');
$list->list[] = new Person('yasue');

$tmp = clone $list;

var_dump($tmp->list[0] === $list->list[0]);