C++で安全なプログラムを開発する上で、予期せぬエラーに対処する「例外処理」は避けて通れない重要な機能です。
ファイルが開けなかった、メモリが足りない……といった実行時のエラーを適切にハンドリングしなければ、アプリケーションは唐突にクラッシュしてしまいます。
この記事では、C++における例外処理(try-catch-throw)の基本的な書き方から、標準例外クラスの活用、そして「例外を使わない」という選択肢の理由やモダンな代替手段(std::expected)まで、サンプルコード付きで徹底解説します。
![]() 執筆者:マヒロ |
|
- OS:Windows 11 / macOS Sequoia
- IDE:Visual Studio / VS Code / IntelliJ IDEA
- その他:Chrome DevTools / 各言語最新安定版
※本メディアでは、上記環境にてコードの動作と情報の正確性を検証済みです。
例外処理(try-catch)の基本構文と書き方
C++の例外処理は、エラーが発生する可能性がある箇所を try ブロックで囲み、発生したエラーを catch ブロックで捕まえて処理するという構造になっています。
エラーを発生させる(投げる)には throw キーワードを使用します。
基本的な実装例:ゼロ除算のチェック
まずは、数値計算でよくある「0で割る」エラーを防ぐコードを見てみましょう。
#include <iostream>
#include <stdexcept> // 標準例外クラスを使用するために必要
double divide(double a, double b) {
if (b == 0.0) {
// エラー発生時は標準の例外クラスを投げるのが推奨(文字列リテラルではなく)
throw std::runtime_error("ゼロによる除算が発生しました!");
}
return a / b;
}
int main() {
double x = 10.0;
double y = 0.0;
try {
// 例外が発生する可能性のある処理
double result = divide(x, y);
std::cout << "計算結果: " << result << std::endl;
}
catch (const std::exception& e) {
// 投げられた例外(std::exceptionとその派生)を受け取る
std::cerr << "エラー: " << e.what() << std::endl;
}
std::cout << "プログラムを正常に終了します。" << std::endl;
return 0;
}
実行結果
エラー: ゼロによる除算が発生しました!
プログラムを正常に終了します。
divide 関数の中で、分母 b が 0.0 の場合に throw を使ってエラーを投げています。
ここでは std::runtime_error という標準ライブラリのクラスを使用しています。
昔のC++コードでは throw "エラーメッセージ" のように文字列を直接投げることがありましたが、型安全性の観点から現在は非推奨です。
main 関数側では、try ブロック内で例外が投げられると即座に処理が中断され、対応する catch ブロックへと制御が移ります。
catch (const std::exception& e) で例外オブジェクトへの参照を受け取り、e.what() でエラーメッセージを取得しています。
独自の例外クラスを作成して詳細な情報を扱う
標準の例外クラス(std::runtime_error や std::logic_error)だけでは表現しきれない固有のエラーがある場合は、自分で例外クラスを作成することができます。
通常は std::exception クラスを継承して作ります。
カスタム例外クラスの実装例
#include <iostream>
#include <exception>
#include <string>
// std::exception を継承した独自クラス
class FileOpenError : public std::exception {
private:
std::string message;
public:
// コンストラクタでファイル名を受け取る
FileOpenError(const std::string& filename) {
message = "ファイル '" + filename + "' を開けませんでした。";
}
// what() をオーバーライドしてメッセージを返す
// noexcept は「この関数は例外を投げない」という宣言
virtual const char* what() const noexcept override {
return message.c_str();
}
};
void openFile(const std::string& name) {
// 擬似的にエラーを発生させる
throw FileOpenError(name);
}
int main() {
try {
openFile("test.txt");
}
catch (const FileOpenError& e) {
std::cerr << "独自例外: " << e.what() << std::endl;
}
catch (const std::exception& e) {
std::cerr << "その他の例外: " << e.what() << std::endl;
}
return 0;
}
実行結果
独自例外: ファイル 'test.txt' を開けませんでした。
std::exception を継承し、what() メソッドをオーバーライドすることで、独自のメッセージを返すクラス FileOpenError を作成しました。
noexcept キーワードは、この関数が決して例外を投げないことをコンパイラに伝えます(なお、C++11以降ではデストラクタもデフォルトで noexcept として扱われます)。
catch ブロックを複数記述することで、特定の例外(FileOpenError)と、それ以外の一般的な例外(std::exception)で処理を分けることができます。
例外処理の適切な「使いどころ」とは?
例外処理は強力ですが、すべてのエラーに対して使うべきではありません。
一般的には以下のような基準で使い分けます。
例外を使うべき場面
- 異常系: ファイルが存在しない、ネットワークが繋がらない、メモリ不足など、プログラムの正常なフローでは対処しきれない事態。
- コンストラクタの失敗: コンストラクタは戻り値を返せないため、生成に失敗したことを伝える唯一の手段が例外です。
- 多階層をまたぐエラー: 深い関数呼び出しの先でエラーが起きた際、バケツリレーのようにエラーコードを返し続けるよりも、例外で一気に上位へ通知する方がコードが簡潔になります。
例外を避けるべき場面
- 頻繁に発生する事象: ユーザーの入力ミスや、ループの終了条件など、通常の制御フローで処理できるものに例外を使うと、パフォーマンスが低下します。
「例外処理は使わない」派の理由とモダンな代替案
C++の開発現場では、「例外処理を禁止する(noexcept)」というコーディング規約が採用されることがあります。
GoogleのC++スタイルガイドなどでも例外の使用を制限していますが、なぜ便利な機能をあえて使わないのでしょうか。
パフォーマンスと「ゼロコスト例外」
例外処理を有効にすると、コンパイラは例外発生時にスタックを巻き戻す(関数を安全に抜けていく)ための準備をします。
現代のコンパイラ(GCC, Clang, MSVCなど)は「ゼロコスト例外(Zero-cost exceptions)」モデルを採用しており、例外が投げられない限り、実行速度のオーバーヘッドはほぼゼロです。
しかし、ひとたび例外が投げられると、スタックの巻き戻し処理に比較的大きなコストがかかります。
そのため、リアルタイム性が求められるゲーム開発や組み込みシステムでは、予測可能なエラーに対して例外を使うことを避ける傾向があります。
制御フローの複雑化
例外は、関数の呼び出し元を飛び越えて、さらに上位の catch ブロックまで一気にジャンプすることがあります。
これは「どこでエラー処理が行われているか」をコード上で追うのを難しくし、プログラムの流れ(制御フロー)を複雑にする要因になります。
【C++23】std::expected による新しいエラー処理
最新のC++23では、例外を使わずにリッチなエラーハンドリングを実現する std::expected が導入されました。
これは「正常値」または「エラー値」のどちらか一方を持つ型で、Rustの Result 型に近いものです。
// C++23 std::expected のイメージ
#include <expected>
#include <string>
std::expected<int, std::string> toInt(const std::string& str) {
if (isNumber(str)) {
return std::stoi(str); // 正常値
} else {
return std::unexpected("数値ではありません"); // エラー値
}
}
例外のような「大域ジャンプ」を伴わずに、戻り値として安全にエラーを伝播できるため、今後のC++開発ではこのスタイルが主流になっていく可能性があります。
C++のスキルを活かして年収を上げる方法
以上、C++での例外処理の書き方・仕組みなどについて解説しました。
C++を扱えるエンジニアは希少価値が高いため、転職によって数十万円の年収アップはザラで、100万円以上年収が上がることも珍しくありません。
なお、転職によって年収を上げたい場合は、エンジニア専門の転職エージェントサービスを利用するのが最適です。
転職エージェントも副業エージェントも、登録・利用は完全無料なので、どんな求人や副業案件があるのか気になる方は、気軽に利用してみるとよいでしょう。
| 年収アップにこだわりたい方 (平均アップ額138万円の実績) | テックゴー |
| 未経験・経験者問わず幅広く探したい方 | ユニゾンキャリア |
| 業界に精通した担当者に相談したい方 | キッカケエージェント |
| ゲーム業界への転職を志望する方 | ファミキャリ |
| エンジニア未経験からキャリアを築く方 | イーチキャリア |



