はじめに
C++でエラーが発生した際、単に throw "エラーメッセージ";
のように文字列を投げるのではなく、標準ライブラリ <stdexcept>
などで定義されている標準例外クラスを使うのが良い習慣です。
これらのクラスは std::exception
を基底クラスとする階層構造を持っており、catch
ブロックで特定の種類のエラーだけを捕捉したり、エラーの大まかな分類で捕捉したりと、柔軟なエラーハンドリングを可能にします。
この記事では、主要な標準例外クラスの紹介と、その中の一つ std::out_of_range
を使った具体的な例を解説します。
主要な標準例外クラス
<stdexcept>
で定義されている主な例外クラスは、大きく2つのグループに分けられます。
- 論理エラー (
logic_error
): プログラムのロジックのバグが原因で発生する、実行前に発見できるはずのエラー。 - 実行時エラー (
runtime_error
): プログラムの実行中に、外部要因などによって発生する、実行前に予測できないエラー。
基底クラス | 例外クラス | 説明 |
logic_error | invalid_argument | 関数に渡された引数が不正 |
domain_error | 関数の定義域外の引数が渡された | |
length_error | オブジェクトが許容する最大長を超えた | |
out_of_range | 範囲外のインデックスでアクセスしようとした | |
runtime_error | range_error | 内部計算の結果が有効範囲外になった |
overflow_error | 算術オーバーフローが発生した | |
underflow_error | 算術アンダーフローが発生した |
標準例外クラスを使ったサンプルコード
このコードは、配列のインデックスが有効範囲内にあるかをチェックし、範囲外であれば std::out_of_range
例外をスローします。main
関数は、try-catch
ブロックでその例外を捕捉します。
完成コード
#include <iostream>
#include <vector>
#include <stdexcept> // out_of_range, exception など
using namespace std;
// vectorの指定したインデックスの値を返す関数
int get_value_at(const vector<int>& data, int index) {
// インデックスが有効範囲外なら、out_of_range例外をスロー
if (index < 0 || index >= data.size()) {
throw out_of_range("指定されたインデックスは範囲外です。");
}
return data[index];
}
int main() {
vector<int> numbers = {10, 20, 30};
try {
// 1. 成功するケース
cout << "インデックス1の値: " << get_value_at(numbers, 1) << endl;
// 2. 失敗するケース (範囲外のインデックスを指定)
cout << "インデックス5の値: " << get_value_at(numbers, 5) << endl;
}
// 3. out_of_range例外を捕捉する
catch (const out_of_range& e) {
cerr << "エラー検知: " << e.what() << endl;
// 捕捉した例外を、さらに呼び出し元に投げることもできる
// throw;
}
// 汎用的なexceptionで、他の種類の例外も捕捉できる
catch (const exception& e) {
cerr << "予期せぬエラー: " << e.what() << endl;
}
return 0;
}
コードの解説
throw out_of_range(...)
throw
キーワードを使って、std::out_of_range
クラスのオブジェクトを生成し、それを「例外」として送出(スロー)しています。コンストラクタの引数に、エラーの詳細を説明する文字列を渡すのが一般的です。
catch (const out_of_range& e)
try
ブロック内でスローされた例外を、catch
ブロックで捕捉(キャッチ)します。
const out_of_range& e
:out_of_range
型(またはその親クラスの型)の例外だけを、このブロックで捕捉することを意味します。e.what()
: 捕捉した例外オブジェクトe
の.what()
メンバ関数を呼び出すと、throw
時にコンストラクタに渡したエラーメッセージ文字列を取得できます。
throw;
catch
ブロックの最後で throw;
とだけ書くと、捕捉したのと同じ例外オブジェクトを、そのまま再度スローすることができます。これにより、現在の関数でログ記録などの部分的なエラー処理を行った後、最終的な処理をさらに呼び出し元の関数に委ねる、といった階層的なエラーハンドリングが可能になります。
まとめ
今回は、C++の標準例外クラスを使い分けて、エラーの種類を明確に表現する方法を解説しました。
- エラーの種類に応じて、
out_of_range
やinvalid_argument
といった、意味的に最も近い例外クラスを選択してthrow
する。 catch
ブロックを複数用意することで、例外の型ごとに異なるエラー処理を記述できる。
適切な例外クラスを使い分けることで、コードを読む人(将来の自分も含む)が、どのような種類のエラーが発生しうるのかを理解しやすくなり、プログラムの堅牢性が向上します。