2015年11月01日

コンストラクタと例外

エラー処理の話の続きです。

C++ での例外処理でよく話題になるのが「コンストラクタで例外を使ってよいか」という件。今ではガンガン使うようにしています。
コンストラクタは戻り値が得られないので、インスタンス化に失敗したかどうかを知るためには、成否を判断するためのフラグをメンバ変数として設けておいて実際に利用する前に判断させるというのがよく使われる方法です。以前は例外の使用は極力避けるようにしていたのでこの方法を採用していましたが、コンストラクタ内で利用する関数が例外を投げた場合を想定してきちんと捕捉して処理しなければならずコーディングが面倒になるし、単にフラグだけでは内部で何が起こったのかわからないので呼び出し側から見ても使いづらいところはあります ( メッセージ出力したり、エラー・コードを管理する手もありますがますますコーディングが大変になってきます) 。なので、今では逆に例外を多用するようにしています。

コンストラクタでの例外を禁止する理由は一般に「メモリ・リーク」の原因になるというものですが、内部で例外が発生したのならそれを捕捉して後処理をしてから再スローすれば済みます。あるサイトで見かけたのがこういうタイプのもの。

SomeClass* p = 0;
try
{
  :
  p = new SomeClass();
  :
}
cacth (...)
{
  (後処理)
}
delete p;

コンストラクタ内で例外が発生したら、p にアドレスが代入される前に catch 節に写ってしまうので、最後の delete p でもオブジェクトは解体されず、メモリ・リークになってしまうように見えます。しかし、ここはちゃんと対策されていて、コンストラクタで例外が投げられたら自動的に delete がよばれ、SomeClass 用に確保されたメモリはきちんと開放されます。但し、SomeClass のデストラクタは呼び出されないので、コンストラクタ内で new を使っている場合は例外を捕捉して後処理を行うことは必須となります。

クラス内で使うメンバのサイズが大きくなければ、new を使って動的にメモリ確保しない方がソースはすっきりしますが、そういうわけにもいかないですよね。