コンストラクタは例外を投げるべきか?

作成:2006年7月2日

吉田誠一のホームページ   >   ソフトウェア工学   >   技術コラム   >   オブジェクト指向

「プログラミングの禁じ手Web版 C++編」では、コンストラクタから例外を投げることは、プログラミングでやってはいけない「禁じ手」の1つとされています。

しかし、私はむしろ、コンストラクタは例外を投げるべきだと考えています。

クラスを設計する際には、オブジェクトが存在しうる最低限の条件を考えます。これは、クラスの役割や位置付けによって決まります。

コンストラクタでは、その最低限の条件を満たすまでに必要な処理を行います。その途中でエラーが起きれば、オブジェクトの生成は失敗です。この時、オブジェクトの生成が失敗したということを表すために、積極的に、コンストラクタは例外を投げるようにするべきだと思います。

例えば、「夫婦」というクラスを作るとしましょう。「夫婦」は、夫という「人」と、妻という「人」の、2人の関係を表すクラスです。

「夫婦」のクラス図とオブジェクト図

「夫婦」は、夫と妻の2人がいて、初めて成立します。つまり、2人の「人」オブジェクトが存在することが、「夫婦」オブジェクトが存在しうる最低限の条件です。

この「夫婦」クラスのオブジェクトを生成するコンストラクタは、引数に2人の「人」オブジェクトを渡すものとなるでしょう。

ここで、もしもコンストラクタの引数にNULLが渡されたら、どうでしょうか。2人が揃っていなければ、「夫婦」としての最低限の条件を満たしていません。そこで、「夫婦」オブジェクトの生成は失敗として、コンストラクタが例外を投げるようにするべきです。

この他にも、次のような場合は、「夫婦」オブジェクトの生成を失敗とすることが考えられます。

コンストラクタから例外を投げるのを禁止してしまうと、「中途半端なオブジェクト未満のもの」や「不当なオブジェクトもどき」が存在することになります。

プログラミングの禁じ手Web版 C++編の記事にあるように、メモリリークを防ぐために、実装の段階で「コンストラクタから例外を投げるのを禁止する」という制約を入れる開発の仕方も、アリかもしれません。

しかし、その場合は、オブジェクトを生成した後で、必ず、オブジェクトの妥当性をチェックする必要があります。また、「夫婦」クラスに「妥当な夫婦かどうかをチェックする」というメソッドを追加するのは、嫌な感じです。

もしくは、プログラミングの禁じ手Web版 C++編の記事にあるように、オブジェクトを生成した後で初期化関数を呼ぶ、という方法もアリかもしれません。

しかし、その場合は、「空のオブジェクト」の存在を認めなければなりません。とはいえ、夫も妻もNULLである、空の「夫婦」オブジェクトとは、一体何なのでしょうか。どのように扱えば良いのか、想像がつきません。

Java言語では、コンストラクタから例外を投げることは、一般的に行われています。

例えば、Java言語で「ファイル」を表すFileクラスは、必ずコンストラクタで引数を指定します。もし、引数にパス名の代わりにnullを渡すと、コンストラクタがNullPointerExceptionという例外を投げるように設計されています。その結果、名前のない空の「ファイル」オブジェクトは存在し得ないようになっています。

プログラミングの禁じ手Web版 C++編の記事では、コンストラクタでの例外を「禁じ手」とした理由として、デストラクタが呼ばれないため,資源の解放忘れやメモリリークを引き起こす、という点を挙げています。

ですが、デストラクタは本来、生成されたオブジェクトを解放するためのものです。オブジェクトを生成できなかったのに、デストラクタで解放しよう、というのは奇妙な話です。オブジェクトの生成に失敗したのなら、デストラクタに依存しないようにコンストラクタの中で後始末を済ませてから、例外を投げる、というのが正解でしょう。

Copyright(C) Seiichi Yoshida ( comet@aerith.net ). All rights reserved.