C++における例外処理の基本とベストプラクティス

例外処理の概要

例外処理は、C++プログラミングにおける重要なエラーハンドリングメカニズムです。戻り値をチェックする従来の方法に比べ、例外はエラーを自動的に伝播させ、メインロジックからエラーハンドリングコードを分離し、複数の関数にまたがる制御フローを処理できます。これにより、各関数で戻り値を繰り返しチェックし、エラーステータスを渡す必要がなくなります。

例外は、配列のインデックスが範囲外、算術オーバーフロー、不正な引数、メモリ不足などの同期エラーに適しています。一方、ディスクI/Oの完了、ネットワークイベント、マウスやキーボードの入力などの非同期イベントには適していません。

例外処理の構文

例外は、throw文で投げられ、tryブロックとcatchブロックでキャッチされます。

例外の投げ方

throw文は、例外オブジェクトを生成して投げます。投げられた後、その行以降のコードは実行されません。

#include <stdexcept>

void validateInput(int value) {
    if (value < 0) {
        throw std::invalid_argument("入力値が不正です");
    }
    // valueが0以上の場合、この行が実行される
}

例外のキャッチ

tryブロック内で例外が投げられると、プログラムの制御は対応するcatchブロックに移ります。型が一致する最初のcatchブロックが実行されます。型が一致しない場合は、catch(...)ブロックが最終的なハンドラとして機能します。

#include <iostream>
#include <stdexcept>

void processValue(int val) {
    try {
        validateInput(val);
        std::cout << "処理を続行します: " << val << std::endl;
    } catch (const std::invalid_argument& e) {
        std::cerr << "エラーが検出されました: " << e.what() << std::endl;
    } catch (...) {
        std::cerr << "未知のエラーが発生しました" << std::endl;
    }
}

例外の宣言(例外仕様)

関数宣言で、その関数が投げうる例外の型を指定できます。これは古い形式ですが、現在はnoexcept指定子がより一般的です。

void readFile() throw(std::runtime_error); // std::runtime_errorのみを投げる
void calculate() throw(); // 例外を投げないことを示す(C++11では非推奨)

noexcept指定子

noexcept指定子は、関数が例外を投げないことをコンパイラに保証します。関数がnoexceptとして宣言されているにもかかわらず例外を投げると、プログラムはstd::terminate()を呼び出して即座に終了します。

void criticalOperation() noexcept {
    // この関数内で例外が投げられると、std::terminate()が呼ばれる
}

void anotherFunction() noexcept(noexcept(criticalOperation()));
// criticalOperation()がnoexceptなら、anotherFunction()もnoexceptとなる

標準例外クラス

実際の開発では、標準で提供される例外クラスを使用することが推奨されます。独自の例外型を定義する必要がある場合は、通常、std::exceptionを継承します。シンプルな要件の場合は、what()仮想関数をオーバーライドするだけで済みます。

RAII(Resource Acquisition Is Initialization)

RAIIは、「リソースの取得は初期化時に、解放はデストラクタで行う」という手法です。オブジェクトのライフタイムをリソースのライフタイムに結びつけ、例外が投げられた場合でもデストラクタが確実に呼び出されるため、リソースリークを防ぎます。

RAII = リソースのライフタイムをオブジェクトに結びつける + 例外安全性の保証

例外安全性保証

広く認められている例外安全性の保証には、4つのレベルがあります。各レベルは、前のレベルの厳密なスーパーセットです。

  1. 不投げ保証(No-throw guarantee): 関数は例外を投げません。
  2. 強い保証(Strong guarantee): 例外が投げられた場合、プログラムの状態は関数呼び出し前の状態に戻ります。
  3. 基本保証(Basic guarantee): 例外が投げられた場合、プログラムは有効な状態を保ち、リソースリークは発生しません。
  4. 保証なし(No guarantee): リソースリークやメモリ破損が発生する可能性があります。

タグ: C++ 例外処理 RAII 例外安全性 noexcept

7月18日 01:06 投稿