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";
}

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

challenge icon

チャレンジ

簡単

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

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

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

    std::exceptionを継承する基本クラス FileException を作成します。このクラスはエラーメッセージを保持し、noexcept 指定子を使用して what() メソッドをオーバーライドする必要があります。

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

    • FileNotFoundException — ファイル名を受け取り、File not found: [filename] というメッセージを作成します。
    • PermissionDeniedException — ファイル名を受け取り、Permission denied: [filename] というメッセージを作成します。

    各派生クラスは、適切なメッセージを使用して親クラスのコンストラクタを呼び出す必要があります。

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

    FileProcessor には、ファイルのオープンをシミュレートする openFile(const std::string& filename) メソッドが必要です:

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

    FileProcessor を作成し、ファイルのオープンを試みます。最も具体的なものから最も一般的なものの順にハンドラを配置した try-catch ブロックを使用します:

    1. FileNotFoundException をキャッチし、File Error: [message] と出力します。
    2. PermissionDeniedException をキャッチし、Access Error: [message] と出力します。
    3. FileException をキャッチし、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

このチャレンジでは、カスタム例外階層によって、適切な詳細レベルでエラーを処理する方法を示します。正確な処理が必要な場合は特定の例外をキャッチし、すべてのファイルエラーを一律に処理したい場合は基本例外をキャッチします。

チートシート

カスタム例外クラスは 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) {}
};

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";
}

what() に指定された noexcept 指定子は、メソッドが例外をスローしないことを保証します。これは基底クラスのインターフェースで要求されています。

自分で試してみよう

#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腕試し

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

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