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

デストラクタが無い言語はオブジェクト指向言語を名乗るな

プログラミング Java C/C++

デストラクタが無いと、真のオブジェクト指向でコードを書くことはできない。
これが理解出来てない人が割と多いようなので、ここに記録しておくことにする。

前提知識

  1. デストラクタとは、オブジェクトを破棄する際に呼び出される関数
  2. Java のファイナライザはデストラクタとは全くの別物。C++ のデストラクタと違って呼び出される保証が無く、決して使ってはならない。

デストラクタが存在しない世界でリソース管理

ファイルになにか適当な文字列を書く処理を考えよう。Java では、下記のようになる。

public static void main(String[] args) throws UnsupportedEncodingException, IOException {
    FileOutputStream output = new FileOutputStream("/tmp/hello");
    try {
        output.write("Hello, World!\n".getBytes("UTF-8"));
    } finally {
        output.close();
    }
}


Java7 で導入された try-with-resources 文を使うと少しだけ綺麗に。

public static void main(String[] args) throws UnsupportedEncodingException, IOException {
    try (FileOutputStream output = new FileOutputStream("/tmp/hello")) {
        output.write("Hello, World!\n".getBytes("UTF-8"));
    }
}

上記2つのコード、下は単なるシンタックスシュガーであって、結局はプログラマが close 処理を行わなければならない。


デストラクタが存在する世界でリソース管理

デストラクタがある C++ で考える。下記コードは上記 Java と同等のことを行うコードであるが、よく見て欲しい。close していない。なぜなら、ofs オブジェクトが破棄される際に実行されるデストラクタ内でクローズ処理を行なってくれるため、呼び出し側は何もしなくて良いからだ。

#include <fstream>

int main(){
    std::ofstream ofs("/tmp/hello");
    ofs << "Hello, World!" << std::endl;
    return 0;
}

そう、デストラクタがあれば、後処理はライブラリ製作者のコードに任せられるのだ。Java のような醜い try finally を毎回書かなくてもよくなり、スムーズなコーディングが可能となる。


上記2つの例からわかること

デストラクタが無い世界では、下記のような事を考えなければならない。

このオブジェクトは後始末が必要なのだろうか?後処理はどうやってするのだろうか? close を呼べばよいのか?dispose?それとも disconnect?このオブジェクトは close で、このオブジェクトは dispose で、・・・。このオブジェクトは Closeable だが、ここで close して良いのだろうか?


これらの問題は、デストラクタが存在すれば解決できる。デストラクタが存在すれば、デストラクタが呼ばれるタイミングで実行されるからだ。つまり、デストラクタが存在しないということは、リソースの解放を行う責任が呼び出し側にあるということだ。 プログラマがリソースの解放しなくてはならないが、ファイナライザはデストラクタのようにオブジェクト破棄時に実行されることを保証していない。しかし必ず後処理を実行して欲しい場合がどうしてもある。そのために finally が存在するのだ。デストラクタが存在すれば finally 構文は不要だ。Java7 からは try-with-resource 文という C# の using 文とほぼ同等の構文ができたが、これもデストラクタが無いため用意された構文だ。


オブジェクト指向とデストラクタ

デストラクタが存在することで、リソースの解放処理も含めて1つのクラス内で完結できる。デストラクタが無い言語ではプログラマが解放しなければならない。つまり、デストラクタが無い言語は不完全なカプセル化しか行えない


Java はデータのカプセル化は出来ても、リソースのカプセル化には無力だ。


そして、リソースのカプセル化はデータのカプセル化より重要だ。なぜなら、データのカプセル化を破ってもコードは動くが、リソースのカプセル化を守らないとリソースリークが発生しやすくなり、プログラムが動かなくなる。そして、一般的にリソースリークは再現や原因究明が困難なバグである。リソースのカプセル化が行えない言語ではプログラマは不要な責任とバグに追われるだろう。


Java のファイナライザについて

Java はオブジェクトの管理を全てガベージコレクタに任せているため、使い終わったらすぐ破棄、というようなことができない。メモリが潤沢にあればガベージコレクタが発動しないこともあり得る。つまりプログラムの終了までずっとファイナライザが呼ばれない可能性があり、デストラクタの代わりとしては使用できない。


その他にも、循環参照があるとどのオブジェクトからデストラクトすればよいのかガベージコレクタは決定できない等の理由のため、現状の Java では決してデストラクタを実装することはできない。少なくともオブジェクト管理アルゴリズムに新たなテクニックを導入しないとまず無理だろう。ただしガベージコレクタとデストラクタは必ずしも排他的なものではなく、C++/CLI ではガベージコレクタとデストラクタが1つの言語内に共存しているという例もある。


まとめ

デストラクタが無い言語は不完全なカプセル化しか行えず、真のオブジェクト指向言語とは言えない。
擬似オブジェクト指向言語とでも呼ぶべきだ。