PHPにfinallyはないけどデストラクタがあるよ

Posted by Hiraku on 2011-11-11

PHP finally検索すると、「PHPにはfinallyがない。欠陥言語だ!」という主張の記事がたくさんヒットします。これに対してPHPを擁護している記事があまりなさそうなので、PHPを擁護してみることにします。

finallyとは何か

まずはおさらい。

try〜catch〜finallyは例外処理を行うための構文です。tryで例外が発生するかもしれない処理を書き、catchで例外が発生したときの対処を書き、finallyでは例外が起きても起きなくても実行される処理を書きます。…というのが初歩的な説明ですが、このfinallyの解説は十分ではありません。

finally節は、途中でどんなことが起ころうとも必ず実行されるというかなり特殊な性質を持っています。たとえば以下のJavaScriptのコードはいずれも「try」「finally」と表示します。tryの中でgoto相当の構文を実行しているにもかかわらずfinallyが実行されていることがわかります。

(function(){
  try {
    alert("try");
    return;
  } finally {
    alert("finally");
  }
})();
block: {
  try {
    alert("try");
    break block;
  } finally {
    alert("finally");
  }
}
try {
  try {
    alert("try");
    throw 1;
  } finally {
    alert("finally");
  }
} catch(e) {}

この不思議な挙動は、対になった処理を記述する際に威力を発揮します。fopen()したら必ずfclose()するなどのように、終わらせる方の処理を忘れてしまうとまずい場合、finallyに書くことで終了処理を確実に実行させることができます。

try {
  var f = open("hogehoge");
} finally {
  close(f);
}

finally節の最大の効能は、この「対になった処理の書きやすさ」「リソース開放の保証」だと思われます。

デストラクタとRAII

PHPにはfinallyがありませんが、デストラクタがあります。対になった処理はクラス化して、それぞれコンストラクタとデストラクタに記述することで確実に実行させることができます。これはRAIIと呼ばれるプログラミングスタイルですね。

class FileReader {
  public function __construct($filename) {
    $this->_fp = fopen($filename, 'r');
  }

  //(ファイルを操作するメソッドいろいろ)

  public function __destruct() {
    fclose($this->_fp);
  }
}

例外機構があるのにfinallyが存在しない言語というのは別にPHPに限った話ではなく、C++やPerlもその仲間です。(Perlはモジュール使えばいいじゃんとか細かいツッコミはとりあえず置いときます)

C++の中の人も「デストラクタがあるからfinallyはいらないよ」という趣旨のコメントを書いたりしています。
Why doesn't C++ provide a "finally" construct?

finallyの必要性

ではなぜfinallyを持つ言語とそうでない言語があるのかというと、主にGC(ガーベージコレクション)の挙動による差だと思われます。

PHPは比較的シンプルな参照カウンタを採用しています。このため、参照がなくなった時点で確実にデストラクタが実行されます。

call_user_func(function(){
  $obj = new Klass;

});
//関数スコープを抜けたのでローカル変数$objは消滅する
//$objの実体を参照するモノがなくなるのでデストラクタが起動する

この点、Perlも事情は同じのようです。

なぜPerlで例外処理のfinallyが特に必要と感じないかを考えてみた - サンプルコードによるPerl入門

これに対して、もっと複雑なGCを採用している言語の場合は、事情が違ってきます。私もあまり詳しくはないのですが、PythonやJavaやRuby、JavaScriptなどは参照が消滅したからといって、そこでいきなりオブジェクトが破棄されるのではなく、GCが起動したときにやっと破棄されるようです。

言い換えると、GCが起動するまでずーっとデストラクタが起動しない恐れがあるということです。

これではopen()⇒close()⇒open()⇒close()の順に呼ばなくてはならない命令は、コンストラクタやデストラクタに書くことができません。うっかりopen()⇒open()⇒…のような呼ばれ方をするかもしれません。finallyの機構は、デストラクタに代わって、終了処理を保証してくれる役割があるのです。

この点はどちらが優れているとかでなく、言語がどちらを選んだかというだけなので、それぞれの言語らしい書き方をすればいいのではないかと。

とりあえず、まとめ

「PHPにはfinallyがない。欠陥言語だ!」という主張は、「Javaにはデストラクタがない。欠陥言語だ!」と同レベルの主張なので、PHP大好きなぺちぱーは気にしなくていいと思うよ。

ただまあ、言語としての必要性という点ではデストラクタがあればいいわけですが、書き方から論じるなら考える余地はあるかもですね。デストラクタはクラスで書かないといけなくなるので、任意の位置に書き込めるfinally機構の方が自由度が高いのかも?しれません。細かいことを言うと「returnの上書き」といった副作用もあるようです。

PHP本家でも要望は上がっているようで、やはり「デストラクタがあるからええやん」派と、「いやいやこういう書き方したいよ」派がずーっと議論しています。個人的には、わりとどうでもいいと思います。別にええやん、PHPはPHPらしい書き方をすれば。


デストラクタは面白い

デストラクタが信頼を持って使える言語というのはLLなら珍しい方です。デストラクタは乱用すると結構面白いことに使えたりします。何しろreturnしようがbreakしようがexitしようが確実に実行されるコードが書けるのですから。

シングルトンのデストラクタ

例えばこれ。シングルトンによるスクリプト実行時間測定クラスです。

<?php
Stopwatch::start();

class Stopwatch {
  private $_start;
  private static $_instance;

  private function __construct(){
    $this->_start = microtime(true);
  }

  public static function start(){
    if (isset(self::$_instance)) {
      return self::$_instance;
    } else {
      return self::$_instance = new self();
    }
  }

  public function __destruct(){
    echo '実行に';
    echo microtime(true) - $this->_start;
    echo '秒かかりました',PHP_EOL;
  }
}

シングルトンはオブジェクトの参照をクラス変数で持ってしまうため、参照が消滅することがありません。デストラクタが呼ばれるのは、確実に「スクリプトが終了するとき」だけになります。そして、途中でexit()などで突然終了したとしても呼ばれるため、このコードをスクリプトの先頭に置いておけば、どんなスクリプトでも確実に実行時間を表示できます。

うーん。他にも何かありそうですね。デストラクタ駆動でプログラミング。



keyword: PHP

PHPの最新記事

×

この広告は180日以上新しい記事の投稿がないブログに表示されております。