読者です 読者をやめる 読者になる 読者になる

C++とfinally(おまけ)

C++ Java C# C++/CLI D言語 Perl PHP Ruby Python JavaScript

こんにちは、株式会社CFlatです。

今回のタイトルはC++ですが、試しにC++以外の幾つかの言語におけるデストラクタやfinallyの相当機能について、少々見ていきたいと思います。

Java

finallyとは別に、クラスにfinalize()メソッドを実装することができます。
finalize()メソッドは、インスタンスが不要になった際に呼ばれるC++のデストラクタとは異なり、インスタンスがGCにより回収される際に呼ばれます。これにより、Javaではリソースリークを自動的に回避するコードを書くことができますが、そのタイミングをプログラマが自由に制御することはできません。finallyブロックは元々、この不自由さを回避するための苦肉の策だったものと思います。

C#

C#も、finallyをサポートしています。C#にもC++同様デストラクタはありますが、これはC++のデストラクタと異なり、Javaのfinalize()メソッドと同様GCがメモリを回収する際に呼ばれるものです。
C#ではさらに、Dispose()メソッドを利用できます。Dispose()は単体で見れば、単なるIDisposableインターフェースの一メソッドに過ぎませんが、usingブロックと組み合わせるとC++と同様、ブロックからどのように抜けても必ずDispose()が呼ばれるようにできます。
この仕様は、後述のC++/CLIの仕様との共通化のためのものです。この仕様は、C++のデストラクタの仕様をC#にも導入するためのものです
  ※ 2013/05/14:egtra様のご指摘を受けて、修正いたしました。

C++/CLI

C++とは異なり(C++/CLIC++の拡張ではなく、別途標準化された別言語です)、finallyをサポートしています。C++/CLIにはファイナライザ(クラス名の前に!をつけたメソッド)があり、これはJavaのfinalize()や、C#のデストラクタと同様の機能です。
そして、C#学習者にとってはややこしいことに、ファイナライザとは別にデストラクタが存在します!
C++/CLIのデストラクタは、C++のデストラクタと同様、不要になった際に自動的に呼び出されます。C++でデストラクタが呼び出されるのと同じタイミング、ということです。
ちなみに、ハンドルが明示的にdeleteされないまま参照されなくなった場合も、ファイナライザはデストラクタを呼ぶよう作る筈なので、問題ありません(とはいえ、ファイナライザ任せにせずともよいコードを書くべきです)。ファイナライザをユーザーが定義しなくても、デストラクタをユーザー定義してあればコンパイラにより適切なファイナライザが生成されます(C#ではDisposeするデストラクタをユーザー定義する必要アリ)。
なお、C++/CLIのデストラクタとC#のDispose()は、厳密にいうと動作が違います。C#のDispose()では基底クラスやフィールドにDispose()があればそれらも呼び出さねばなりませんが、C++/CLIのデストラクタはそのクラス内の処理だけ書いておけば十分です。関連インスタンスのデストラクタは、自動的に適切に呼ばれます(Dispose()では、それら全てひっくるめた処理を自前で実装するイメージ)。

D言語

finallyをサポートしています。デストラクタもサポートしていますが、C#と同様にGCが回収するタイミングで呼ばれるほか、インスタンスを明示的にdeleteすることでも呼び出されます。
さらにD言語では、scope文を用いることができます。これはtry文の「tryが長くなるとcatchやfinallyがどんどん離れていく」という弱点を克服するための機能で、いわば任意のタイミングでcatch(...)やfinalyに実行すべき処理を登録するようなものです。
前回の最後に提案した擬似finallyクラスは、まさにscope(exit)と同機能だと言えます。

Perl

デストラクタ(DESTORY)をサポートしています。PerlのGCは参照カウント方式なので、デストラクタはインスタンスが参照されなくなった際に呼び出されます。
例外処理はありません。ただし、eval()内でdie()が呼ばれた際、呼び出し元では特殊変数$@にdie()の引数が格納されて処理が続行されます。これを利用して、try〜catchに似た処理を行うことはできます。
これとブロックを組み合わせると、try〜catch〜finallyをライブラリとして実装できるという不思議仕様(c.f. http://perldoc.jp/docs/modules/Error-0.15/Error.pod)。

PHP

PHP5から、デストラクタ(__destruct)がサポートされました。try〜catchもPHP5からサポートされていますが、finallyのサポートはPHP5.5からになります。
PHPのGCも参照カウント方式なので、デストラクタはインスタンスが参照されなくなった際に呼び出されます。

Ruby

begin〜rescure〜ensure〜endブロックが、try〜catch〜finallyに相当します。ファイナライザをサポートしており、GCに回収されるタイミングで呼び出されます。
上記とは別にcatch〜endとthrowがありますが、こちらは例外処理とは全く関係のない構文で、多重ループを一気に抜けるような時に使われるものです(Javaのラベルつきbreak文に相当)。

Python

try〜except〜else〜finallyをサポートしています。またデストラクタ(__del__)をサポートしていますが、実行はGCに回収されるタイミングです。
さらにwithをサポートしており、withブロックの最初でインスタンスの__enter__メソッドを、抜ける際には__exit__メソッドを呼び出します。C#のusingと似ていますが、範囲をインスタンスの寿命と一致させる必要はありません。

JavaScript

finallyをサポートしています。デストラクタをサポートしていません。


あまり挙げてもキリがありませんので、こんなところで終わりにしておきます