先日、「PHP難しすぎ!」という記事を書きました。PHPの難しさはマニュアルを読み解くことの難しさにほかなりません。また、仕様が不明瞭な部分が多く、振る舞いを正確に理解するには個別に実験する以外にないのです。今回も、そんなPHPの仕様が不明瞭なところを少し実験してみたいと思います。
まずは、次のようなクラスを作ってみます。
0 1 2 3 4 5 6 7 8 9 10 11 12 |
class A { function __construct() { echo "A::__construct\n"; } function __destruct() { echo "A::__destruct\n"; } }; |
このクラスは、コンストラクタとデストラクタが呼び出されるタイミングを知るためのものです。
クラスAをnew演算子で生成する式が部分式の場合、生成されたクラスAのオブジェクトの生存期間は果たしてどうなるのでしょうか? C++だと、newで生成したオブジェクトは明示的にdeleteしなければ生存期間が継続しますし、一時オブジェクトの場合は完結式の最後までが生存期間になると決まっています。
ちょっと複雑ですが、試しに次のような意地悪なコードを書いてみました。
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
<?php class A { function __construct() { echo "A::__construct\n"; } function __destruct() { echo "A::__destruct\n"; } function f() { echo "f\n"; } }; function test($a) { echo "test\n"; return true; } function main() { echo "→ main\n"; for (test(new A()) && test(1), printf("expr1\n"); printf("expr2\n"), false; ); echo "main →\n"; } main(); |
これを実行すると次のような結果になります。
0 1 2 3 4 5 6 7 8 9 |
→ main A::__construct test A::__destruct test expr1 expr2 main → |
ここから読み取れるのは、クラスAのオブジェクトは完結式の最後までではなく、どうやら副作用完了点(PHPでそのような言い方をするのかどうか知りませんが)までではないかと予想できます。すなわち、&&の直前までということです。
この推測が正しいかどうかを確かめるために、
0 1 2 |
for (test(new A()) + test(1), printf("expr1\n"); printf("expr2\n"), false; ); |
のように修正して試してみました。
するとどうでしょう! 結果は先ほどと同じになってしまいました。
とすると、クラスAのオブジェクトの生存期間は副作用完了点までではなく、関数から戻った時点でおそらく終わっているように見受けられます。今度は次のようなコードを試してみました。
0 1 2 3 4 5 6 7 |
function main() { echo "→ main\n"; test(new A()); echo "main →\n"; } |
結果は以下のようになりました。
0 1 2 3 4 5 6 |
→ main A::__construct test A::__destruct main → |
予想通り、クラスAのオブジェクトの生存期間は関数から戻るまでのようです。
さらにもうひとつ実験を行ってみました。
0 1 2 3 4 5 6 7 |
function main() { echo "→ main\n"; new A(); echo "main →\n"; } |
この結果は以下の通りです。
0 1 2 3 4 5 |
→ main A::__construct A::__destruct main → |
今度はスコープから抜ける前にオブジェクトが解体されてしまっています。
一連の振る舞いは、PHPが参照カウンタ方式のガベージコレクションを用いていることで説明がつくようです。
http://php.net/manual/ja/features.gc.refcounting-basics.php
よく考えれば当たり前のことですが、参照カウンタがゼロになったとしても、正確にどこでオブジェクトが解体されるのかを把握する上で実験は有意義でした。
上記ページでは、refcountがゼロに達すると変数コンテナ(ここではクラスAのオブジェクト)が破棄されるとあります。おそらく、何らかの変数に代入することでrefcountがインクリメントされるのでしょう。
今回のケースではクラスAのオブジェクトをnewで構築したあと、変数には代入していません。なのでrefcountがゼロのままであり、ただちに解体されたのだと解釈できます。
さて、ここでひとつ問題が生じてきます。refcountがゼロになった時点でただちにオブジェクトが解体されるのであれば、何らかの変数に代入する際には、なぜ代入前に(この時点ではrefcountはゼロのはず)解体されてしまわないのでしょうか?
もし仮に、変数代入前はrefcountの値が確定していないのだとしたら、変数に代入しない場合に解体されてしまうことの説明がつきません。このあたりはどうしてもマニュアルからは読み取れませんでした。
やはりPHPはかなり高難度のプログラミング言語だと思います。こんな難しい言語を操るPHPプログラマーには敬意を払わずにはいられません。