オブジェクトの参照渡しと値渡しについて
唐突ですが、今日は昨日 id:kensuu に聞かれてちゃんとこたえられなくて id:Yudoufu に聞いてちゃんと理解したことをまとめようと思います(謎
というわけで、オブジェクトの参照渡しと値渡しについてです。(環境:PHP5.2.6)
関数に値を引き渡す(通常の変数)
まず、オブジェクトではなく通常の値を渡す場合を考えます。
以下のコードを見てください。
<?php // 参照渡し function test_r (&$a) { $a = 2; } // 値渡し function test_n ($a) { $a = 3; } $b = 1; echo $b; echo "\n"; test_r($b); echo $b; echo "\n"; test_n($b); echo $b; echo "\n";
この結果は、
1 2 2
となります。
test_r() は、値を参照で渡しているため、関数内で変更された値は、 $b にも影響しますが、 test_n() は値渡しでコピーされるため、関数内で変更された値は $b には反映されません。
関数に値を引き渡す場合(オブジェクト)
次に、オブジェクトを関数に渡す場合を考えます。
まず、以下のコードを見てください。
<?php class Test { var $a = 1; var $b = 2; function write ($a, $b) { $this->a = $a; $this->b = $b; } function print_var () { echo "a :" . $this->a . " "; echo "b :" . $this->b . "\n"; } } function test_write (&$t) { $t->write(3, 4); $t->print_var(); } function test_write2 ($t) { $t->write(3, 4); $t->print_var(); }
Testという、2つのプロパティに値を書き込むメソッドと2つの値を出力するだけのメソッドを持つクラスを用意します。
そして、それに参照渡しをする関数 test_write() と、値渡しをする関数 test_write2() の2つを用意します。
そして、以下の2つが、テストの実行コードと結果です。関数に渡す前、関数内、関数に渡した後の3箇所で、出力のメソッドを呼び出しています。
test_write() のテスト
<?php $test = new Test(); $test->print_var(); test_write($test); $test->print_var();
実行結果
a :1 b :2 a :3 b :4 a :3 b :4
関数内でwriteメソッドを使って書き換えられた値は、関数の外のオブジェクト $test にも反映されています。
次に test_write2() です。
<?php $test = new Test(); $test->print_var(); test_write2($test); $test->print_var();
実行結果
a :1 b :2 a :3 b :4 a :3 b :4
ここが問題です。
参照渡し(&)で渡したわけではないのに、関数の外の $test のプロパティまで更新されています。
PHP5では、オブジェクトを渡すときは参照になる・・?
どこかでそんなことを聞いたことがあります。(そんな曖昧なことだからいざというとき困るのです
ということで、確かめるべく test_write() と test_write2() を以下のように書き換えます。具体的には、最後に $t = null; として、$tを亡き者にしようとしているだけです。
<?php function test_write (&$t) { $t->write(3, 4); $t->print_var(); $t = null; } function test_write2 ($t) { $t->write(3, 4); $t->print_var(); $t = null; }
参照で渡されているなら、関数実行後、$t が null になっているはずなので、print_varメソッドを呼べなくなるはずです。
test_write()
<?php $test = new Test(); $test->print_var(); test_write($test); $test->print_var();
実行結果
a :1 b :2 a :3 b :4 PHP Fatal error: Call to a member function print_var() on a non-object
予想通り、関数の中でnullを代入されてしまったので、関数の外の $test にも影響をあたえ、オブジェクトが使えなくなっています。
で、問題が次。
test_write2()
<?php $test = new Test(); $test->print_var(); test_write2($test); $test->print_var();
実行結果
a :1 b :2 a :3 b :4 a :3 b :4
!!!!!
おい!!
と、なったわけです。
同じ場所を参照していて、その実体を null で置き換えたなら、3つ目の print_varメソッドは呼べないはずですが、正しく 3 4 の値を出力してくれるのです。
なんでなんで?
ということで、
カラクリを図にした *1
もう、図の通りなのですがw、要するに、値渡しをしたつもりでも、ポインタを格納する場所は複製されても、その内容は渡しす前のものを参照しているので、その実体は同じものを見ている。逆に参照渡しは、同じポインタの格納場所を参照しているので、nullされたら関数の外でも使えなくなってしまうということです。