C++: 例外処理
最終更新:2026-08-31
プログラム実行時、エラーは避けられません:ファイルが見つからない、メモリ不足、ネットワーク障害……
しかし、プログラムはクラッシュしてはいけません。
例外処理はプログラムの「安全ネット」——問題が起きても、適切に処理し、ユーザーに伝え、ログを残せます。
1. 例外処理の基本
(1) 1.1 例外とは?
例外はプログラム実行時のエラー状態を表します。
主な例外の種類:
- ゼロ除算
- ヌルポインタアクセス
- ファイルオープン失敗
- メモリ確保失敗
(2) 1.2 例外処理の仕組み
C++の例外処理は3つのキーワードを使用します:
| キーワード | 機能 |
|---|---|
try |
例外を監視するコード |
catch |
例外を捕捉して処理 |
throw |
例外を投げる |
(3) 1.3 基本的な例
▶ サンプル 1:コード例(難易度 ⭐)
#include <iostream>
int divide(int a, int b) {
if (b == 0) {
throw "ゼロ除算エラー!"; // 例外を投げる
}
return a / b;
}
int main() {
try {
std::cout << divide(10, 2) << std::endl; // 正常終了
std::cout << divide(10, 0) << std::endl; // 例外発生
}
catch (const char* msg) { // 例外を捕捉
std::cerr << "エラー:" << msg << std::endl;
}
std::cout << "プログラム終了" << std::endl;
return 0;
}
出力:
TEXT 📖 参照専用5 エラー:ゼロ除算エラー! プログラム終了
- 例外が発生しても、プログラムはクラッシュせず、catchブロックにジャンプして処理を継続します
2. 例外の型
(1) 2.1 投げられる型
throw で任意の型を投げられます:
| 型 | 例 |
|---|---|
| 基本型 | throw 42; |
| 文字列 | throw "エラー"; |
| 標準例外 | throw std::runtime_error("エラー"); |
| カスタム例外 | throw MyException(); |
(2) 2.2 複数の例外
▶ サンプル:複数型の例外(難易度 ⭐⭐)
#include <iostream>
### ▶ サンプル 2:(難易度 ⭐)
#include <string>
void process(int value) {
if (value == 0) {
throw 0; // int型
}
if (value < 0) {
throw std::string("負の値です"); // string型
}
}
int main() {
try {
process(-5);
}
catch (int e) {
std::cerr << "int例外:" << e << std::endl;
}
catch (const std::string& e) {
std::cerr << "string例外:" << e << std::endl;
}
return 0;
}
3. 標準例外クラス
(1) 3.1 std::exception階層
C++標準ライブラリは例外クラスを定義しています。<stdexcept> ヘッダ:
std::exception
├── std::logic_error
│ ├── std::invalid_argument
│ ├── std::out_of_range
│ └── std::length_error
└── std::runtime_error
├── std::overflow_error
└── std::underflow_error
(2) 3.2 標準例外の使用
| 例外クラス | 説明 |
|---|---|
std::invalid_argument |
無効な引数 |
std::out_of_range |
範囲外アクセス |
std::runtime_error |
実行時エラー |
std::bad_alloc |
メモリ確保失敗(new) |
▶ サンプル:標準例外の使用(難易度 ⭐⭐)
#include <iostream>
#include <stdexcept>
#include <vector>
int main() {
std::vector<int> v = {1, 2, 3};
try {
v.at(10); // 範囲外アクセス
}
catch (const std::out_of_range& e) {
std::cerr << "例外:" << e.what() << std::endl;
}
return 0;
}
4. カスタム例外
(1) 4.1 std::exceptionを継承
推奨: カスタム例外クラスは std::exception を継承します。
▶ サンプル:カスタム例外クラス(難易度 ⭐⭐⭐)
#include <iostream>
#include <exception>
#include <string>
// カスタム例外クラス
class MyException : public std::exception {
private:
std::string msg;
public:
MyException(const std::string& msg) : msg(msg) {}
const char* what() const noexcept override {
return msg.c_str();
}
};
int main() {
try {
throw MyException("カスタムエラーが発生しました");
}
catch (const MyException& e) {
std::cerr << "捕捉:" << e.what() << std::endl;
}
return 0;
}
5. スタックアンワインディング
(1) 5.1 スタックアンワインディングとは?
スタックアンワインディング:例外が投げられた時、プログラムは関数呼び出しスタックを遡り、適切なcatchブロックを見つけるまで、各スコープのローカルオブジェクトのデストラクタを実行します。
重要なポイント:
- 基本原則: プログラムはスタックを遡り、各関数のローカルオブジェクトのデストラクタを呼ぶ
- リソース管理: メモリ、ファイルハンドラなど(デストラクタで解放)
- 例外安全: 関数が例外を投げてもリソースがリークしないこと
(2) 5.2 RAII——リソース管理のベストプラクティス
RAII(Resource Acquisition Is Initialization):オブジェクトの生命周期でリソースを管理し、デストラクタで解放します。
▶ サンプル:スマートポインタで例外時の解放(難易度 ⭐⭐⭐)
#include <iostream>
#include <memory>
void process() {
// unique_ptrで確保、例外が起きても自動解放
std::unique_ptr<int> p(new int(42));
// ... 例外が発生する可能性のある処理 ...
std::cout << *p << std::endl;
} // pのデストラクタが呼ばれ、メモリ解放
int main() {
try {
process();
}
catch (...) {
std::cerr << "例外が発生しました" << std::endl;
}
return 0;
}
6. noexcept指定子
(1) 6.1 基本的な使い方
C++11の noexcept は、関数が例外を投げないことを宣言します。
▶ サンプル:例外を投げない関数(難易度 ⭐)
void safeFunction() noexcept {
// 例外を投げない安全な関数
}
void unsafeFunction() {
// 例外を投げる可能性がある
}
- デストラクタは暗黙的に
noexceptです noexceptはコンパイラの最適化に役立ちます
▶ サンプル 2:複数のcatchブロック(難易度 ⭐⭐)
#include <iostream>
#include <stdexcept>
void processData(int value) {
if (value < 0) {
throw std::invalid_argument("負の値は許可されていません");
}
if (value > 100) {
throw std::out_of_range("値が上限を超えています");
}
std::cout << "処理中: " << value << std::endl;
}
int main() {
int testValues[] = {50, -5, 150};
for (int val : testValues) {
try {
processData(val);
} catch (const std::invalid_argument& e) {
std::cout << "無効な引数: " << e.what() << std::endl;
} catch (const std::out_of_range& e) {
std::cout << "範囲外: " << e.what() << std::endl;
}
}
return 0;
}
出力:
TEXT 📖 参照専用処理中: 50 無効な引数: 負の値は許可されていません 範囲外: 値が上限を超えています
▶ サンプル 3:ネストした例外処理(難易度 ⭐⭐)
#include <iostream>
#include <stdexcept>
void innerFunction(int value) {
if (value < 0) {
throw std::invalid_argument("負の値は許可されていません");
}
std::cout << "内部関数が処理しました: " << value << std::endl;
}
void outerFunction(int value) {
try {
innerFunction(value);
} catch (const std::invalid_argument& e) {
std::cout << "外部で捕捉: " << e.what() << std::endl;
throw; // mainに再スロー
}
}
int main() {
try {
outerFunction(10);
outerFunction(-5);
} catch (const std::exception& e) {
std::cout << "mainで捕捉: " << e.what() << std::endl;
}
std::cout << "プログラム終了" << std::endl;
return 0;
}
出力:
TEXT 📖 参照専用内部関数が処理しました: 10 外部で捕捉: 負の値は許可されていません mainで捕捉: 負の値は許可されていません プログラム終了
❓ よくある質問
Q:いつ例外を使う? A:回復可能なエラー状況で、エラー処理コードを通常のフローから分離したい場合。ストリームなどで使用。
Q:catch(...) とは? A:すべての型の例外を捕捉します。ログを残して再スローする場合に使用。
try {
// ...
}
catch (...) {
// ログを記録
throw; // 再スロー
}
Q:コンストラクタで例外を投げると? A:オブジェクトは生成されませんが、メモリリークの可能性があります。スマートポインタやRAIIでリソース管理を。
📖 まとめ
| トピック | 要点 |
|---|---|
| try-catch-throw | 例外処理の基本構文 |
| 標準例外 | std::runtime_error など |
| カスタム例外 | std::exception を継承 |
| スタックアンワインディング | RAIIでリソース管理 |
| noexcept | 関数が例外を投げないことを宣言 |
📝 練習問題
-
初級(難易度 ⭐): 除算関数を書き、0で除算時に
std::runtime_errorを投げ、main関数でtry/catchしてエラーを出力してください。 -
中級(難易度 ⭐⭐): カスタム例外クラス(
std::exceptionを継承)を定義し、ファイルオープンエラーとネットワークエラーを区別して、throwとcatchを試してください。 -
上級(難易度 ⭐⭐⭐): 「リソース管理」クラスを実装し、コンストラクタでリソースを確保し、デストラクタで解放。例外を投げてもリークしないことを確認(RAII)。
- 例外処理の基本:try、throw、catch の3つ
- 標準例外クラスは std::exception を継承
- catch は型でマッチング、順序に注意
- スタックアンワインディング:例外捕捉までデストラクタが呼ばれる
- noexcept は関数が例外を投げないことを宣言
次のレッスン:ファイル操作応用(#41)