【簡単解説】phpの関数引数の参照渡しと値渡し

2017年12月26日


phpの引数について興味深いコードがありましたので、今回はphpの参照渡しと値渡しについての解説です。


参照渡し


元々のコードは多少複雑だったので、わかりやすく書き直したものが以下のコードです。

function hogehoge( &$foo ) {
    $i = bar();
    $foo->set("bar", $i );
}


関数barの返り値を何かしらのクラスインスタンスの$fooにsetメソッドを使って格納する処理をまとめた関数です。 ですが、この関数の引数の前に、&が付いています。 phpをご存知の方なら「えっ何やってんだ??」と思うかも知れませんね。

この&が何かというと、変数を参照渡しにしてくれるものです。

$a = 10;
function add( $c ) {
     $c = 100;
}
add( $a );
echo $a; // 10

$a = 10;
function add( &$c ) {
     $c = 100;
}
add( $a );
echo $a; // 100


以上のようになります。&をつけると、$aのポインタが$cに渡されているので、$cを変更すると$aも変わりましたね。

今は、整数値を渡したので、今度はオブジェクトを渡してみましょう。

class stuck {
     public $i = 0;
}
function add( $c ) {
     $c->i = 20;
}

$ins = new stuck();
$ins2 = $ins;
add( $ins2 );
var_dump( $ins ); // object(stuck)#1 (1) { ["i"]=> int(20) }

今度は&がありません。ですが$iは増加しています。

…ここで「んっ?」となりますよね。

なぜ&がないのかというと、基本的に引数にはポインタが渡されているからです。しかし、整数値の例では&が無いと$aは変わらなかったですよね?
その理由は実は単純で、変数の値が変わった時に初めて、値がコピーされるからです。

つまり、こういうことです。
変数が大きい場合に、関数に変数を渡すたびに値のコピーが発生したら、処理速度は大幅に遅くなりますよね。しかし、値渡しにならないとしたら、最初の例も出力は100になります。つまり、10が保存されないという非常に面倒な状況になります。
もしこうなってしまうと、phpが言語として致命的な欠陥を抱えていることになってしまいますよね。

オブジェクトの例の場合で、仮に出力をobject(stuck)#1 (1) { [“i”]=> int(0) }にしたい場合はディープコピーをします。

$ins2 = clone $ins;


上記のような記述であればOKです。

もしLL言語に接していない方だったら、冒頭に挙げたコードのように、オブジェクトを引数に取るのに&をつけてしまうかもしれませんね。

phpと別言語との比較


「phpの関数引数の参照渡しと値渡し」と銘打って始まった記事ではありますが、別にpythonでもjavascriptでもLL言語は基本的に関数の引数は参照渡しです。
もっとも、当然ながらphpとは挙動が異なります。

例えば、配列の場合は以下のようになります。

javascript


var a = [ 10 ];
var add = function( c ) {
     c.push( 20 );
}
add( a );
console.log( a ); // [ 10, 20 ]


python


a = [ 10 ]
def add( c ):
     c.append( 20 )
add( a )
print( a ) // [ 10, 20 ]


ruby


a = [ 10 ]
def add( c )
     c.push( 20 )
end
add( a )
p a // [ 10, 20 ]


php


$a = [ 10 ];
function add( $c ) {
     $c[] = 20;
};
add( $a );
var_dump( $a ); // array(1) { [0]=> int(10) }


phpだけちょっと変わっていますね(笑)