C++における「例外処理(Exceptions)」は、プログラムの実行中に発生するエラーを効果的に管理するための重要なメカニズムです。例外処理を使うことで、プログラムが予期しないエラーに遭遇した場合でも、安全かつ適切に処理を行い、プログラムのクラッシュを防ぐことができます。この記事では、C++における例外処理の基本から高度な使い方までを完全かつ包括的に説明します。
1. 例外処理の基本
C++で例外を処理するためには、主に3つのキーワードを使用します:
-
throw:例外を投げるために使用します。
-
try:例外が発生する可能性のあるコードブロックを囲むために使用します。
-
catch:例外を受け取り、処理を行うために使用します。
例外処理の基本的な流れ
cpp#include
using namespace std;
int main() {
try {
// ここで例外を投げる
throw 20;
} catch (int e) {
// 例外を受け取って処理する
cout << "Caught an exception: " << e << endl;
}
return 0;
}
このコードでは、throw 20;によって整数の例外を投げ、その例外をcatchブロックで受け取って処理しています。この例では、20という整数が例外として投げられ、catch (int e)でその整数を受け取り、コンソールに出力しています。
2. 例外の種類
C++では、例外はクラスのオブジェクトとして扱われます。最も一般的なのは、標準ライブラリのstd::exceptionを基にした例外クラスです。C++では、基本的にどんな型のオブジェクトも例外として投げることができます。
例外クラスの例
cpp#include
#include // std::exceptionを含むヘッダ
using namespace std;
class MyException : public exception {
public:
const char* what() const noexcept override {
return "My custom exception occurred!";
}
};
int main() {
try {
throw MyException();
} catch (const MyException& e) {
cout << "Caught exception: " << e.what() << endl;
}
return 0;
}
この例では、MyExceptionというカスタム例外クラスを定義し、それをthrowで投げ、catchブロックで捕まえています。what()メソッドは、例外に関するエラーメッセージを返します。
3. 複数の例外を処理する
C++では、catchブロックを複数記述することができ、異なる型の例外を個別に処理することができます。
cpp#include
using namespace std;
int main() {
try {
// ここで異なるタイプの例外を投げる
throw 42; // 整数型の例外
} catch (int e) {
cout << "Caught an integer exception: " << e << endl;
} catch (const char* msg) {
cout << "Caught a string exception: " << msg << endl;
} catch (...) {
cout << "Caught an unknown exception!" << endl;
}
return 0;
}
ここでは、整数型の例外を最初に捕捉し、異なる型の例外が投げられた場合には、適切に処理されるように複数のcatchブロックを使用しています。最後にcatch(...)を使うことで、他の予期しない型の例外を捕捉することができます。
4. 例外を再投げる
一度捕捉した例外を再度投げることも可能です。これを行うには、throw;と記述します。この場合、例外の情報はそのまま保持され、再び外部のcatchブロックに伝達されます。
cpp#include
using namespace std;
void foo() {
try {
throw 30;
} catch (int e) {
cout << "Caught exception in foo: " << e << endl;
throw; // 例外を再投げする
}
}
int main() {
try {
foo();
} catch (int e) {
cout << "Caught exception in main: " << e << endl;
}
return 0;
}
このコードでは、foo関数内で発生した例外をキャッチし、再投げしてmain関数で処理を行っています。
5. 例外の安全性
例外処理を使う際、特にリソース管理やメモリ管理が重要です。C++では、例外が発生してもリソースが適切に解放されるように、RAII(Resource Acquisition Is Initialization)というパターンが推奨されています。
スマートポインタを使った例
C++では、std::unique_ptrやstd::shared_ptrなどのスマートポインタを使うことで、リソースの管理を簡素化できます。これらのスマートポインタは、例外が発生しても自動的にメモリを解放します。
cpp#include
#include
using namespace std;
void riskyFunction() {
unique_ptr<int> ptr(new int(10));
throw runtime_error("An error occurred");
}
int main() {
try {
riskyFunction();
} catch (const exception& e) {
cout << "Caught exception: " << e.what() << endl;
}
return 0;
}
ここでは、unique_ptrを使って動的に割り当てたメモリを管理しています。riskyFunction内で例外が発生しても、unique_ptrが自動的にメモリを解放するため、メモリリークを防ぐことができます。
6. 例外を使う際のベストプラクティス
-
例外は必要なときだけ使う:例外はエラー処理のためのものです。通常の制御フローでは使用しないようにしましょう。
-
例外を適切にキャッチする:複数の例外を処理する場合、
catchブロックの順番を適切に配置し、具体的な例外を最初にキャッチするようにします。 -
リソース管理を怠らない:
RAIIパターンを利用して、例外発生時でもリソースが適切に解放されるようにします。
7. C++17以降の例外処理の進化
C++17では、noexceptという新しいキーワードが導入されました。このキーワードは、関数が例外を投げないことを保証するために使います。これにより、コンパイラは最適化を行いやすくなり、プログラムの性能が向上します。
cppvoid foo() noexcept {
// ここでは例外を投げないことが保証される
}
noexceptを関数に付けることで、その関数が例外を投げることはないとコンパイラに伝え、より効率的なコード生成を実現できます。
8. 結論
C++における例外処理は、プログラムのエラーハンドリングを適切に行うために非常に重要な機能です。基本的な使い方を理解した後は、カスタム例外を作成したり、例外を再投げしたり、例外安全なコードを書くための技術を学んだりすることが重要です。例外を効果的に活用することで、より堅牢で信頼性の高いプログラムを作成できます。
