【C++】例外処理(try/catch)とは?|エラーを安全に扱う基本

C++

今回は、C++でよく出てくる「例外処理(try / catch)とは何か」について解説していきます。

「例外処理って何?」
「try / catch / throw の意味がわからない…」
「エラーが起きたとき、どう対処すればいいの?」

こんな疑問はありませんか?

プログラムは、ファイルが見つからない・配列の範囲外にアクセスした、など実行中に想定外のことが起きることがあります。例外処理を使うと、そんなときにいきなりクラッシュさせず、安全に対処できます。

この記事を読み終えると、あなたは例外処理の意味・try/catch/throwの使い方・実際の使いどころ・初心者がハマりやすいミスをしっかり理解できると思いますので、ぜひ最後まで読んでいただけると嬉しいです。

例外処理とは?

例外処理とは、エラー(例外)が起きたときに、プログラムを止めずに対処する仕組みです。3つのキーワードで動きます。

  • throw … 例外を投げる(エラーを知らせる)
  • try … 例外が起きるかもしれない処理を囲む
  • catch … 投げられた例外を受け取って対処する
#include <iostream>

int main() {
    try {
        int hp = -5;
        if (hp < 0) {
            throw "HPがマイナスです"; // 例外を投げる
        }
        std::cout << "HP: " << hp << "n";
    }
    catch (const char* msg) {
        std::cout << "エラー: " << msg << "n"; // 受け取って対処
    }
    return 0;
}
エラー: HPがマイナスです

throw した瞬間に処理が中断され、catch に飛んで対処が行われます。プログラムは落ちずに続けられます。

なぜ例外処理が必要?

理由は、エラーを無視すると、そのままクラッシュしたり誤動作したりするからです。戻り値でエラーをいちいちチェックする方法もありますが、例外を使うと正常な処理とエラー対処をきれいに分けて書けるので見通しが良くなります。

標準の例外クラスを使う

実際には、文字列ではなく標準の例外クラスを使うのが一般的です。std::runtime_error などがあり、what() でメッセージを取り出せます。

#include <iostream>
#include <stdexcept>

int main() {
    try {
        throw std::runtime_error("セーブデータの読み込みに失敗");
    }
    catch (const std::exception& e) {
        std::cout << "例外: " << e.what() << "n";
    }
    return 0;
}
例外: セーブデータの読み込みに失敗

catch (const std::exception& e) と書いておくと、標準の例外をまとめて受け取れます。

実践例:範囲外アクセスを検知する

std::vectorat() は、範囲外にアクセスすると std::out_of_range を投げてくれます。[] と違って安全にエラーを検知できます(vectorの基本は配列とvectorの違いの記事へ)。

#include <iostream>
#include <vector>
#include <stdexcept>

int main() {
    std::vector<int> enemies = {10, 20, 30};
    try {
        std::cout << enemies.at(5) << "n"; // 範囲外!
    }
    catch (const std::out_of_range& e) {
        std::cout << "範囲外アクセス: " << e.what() << "n";
    }
    return 0;
}
範囲外アクセス: invalid vector subscript

【重要】私が実際に例外処理で困った体験談

個人開発で、敵リストを vector[] でアクセスしていたとき、範囲外なのにエラーも出ずに変な値を読んで、原因不明のクラッシュに数時間悩まされました。

試しに []at() に変えたところ、out_of_range の例外がはっきり出て、どこで範囲外を踏んでいるか一発で特定できました。デバッグ中は at() が心強い、と学んだ出来事でした。デバッグの基本はデバッグのやり方の記事もどうぞ。

例外処理使用時のよくある失敗例と対処法

①catchの型が合っていない

投げた型と catch の型が違うと受け取れません。標準例外は catch (const std::exception& e) で広く受けるのが基本です。

②例外を握りつぶす(空のcatch)

catch の中を空にすると、エラーが起きてもなかったことにされます。最低でもログを出すなど、握りつぶさないようにしましょう。

③例外でリソースが漏れる

途中で例外が飛ぶと delete が実行されず、メモリリークになることがあります。スマートポインタなどのRAIIで持てば、例外が飛んでも自動で後始末されて安全です。

注意点

  • 標準例外は const std::exception& で受け取る
  • 例外は握りつぶさない(せめてログを残す)
  • 毎フレーム回るゲームループ内で多用しない(例外は重め)。想定内の分岐は if

まとめ

  • 例外処理はエラー時にプログラムを止めずに対処する仕組み
  • throw で投げ、try で囲み、catch で受け取る
  • 標準例外は what() でメッセージを取れる
  • 範囲外検知には at()、リソース管理にはRAIIが安全

例外処理は、想定外のエラーからプログラムを守る安全装置です。まずはファイル読み込みや範囲外アクセスなど、「失敗しうる処理」から try / catch を使ってみてください。

関連記事