記事内にはプロモーションが含まれています

【C++】例外処理の書き方・仕組みや使いどころ

【C++】例外処理の書き方・仕組みや使いどころ C++

C++で安全なプログラムを開発する上で、予期せぬエラーに対処する「例外処理」は避けて通れない重要な機能です。

ファイルが開けなかった、メモリが足りない……といった実行時のエラーを適切にハンドリングしなければ、アプリケーションは唐突にクラッシュしてしまいます。

しかし、C++の例外処理には「処理速度が遅くなる」「コードが複雑になる」といった議論もあり、「例外処理を使うべきか、使わないべきか」という点で迷うエンジニアも少なくありません。

この記事では、C++における例外処理(try-catch-throw)の基本的な書き方から、標準例外クラスの活用、そして「例外を使わない」という選択肢の理由やモダンな代替手段(std::expected)まで、サンプルコード付きで徹底解説します。

【本記事の信頼性】
プロフィール
執筆者:マヒロ
  • 執筆者は元エンジニア
  • SES⇒大手の社内SE⇒独立
  • 現在はプログラミングスクールを運営
  • モットーは「利他の精神」
💻 本記事の検証環境(2026年2月確認)
  • OS:Windows 11 / macOS Sequoia
  • IDE:Visual Studio / VS Code / IntelliJ IDEA
  • その他:Chrome DevTools / 各言語最新安定版

※本メディアでは、上記環境にてコードの動作と情報の正確性を検証済みです。

例外処理(try-catch)の基本構文と書き方

C++の例外処理は、エラーが発生する可能性がある箇所を try ブロックで囲み、発生したエラーを catch ブロックで捕まえて処理するという構造になっています。

エラーを発生させる(投げる)には throw キーワードを使用します。

基本的な実装例:ゼロ除算のチェック

まずは、数値計算でよくある「0で割る」エラーを防ぐコードを見てみましょう。

【注意】
C++の仕様では、整数のゼロ除算は例外を自動的に投げません(未定義動作となり、多くの場合クラッシュします)。浮動小数点数の場合は infNaN になります。そのため、以下の例のようにプログラマが事前にチェックして、明示的に throw する必要があります。
#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 関数の中で、分母 b0.0 の場合に throw を使ってエラーを投げています。

ここでは std::runtime_error という標準ライブラリのクラスを使用しています。
昔のC++コードでは throw "エラーメッセージ" のように文字列を直接投げることがありましたが、型安全性の観点から現在は非推奨です。

main 関数側では、try ブロック内で例外が投げられると即座に処理が中断され、対応する catch ブロックへと制御が移ります。

catch (const std::exception& e) で例外オブジェクトへの参照を受け取り、e.what() でエラーメッセージを取得しています。

独自の例外クラスを作成して詳細な情報を扱う

標準の例外クラス(std::runtime_errorstd::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++のスキルがある場合には、「転職して年収をアップさせる」「副業で稼ぐ」といった方法を検討するのがおすすめです。

C++を扱えるエンジニアは希少価値が高いため、転職によって数十万円の年収アップはザラで、100万円以上年収が上がることも珍しくありません。

なお、転職によって年収を上げたい場合は、エンジニア専門の転職エージェントサービスを利用するのが最適です。
今すぐ転職する気がなくとも、とりあえず転職エージェントに無料登録しておくだけで、スカウトが届いたり、思わぬ好待遇の求人情報が送られてきたりするというメリットがあります。
併せて、副業案件を獲得できるエージェントにも登録しておくと、空いている時間を活かして稼げるようなC++の案件を探しやすくなります。

転職エージェントも副業エージェントも、登録・利用は完全無料なので、どんな求人や副業案件があるのか気になる方は、気軽に利用してみるとよいでしょう。
エンジニアのキャリア・スキルアップ相談窓口
当ブログの読者に選ばれている、実績豊富な転職エージェントを厳選しました。
【転職】年収・環境を改善したい
年収アップにこだわりたい方 (平均アップ額138万円の実績)
未経験・経験者問わず幅広く探したい方
業界に精通した担当者に相談したい方
ゲーム業界への転職を志望する方
エンジニア未経験からキャリアを築く方
【独立】フリーランスとして稼ぎたい
国内最大級のフリーランス案件数から比較したい方
週1〜3日など柔軟な働き方を希望する方
【学習】スキルに不安のある方向け(格安スクール「デイトラ」)