PHPにおけるインスタンスの永続化と参照渡し、コピーPHPオブジェクト指向プログラミング入門(終)(3/3 ページ)

» 2015年12月24日 05時00分 公開
[古庄道明株式会社格子組]
前のページへ 1|2|3       

「浅い」コピーと「深い」コピー

 ここまでインスタンスのコピーを見てきましたが、実はまだ、問題が隠れています。

 まずは、問題のあるコードから見ていきましょう。

<?php
//
class hoge {
  public $i_;
  public $obj_;
}
class foo {
  public $j_;
}
//
$obj = new hoge();
$obj->i_ = 1;
$obj->obj_ = new foo();
$obj->obj_->j_ = 20;
$obj2 = clone $obj; // 代入ではなくcloneを使う
var_dump($obj);
var_dump($obj2);
//
$obj2->i_ = 999; // 片方だけ値を変更
$obj2->obj_->j_ = 999;
var_dump($obj);
var_dump($obj2);
リスト9
object(hoge)#1 (2) {
  ["i_"]=>
  int(1)
  ["obj_"]=>
  object(foo)#2 (1) {
    ["j_"]=>
    int(20)
  }
}
object(hoge)#3 (2) {
  ["i_"]=>
  int(1)
  ["obj_"]=>
  object(foo)#2 (1) {
    ["j_"]=>
    int(20)
  }
}
object(hoge)#1 (2) {
  ["i_"]=>
  int(1)
  ["obj_"]=>
  object(foo)#2 (1) {
    ["j_"]=>
    int(999)
  }
}
object(hoge)#3 (2) {
  ["i_"]=>
  int(999)
  ["obj_"]=>
  object(foo)#2 (1) {
    ["j_"]=>
    int(999)
  }
}
結果9

 $objには、#1、$obj2には#3のインスタンスが入っているので、プロパティ$i_の値は適切に分離ができています。しかし、プロパティ$obj_の中にあるインスタンスは、#1の中も#3の中も、どちらも#2のインスタンスが入ってしまっています(結果9の25、34行目が同じ)。「$obj2->obj_->j_ = 999;」(リスト9の20行目)で片方のインスタンスだけ値を変更したいのに、#1の中の$obj_も、引きずられるように値が変更されてしまっているのです。

 言い方を変えると、#1は適切にコピー(clone)されていますが、#1の中にあるインスタンスは「コピー(clone)されていない、中途半端な状態になっている」といえます。

 こういった状態を「浅いコピー(シャローコピー:Shallow Copy)」と言って、オブジェクト指向プログラミングでは、言語を問わず出て来る概念です。もちろん「意図してこの状態にしている」のであればいいのですが、大抵の場合はそうではないので、より適切に「深いコピー(ディープコピー:Deep Copy)」を行う必要があることが多いといわれています。

マジックメソッド「__clone」で「深いコピー」

 PHPでは、こんなときにもマジックメソッドを用います。「__clone」というマジックメソッドは「cloneされた直後」に暗黙的に呼び出されるので、この__cloneを使って「深いコピー」を実装します。

 では、実装例を見ていきましょう。

<?php
//
class hoge {
//
  public function __clone() {
    echo "call __clone()\n";
    $this->obj_ = clone $this->obj_; // プロパティのうち「インスタンス型のもの」は全てcloneしておく
  }
//
public $i_;
  public $obj_;
}
class foo {
  public $j_;
}
//
$obj = new hoge();
$obj->i_ = 1;
$obj->obj_ = new foo();
$obj->obj_->j_ = 20;
$obj2 = clone $obj; // 代入ではなくcloneを使う
var_dump($obj);
var_dump($obj2);
//
$obj2->i_ = 999; // 片方だけ値を変更
$obj2->obj_->j_ = 999;
var_dump($obj);
var_dump($obj2);
リスト10
call __clone()
object(hoge)#1 (2) {
  ["i_"]=>
  int(1)
  ["obj_"]=>
  object(foo)#2 (1) {
    ["j_"]=>
    int(20)
  }
}
object(hoge)#3 (2) {
  ["i_"]=>
  int(1)
  ["obj_"]=>
  object(foo)#4 (1) {
    ["j_"]=>
    int(20)
  }
}
object(hoge)#1 (2) {
  ["i_"]=>
  int(1)
  ["obj_"]=>
  object(foo)#2 (1) {
    ["j_"]=>
    int(20)
  }
}
object(hoge)#3 (2) {
  ["i_"]=>
  int(999)
  ["obj_"]=>
  object(foo)#4 (1) {
    ["j_"]=>
    int(999)
  }
}結果10

 __cloneが呼び出されていること。その中でcloneした結果として、#1の中のobj_と、#3の中のobj_の中にあるインスタンスが、それぞれ「別のもの(#2と#4)」になっていること(結果10の26、35行目が異なる)。その結果として「$obj2->obj_->j_ = 999;」(リスト10の26行目)の操作が、$objに影響していないことが分かると思います。

 「インスタンスをコピーしたい」というケースはあまりありません。しかし「必要になった」ときに、こういった知識の有無が業務の速度に大きく影響することがあるので、「浅いコピー」「深いコピー」という考え方を、しっかりと理解しておくとよいでしょう。

本連載のまとめ

 全6回にわたって、「PHPにおけるオブジェクト指向」の「比較的よくある」話をいくつかしました。その中でも「他言語でも一般的に通用する内容」「PHPに比較的固有なお話」がありました。この2つは、どちらも大切でバランス良く学習していくといいでしょう。特に、プログラミング言語固有ではない「プログラミングの知識」は中級プログラマーの頃に意識する必要があると思います。

 またどこかでチャンスがありましたら、PHPの「少しだけ濃い、深い辺り」に踏み込んだ記事を皆さまにお届けできればと思っております。それでは、全6回をご愛読いただきありがとうございました。本稿がプログラマーの皆さまの参考になれば、望外の幸いです。

筆者紹介

古庄道明

1970年浅草生まれ。1995年に富士通系のソフトハウスに就職しプログラマーに転身。1999年個人事業主として独立し現在に至る。「寺子屋」「格子組」といったエンジニア支援活動を独自に展開し、占い師時代の「ガルーダ」という占い師名にちなんだ「がる先生」の愛称で親しまれている。

コンサルティングからシステム設計、ネットワークにセキュリティと、守備範囲は比較的多岐にわたる。「技術の基本は、その技術がないときの“困ってる”が根っこ」をモットーに、古い話から現代へ歴史をたどるように教えるのが持ち味。


前のページへ 1|2|3       

Copyright © ITmedia, Inc. All Rights Reserved.

RSSについて

アイティメディアIDについて

メールマガジン登録

@ITのメールマガジンは、 もちろん、すべて無料です。ぜひメールマガジンをご購読ください。