Menu
Coddy logo textTech

OOPにおける例外処理

CoddyのC++ジャーニー「オブジェクト指向プログラミング」セクションの一部 — レッスン 81/104。

C++における例外処理は、実行時エラーを検出し、それに対応するための構造化された手法を提供します。オブジェクト指向プログラミングでは、例外はクラス階層と自然に統合され、何が問題だったのかというコンテキストを持つ、意味のあるエラー型を作成することができます。

基本的なメカニズムは、trythrow、および catch という3つのキーワードを使用します。失敗する可能性のあるコードは try ブロック内に記述し、エラーは throw で通知され、catch ブロックが特定の例外タイプを処理します:

#include <iostream>
#include <stdexcept>

class BankAccount {
    double balance;
public:
    BankAccount(double b) : balance(b) {}
    
    void withdraw(double amount) {
        if (amount > balance) {
            throw std::runtime_error("Insufficient funds");
        }
        balance -= amount;
    }
    
    double getBalance() const { return balance; }
};

int main() {
    BankAccount account(100.0);
    
    try {
        account.withdraw(150.0);
    } catch (const std::runtime_error& e) {
        std::cout << "Error: " << e.what() << "\n";
    }
    
    std::cout << "Balance: " << account.getBalance() << "\n";
}

C++標準ライブラリは、std::exceptionをルートとする例外クラスの階層を提供します。一般的な派生クラスには、実行時の問題のためのstd::runtime_errorや、プログラミング上のミスのためのstd::logic_errorが含まれます。すべての標準例外は、エラーメッセージを返すwhat()メソッドを提供します。

例外がスローされると、スタックの巻き戻しが発生します。プログラムが一致するcatchブロックを検索する際、すべてのローカルオブジェクトに対してデストラクタが呼び出されます。これにより、エラーが発生した場合でも適切なクリーンアップが保証されます。これが、RAIIと例外処理が非常にうまく連携する理由です。

challenge icon

チャレンジ

簡単

オブジェクト指向の文脈における例外処理を実演する、安全な割り算計算機を作成しましょう。割り算を実行し、問題が発生したときに意味のある例外をスローする Calculator クラスを作成し、メインプログラムでそれらの例外を適切に処理します。

コードは以下の3つのファイルに分けて構成します。

  • Calculator.h: 適切なエラー処理を伴う数学的演算を実行する Calculator クラスを定義します。

    Calculator には、割り算の結果を返す divide(double numerator, double denominator) メソッドが必要です。ただし、分母がゼロの場合は、メッセージ "Division by zero" を持つ std::runtime_error をスローする必要があります。分子または分母のいずれかが負の場合は、メッセージ "Negative values not allowed" を持つ std::invalid_argument をスローする必要があります。

    また、値の平方根を返す safeSqrt(double value) メソッドも含めてください。値が負の場合は、メッセージ "Cannot compute square root of negative number" を持つ std::runtime_error をスローします。sqrt 関数のために <cmath> をインクルードする必要があります。

  • Calculator.cpp: ヘッダーファイルで宣言されたメソッドを実装します。必要なヘッダー(<stdexcept><cmath>)をインクルードし、演算を実行する前に条件を確認してください。
  • main.cpp: 3つの入力(それぞれ別の行)を読み取ります。
    1. 操作タイプ: "divide" または "sqrt"
    2. 1番目の数値 (double)
    3. 2番目の数値 (double) — 割り算にのみ使用され、sqrt の場合は無視されます

    Calculator オブジェクトを作成し、try ブロック内で要求された操作を実行します。操作が成功した場合は、"Result: [value]" と出力します。例外がキャッチされた場合は、"Error: [message]" と出力します。ここで、メッセージは例外の what() メソッドから取得します。

    try-catch ブロックの後(例外が発生したかどうかに関わらず)、例外処理後もプログラムの実行が継続されることを示すために "Calculation complete" と出力します。

例えば、入力が "divide"102 の場合:

Result: 5
Calculation complete

入力が "divide"100 の場合:

Error: Division by zero
Calculation complete

入力が "divide"-52 の場合:

Error: Negative values not allowed
Calculation complete

入力が "sqrt"160 の場合:

Result: 4
Calculation complete

入力が "sqrt"-90 の場合:

Error: Cannot compute square root of negative number
Calculation complete

例外をキャッチして意味のあるメッセージを表示し、その後実行を継続することで、プログラムがいかに適切にエラーを処理しているかに注目してください。このパターンにより、エラー処理をクリーンに保ち、メインのロジックを正常なパスに集中させることができます。

チートシート

C++における例外処理では、trythrowcatchという3つのキーワードを使用します。失敗する可能性のあるコードはtryブロック内に記述し、エラーはthrowで通知され、catchブロックで特定の例外タイプを処理します。

try {
    // Code that might throw an exception
    account.withdraw(150.0);
} catch (const std::runtime_error& e) {
    std::cout << "Error: " << e.what() << "\n";
}

クラスメソッドから例外をスローする場合:

void withdraw(double amount) {
    if (amount > balance) {
        throw std::runtime_error("Insufficient funds");
    }
    balance -= amount;
}

C++標準ライブラリは、std::exceptionをルートとする例外クラスを提供しています。一般的な型には以下が含まれます:

  • std::runtime_error - 実行時の問題用
  • std::logic_error - プログラミング上のミス用
  • std::invalid_argument - 無効な引数用

すべての標準例外は、エラーメッセージを返すwhat()メソッドを提供しています。

例外がスローされると、スタックの巻き戻しが発生します。プログラムが一致するcatchブロックを検索する際、すべてのローカルオブジェクトに対してデストラクタが呼び出され、適切なクリーンアップが保証されます。

標準例外クラスを使用するには、<stdexcept>をインクルードしてください。

自分で試してみよう

#include <iostream>
#include <string>
#include "Calculator.h"

using namespace std;

int main() {
    // 入力を読み込む
    string operation;
    double num1, num2;
    
    cin >> operation;
    cin >> num1;
    cin >> num2;
    
    // Calculator オブジェクトを作成する
    Calculator calc;
    
    // TODO: try-catch ブロックを使用して演算を実行する
    // - operation が "divide" の場合、calc.divide(num1, num2) を呼び出す
    // - operation が "sqrt" の場合、calc.safeSqrt(num1) を呼び出す
    // - 成功した場合、"Result: " に続けて結果を表示する
    // - 例外をキャッチし、"Error: " に続けて例外メッセージを表示する (what() を使用)
    // - try-catch の後、常に "Calculation complete" を表示する
    
    
    return 0;
}
quiz icon腕試し

このレッスンには短いクイズがあります。レッスンを始めて解答し、進捗を記録しましょう。

オブジェクト指向プログラミングのすべてのレッスン