Menu
Coddy logo textTech

カスタム例外階層

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

標準ライブラリには std::runtime_error のような便利な例外クラスが用意されていますが、実際のアプリケーションでは、カスタム例外階層を作成すると役立つことがよくあります。標準例外を継承する独自の例外クラスを定義することで、追加のコンテキストを持ち、より正確なエラー処理を可能にするドメイン固有のエラー型を作成できます。

カスタム例外クラスは通常、std::exceptionまたはその派生クラスのいずれかを継承し、what()メソッドをオーバーライドします。

#include <exception>
#include <string>

class DatabaseException : public std::exception {
    std::string message;
public:
    DatabaseException(const std::string& msg) : message(msg) {}
    const char* what() const noexcept override { return message.c_str(); }
};

class ConnectionException : public DatabaseException {
public:
    ConnectionException(const std::string& host) 
        : DatabaseException("Failed to connect to: " + host) {}
};

class QueryException : public DatabaseException {
public:
    QueryException(const std::string& query) 
        : DatabaseException("Invalid query: " + query) {}
};

この階層により、catch ブロックは例外をさまざまな具体性のレベルで処理できます。

try {
    // データベース操作...
    throw ConnectionException("localhost");
} catch (const ConnectionException& e) {
    std::cout << "Connection issue: " << e.what() << "\n";
} catch (const DatabaseException& e) {
    std::cout << "Database error: " << e.what() << "\n";
} catch (const std::exception& e) {
    std::cout << "General error: " << e.what() << "\n";
}

noexcept 指定子を what() に付けることは重要です。これは、そのメソッドが例外を送出しないことを保証するものであり、base クラスのインターフェースによって要求されています。C++ は最初に一致する handler を使用するため、catch ブロックは最も具体的なものから最も一般的なものの順に並べてください。

challenge icon

チャレンジ

簡単

異なる種類のファイル関連エラーを処理する、カスタム例外階層を備えたファイル処理システムを構築しましょう。base 例外クラスを継承するドメイン固有の例外を作成し、さまざまな詳細度のレベルで正確なエラー処理を行えるようにします。

コードを3つのファイルに分けて整理します。

  • FileExceptions.h:ファイル操作用のカスタム例外階層を定義します。

    std::exceptionを継承するbase FileException クラスを作成します。エラーメッセージを保持し、noexcept指定子を付けてwhat()メソッドをオーバーライドします。

    次に、2つの派生例外クラスを作成します。

    • FileNotFoundException:filenameを受け取り、メッセージFile not found: [filename]を作成します。
    • PermissionDeniedException:filenameを受け取り、メッセージPermission denied: [filename]を作成します。

    各派生クラスは、適切なメッセージを指定して親のconstructorを呼び出します。

  • FileProcessor.h:ファイル操作をシミュレートし、カスタム例外をスローするFileProcessorクラスを定義します。

    FileProcessorには、ファイルを開く処理をシミュレートするメソッドopenFile(const std::string& filename)を実装します。

    • filenameにmissingが含まれている場合、FileNotFoundExceptionをスローします。
    • filenameにlockedが含まれている場合、PermissionDeniedExceptionをスローします。
    • それ以外の場合は、文字列Successfully opened: [filename]を返します。
  • main.cpp:入力から1つのfilenameを読み取ります。

    FileProcessorを作成し、ファイルを開こうとします。最も具体的なものから最も一般的なものの順にhandlersを並べたtry-catchブロックを使用します。

    1. FileNotFoundExceptionをCatchし、File Error: [message]を出力します。
    2. PermissionDeniedExceptionをCatchし、Access Error: [message]を出力します。
    3. FileExceptionをCatchし、General File Error: [message]を出力します。

    例外がスローされなかった場合は、openFile()が返した成功メッセージを出力します。

たとえば、入力がreport.txtの場合:

Successfully opened: report.txt

入力がmissing_data.csvの場合:

File Error: File not found: missing_data.csv

入力がlocked_config.iniの場合:

Access Error: Permission denied: locked_config.ini

このchallengeでは、カスタム例外階層によって適切な詳細度のレベルでエラーを処理できることを示します。正確な処理が必要な場合は特定の例外をCatchし、すべてのファイルエラーを一律に処理したい場合はbase例外をCatchできます。

自分で試してみよう

#include <iostream>
#include <string>
#include "FileExceptions.h"
#include "FileProcessor.h"

using namespace std;

int main() {
    string filename;
    cin >> filename;
    
    FileProcessor processor;
    
    // TODO: 例外を処理するために try-catch ブロックを使用する
    // ハンドラを最も具体的なものから最も一般的なものへ順序付けする:
    // 1. FileNotFoundException をキャッチする - 出力: "File Error: [message]"
    // 2. PermissionDeniedException をキャッチする - 出力: "Access Error: [message]"
    // 3. FileException をキャッチする - 出力: "General File Error: [message]"
    // 例外がない場合、openFile() からの成功メッセージを出力する
    
    try {
        // TODO: processor.openFile() を呼び出し、結果を処理する
        
    }
    // TODO: 最も具体的なものから最も一般的なものの順に catch ブロックを追加する
    
    return 0;
}
quiz icon腕試し

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

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

自分で練習してみよう: C++オンラインコンパイラ