【C++】エラーハンドリングの3つの主要な方法(戻り値・例外・グローバル変数)

目次

はじめに

C++で堅牢なプログラムを作成するには、エラーハンドリングが不可欠です。ファイルが見つからない、ネットワークに接続できない、不正な入力が行われた、といったエラーは日常的に発生します。これらのエラーを放置すると、プログラムはクラッシュしたり、データを破壊したりする可能性があります。

C++には、エラーを処理するためのいくつかの伝統的なアプローチがあります。この記事では、3つの主要なエラーハンドリング手法について、それぞれの長所と短所を解説します。

  1. 戻り値によるエラーハンドリング
  2. 例外によるエラーハンドリング (C++の標準的な方法)
  3. グローバル変数によるエラーハンドリング

1. 戻り値によるエラーハンドリング

関数が処理に成功したか失敗したかを示す特別な値(エラーコード、例: -1false)を、関数の戻り値として返す方法です。C言語で広く使われてきた伝統的な手法です。

サンプルコード

#include <iostream>
#include <string>

// 文字列を整数に変換する。失敗した場合は-1を返す。
int convert_to_int(const std::string& s) {
    try {
        return std::stoi(s);
    } catch (...) {
        return -1; // エラーコードとして-1を返す
    }
}

int main() {
    int result = convert_to_int("123");
    if (result != -1) {
        std::cout << "変換成功: " << result << std::endl;
    } else {
        std::cout << "変換失敗" << std::endl;
    }
    
    result = convert_to_int("abc"); // 失敗するケース
    if (result == -1) {
        std::cout << "変換失敗" << std::endl;
    }
    
    return 0;
}

問題点:

  • エラーと正常値の区別: もし -1 が正常な値としてあり得る場合(例: 温度の測定)、エラーと正常値を区別できなくなります。
  • コードの冗長化: 関数を呼び出すたびに、if文で戻り値をチェックする必要があり、コードが冗長で読みにくくなります。

2. 例外によるエラーハンドリング (推奨)

エラーが発生した時点で、throw を使って「例外オブジェクト」を送出し、関数の呼び出し元にエラー処理を委ねる方法です。現代のC++における標準的なエラーハンドリング手法です。

サンプルコード

#include <iostream>
#include <stdexcept> // runtime_error
#include <string>

// 例外を投げる関数
void process_data() {
    // ... 何らかの処理 ...
    throw std::runtime_error("データ処理中にエラーが発生しました。");
}

void sub_task() {
    process_data();
}

int main() {
    // 1. tryブロックで、例外が発生する可能性のある処理を囲む
    try {
        sub_task();
    }
    // 2. catchブロックで、特定の型の例外を捕捉する
    catch (const std::runtime_error& e) {
        std::cerr << "エラーを捕捉しました: " << e.what() << std::endl;
    }
    
    return 0;
}

メリット:

  • エラー処理の分離: 正常系の処理と、異常系の処理(catchブロック)を明確に分離できるため、コードが非常にクリーンになります。
  • 詳細なエラー情報: e.what() のように、エラーに関する詳細な情報を簡単に伝達できます。
  • 呼び出し階層のスキップ: エラーが発生すると、catchブロックが見つかるまで、関数の呼び出し階層を一気に遡ります。途中の関数でエラーチェックを繰り返す必要がありません。

3. グローバル変数によるエラーハンドリング

エラー情報を、プログラム全体からアクセスできるグローバル変数に格納する方法です。古いC言語のライブラリ(例: errno)などで見られます。

サンプルコード

#include <iostream>

// グローバルなエラーコード変数
int g_error_code = 0;

void do_something() {
    // 何らかの理由で失敗したと仮定
    g_error_code = 1; // エラーコードを設定
}

int main() {
    g_error_code = 0; // 実行前にリセット
    do_something();
    
    if (g_error_code != 0) {
        std::cout << "エラーが発生しました。コード: " << g_error_code << std::endl;
    }
    
    return 0;
}

問題点: グローバル変数は、プログラムのどこからでも変更できてしまうため、大規模なプログラム、特にマルチスレッド環境では、いつ、どこでエラーが設定されたのかを追跡するのが非常に困難になり、バグの温床となります。この方法は、現代のC++では一般的に非推奨です。


まとめ

今回は、C++の3つの主要なエラーハンドリング手法を解説しました。

方法メリットデメリット推奨度
戻り値シンプルコードが冗長、エラーと正常値の区別が曖昧△ (限定的)
例外エラー処理を分離でき、コードが明確try-catch構文の理解が必要◎ (推奨)
グローバル変数(ほぼ無し)バグの温床になりやすい☓ (非推奨)

結論として、C++では、回復可能なエラーを扱う際には std::expected (C++23) や std::optional (C++17) を、回復不能な、あるいはプログラムの前提を崩すようなエラーを扱う際には「例外」を使うのが、最も安全でモダンなベストプラク-ティスです。

よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!

この記事を書いた人

私が勉強したこと、実践したこと、してることを書いているブログです。
主に資産運用について書いていたのですが、
最近はプログラミングに興味があるので、今はそればっかりです。

目次