プログラミング、リファクタリング、そしてすべてにおける究極の疑問: No. 8

同カテゴリーの次の記事

Unreal* Engine 4.19 の最適化にインテルのソフトウェア・エンジニアが協力

この記事は、インテル® デベロッパー・ゾーンに公開されている「The Ultimate Question of Programming, Refactoring, and Everything」の日本語参考訳です。


8. デストラクター内の例外は危険

この問題は、LibreOffice プロジェクトで見つかりました。このエラーは、次の PVS-Studio 診断によって検出されます。

V509 The ‘dynamic_cast‘ operator should be located inside the try..catch block, as it could potentially generate an exception. Raising exception inside the destructor is illegal. (V509 ‘dynamic_cast‘ は例外を生成する可能性があるため try..catch ブロック内に配置されるべきです。デストラクター内で例外を発生させることはできません。)

virtual ~LazyFieldmarkDeleter()
{
  dynamic_cast<Fieldmark&>
    (*m_pFieldmark.get()).ReleaseDoc(m_pDoc);
}

説明

プログラムで例外がスローされると、スタックのアンロールが開始され、オブジェクトはデストラクターの呼び出しによって破棄されます。スタックのアンロール中に破棄されるオブジェクトのデストラクターがそのデストラクターを離れる別の例外をスローすると、C++ ライブラリーは terminate() 関数を呼び出して直ちにプログラムを終了します。これは、デストラクターは例外を外に出してはならないという規則に基づきます。デストラクター内でスローされた例外は、そのデストラクター内で処理されなければなりません。

上記のコードは危険です。dynamic_cast (英語) 演算子は、オブジェクト参照を要求された型へキャストできない場合 std::bad_cast 例外を生成します。

同様に、例外をスローできるほかの構造も危険です。例えば、デストラクターで new 演算子を使用してメモリーを割り当てることは安全ではありません。失敗した場合、new 演算子は std::bad_alloc 例外をスローします。

正しいコード

上記のコードは、dynamic_cast を参照ではなく、ポインターとともに使用するように修正できます。そうすることで、オブジェクトの型を変換できない場合、例外を生成せずに nullptr を返します。

virtual ~LazyFieldmarkDeleter()
{
  auto p = dynamic_cast<Fieldmark*>m_pFieldmark.get();
  if (p)
    p->ReleaseDoc(m_pDoc);
}

推奨事項

デストラクターはできるだけ単純にします。デストラクターは、メモリー割り当てやファイル読み取りのためのものではありません。

場合によっては、デストラクターを単純にできないこともありますが、努力はすべきです。また、複雑なデストラクターは、一般にクラス設計の貧弱さを示しており、準備不十分なソリューションと言えます。

デストラクターのコードが増えると、すべての可能性のある問題に対応することが難しくなります。例外をスローできる領域とできない領域を区別するのが困難になります。

例外が発生する可能性がある場合、次のように catch(…) を使用して抑止すると良いでしょう。

virtual ~LazyFieldmarkDeleter()
{
  try 
  {
    dynamic_cast<Fieldmark&>
      (*m_pFieldmark.get()).ReleaseDoc(m_pDoc);
  }
  catch (...)
  {
    assert(false);
  }
}

これにより、デストラクター内のいくつかの問題を隠蔽できるだけでなく、多くの場合アプリケーションの動作が安定します。

私は、デストラクターで全く例外をスローしないように主張しているわけではありません。すべては特定の状況に依存します。状況によっては、デストラクターで例外を生成したほうが良いこともあります。実際、特別なクラスでそのようなケースを目にしたことがあります。しかし、それらはまれなケースです。私が目にした特別なクラスでは、オブジェクトを破棄するときに例外を生成するように設計されていましたが、”own string”、”dot”、”brush”、”triangle”、”document” などの一般的なクラスでは、デストラクターから例外をスローすべきではありません。

二重の例外はプログラムの終了を引き起こすため、よく考慮して決定を下す必要があります。

コンパイラーの最適化に関する詳細は、最適化に関する注意事項を参照してください。

関連記事

  • Parallel Universe マガジンParallel Universe マガジン Parallel Universe へようこそ。 米国インテル社が四半期に一度オンラインで公開しているオンラインマガジンです。インテルの技術者によるテクノロジーの解説や、最新ツールの紹介など、並列化に関する記事を毎号掲載しています。第1号からのバックナンバーを PDF 形式で用意しました、ぜひご覧ください。 12 […]
  • 比較関数の罠比較関数の罠 この記事は、インテル® デベロッパー・ゾーンに公開されている「The Evil within the Comparison Functions」の日本語参考訳です。 この記事の PDF […]
  • マルチスレッド開発ガイド: 4.6 インテル® Parallel Composer を利用して並列コードを開発するマルチスレッド開発ガイド: 4.6 インテル® Parallel Composer を利用して並列コードを開発する コードの並列化にはさまざまな手法があります。この記事では、インテル® Parallel Composer で利用可能な手法の概要を説明し、各手法の主な長所を比較します。インテル® Parallel Composer は Windows* 上の C/C++ を使用した開発のみを対象としていますが、これらの手法の多くは Fortran や […]
  • 並列プログラミングにおけるロックの効率的な使用並列プログラミングにおけるロックの効率的な使用 この記事は、インテル® ソフトウェア・ネットワークに掲載されている「Using Locks Effectively in Parallel Programming […]
  • インテル® AVX 命令を使用した complex float データ型の IIR フィルターの実装インテル® AVX 命令を使用した complex float データ型の IIR フィルターの実装 この記事は、インテル® ソフトウェア・ネットワークに掲載されている「Intel® AVX Realization Of IIR Filter For Complex Float Data」の日本語参考訳です。 はじめに この記事では、インテル® AVX の SIMD (Single Instruction […]