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

C++

ファイルが見つからない。配列の範囲外にアクセスした。プログラムは、実行中に、想定外のことが起きることがあります。そのたびに、いきなりクラッシュしていては困ります。

例外処理を使うと、こうした失敗を、安全に受け止められます。この記事では、throw・try・catchの意味と使い方、実際の使いどころ、初心者がハマりやすいミスを解説します。

例外処理とは?

例外処理とは、エラー(例外)が起きたときに、プログラムを止めずに対処する仕組みです。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::vector の at() は、範囲外にアクセスすると 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が安全

throwで投げて、tryで囲み、catchで受け取ります。標準例外のメッセージは、what()で取れます。範囲外の検知にはat()、リソースの管理には、RAIIが安全です。

関連記事