C++の例外を使ったエラーハンドリング設計パターン完全ガイド

C++でのエラーハンドリングは、プログラムの信頼性と保守性を高める重要な要素です。本記事では、C++の例外処理の基本から高度な設計パターンまでを網羅的に解説します。これにより、エラーハンドリングを適切に設計し、バグの少ない堅牢なアプリケーションを開発するための知識とスキルを習得できます。

目次

エラーハンドリングの基本概念

エラーハンドリングは、プログラムが予期しない状況やエラーに対処するための重要な技術です。C++では、例外処理機構を用いてエラーを効率的に管理できます。エラーハンドリングの基本概念を理解することで、プログラムの安定性と信頼性を向上させることができます。

エラーの種類と対処方法

プログラム中で発生するエラーは、主に以下の3種類に分類されます:

1. 構文エラー

コンパイル時に検出されるエラーで、プログラムの文法が正しくない場合に発生します。これらはコンパイラが指摘してくれるため、修正が比較的容易です。

2. ロジックエラー

プログラムの論理が誤っている場合に発生するエラーで、意図しない動作や結果をもたらします。デバッグを通じて特定し、修正する必要があります。

3. ランタイムエラー

プログラム実行中に発生するエラーで、メモリ不足やゼロ除算などが原因です。これらのエラーは、例外処理を使って対処するのが一般的です。

エラーハンドリングの重要性

エラーハンドリングは、以下の理由で重要です:

1. プログラムの安定性向上

エラーハンドリングを適切に行うことで、プログラムが予期しない状況に陥った場合でも適切に対処し、クラッシュを防ぎます。

2. 保守性の向上

エラーが発生した場合の処理が明確になるため、コードの保守が容易になります。エラーの原因を特定しやすくなり、修正が迅速に行えます。

3. ユーザーエクスペリエンスの向上

ユーザーにとっては、エラーが発生しても適切に処理され、適切なメッセージが表示されることで、信頼性の高いソフトウェアと感じられます。

このように、エラーハンドリングの基本概念を理解し、適切に実装することは、質の高いソフトウェアを開発するために不可欠です。次のセクションでは、C++における具体的なエラーハンドリングの手法について詳しく見ていきます。

例外クラスの設計

C++でのエラーハンドリングにおいて、例外クラスの設計は非常に重要です。適切な例外クラスを設計することで、エラーの種類や原因を明確にし、エラーハンドリングをより効率的に行えます。

標準例外クラスの利用

C++標準ライブラリには、様々な例外クラスが用意されています。以下に代表的な例を示します:

1. std::exception

全ての例外クラスの基底クラスであり、標準的な例外を表します。catchブロックでstd::exceptionをキャッチすることで、全ての標準例外を処理できます。

try {
    // 例外を投げる可能性のあるコード
} catch (const std::exception& e) {
    std::cerr << "例外が発生: " << e.what() << std::endl;
}

2. std::runtime_error

実行時エラーを表すクラスで、プログラムの実行中に発生するエラーを捕捉します。

3. std::logic_error

論理エラーを表すクラスで、プログラムの論理に問題がある場合に使用されます。

カスタム例外クラスの設計

独自の例外クラスを設計することで、特定のエラー条件に対応した柔軟なエラーハンドリングが可能になります。以下にカスタム例外クラスの設計例を示します。

#include <exception>
#include <string>

class CustomException : public std::exception {
private:
    std::string message;

public:
    explicit CustomException(const std::string& msg) : message(msg) {}

    const char* what() const noexcept override {
        return message.c_str();
    }
};

カスタム例外クラスの使用例

カスタム例外クラスを使用することで、エラーメッセージをより具体的に伝えることができます。

try {
    throw CustomException("カスタム例外が発生しました");
} catch (const CustomException& e) {
    std::cerr << "捕捉された例外: " << e.what() << std::endl;
}

このように、標準の例外クラスとカスタム例外クラスを使い分けることで、エラーの種類に応じた柔軟なエラーハンドリングが実現できます。次のセクションでは、具体的な例外のスローとキャッチの方法について解説します。

例外のスローとキャッチの方法

C++では、例外をスローしてキャッチすることで、エラーが発生した際に適切に対処できます。ここでは、具体的な例外のスローとキャッチの方法について説明します。

例外をスローする方法

例外をスローするには、throwキーワードを使用します。スローされる例外は、基本的にクラスのインスタンスです。

#include <iostream>
#include <stdexcept>

void functionThatThrows() {
    throw std::runtime_error("ランタイムエラーが発生しました");
}

int main() {
    try {
        functionThatThrows();
    } catch (const std::runtime_error& e) {
        std::cerr << "捕捉された例外: " << e.what() << std::endl;
    }
    return 0;
}

この例では、functionThatThrows関数内でstd::runtime_error例外をスローし、main関数でその例外をキャッチしています。

複数の例外をキャッチする方法

複数の種類の例外をキャッチするために、複数のcatchブロックを使用することができます。

#include <iostream>
#include <stdexcept>

void functionThatThrows() {
    throw std::runtime_error("ランタイムエラーが発生しました");
}

int main() {
    try {
        functionThatThrows();
    } catch (const std::runtime_error& e) {
        std::cerr << "捕捉されたランタイムエラー: " << e.what() << std::endl;
    } catch (const std::exception& e) {
        std::cerr << "捕捉された例外: " << e.what() << std::endl;
    }
    return 0;
}

この例では、まずstd::runtime_errorをキャッチし、次に一般的なstd::exceptionをキャッチしています。特定の例外を先にキャッチし、より一般的な例外を後にキャッチするのが一般的な方法です。

例外の再スロー

キャッチした例外を再スローすることも可能です。これにより、エラー情報を上位の関数に伝えることができます。

#include <iostream>
#include <stdexcept>

void functionThatThrows() {
    throw std::runtime_error("ランタイムエラーが発生しました");
}

void wrapperFunction() {
    try {
        functionThatThrows();
    } catch (const std::runtime_error& e) {
        std::cerr << "例外を再スローします: " << e.what() << std::endl;
        throw;  // 例外を再スロー
    }
}

int main() {
    try {
        wrapperFunction();
    } catch (const std::exception& e) {
        std::cerr << "最終的に捕捉された例外: " << e.what() << std::endl;
    }
    return 0;
}

この例では、wrapperFunction内で例外をキャッチし、メッセージを出力した後に例外を再スローしています。main関数で最終的にその例外をキャッチしています。

これらの方法を組み合わせることで、C++における柔軟で強力なエラーハンドリングが実現できます。次のセクションでは、C++標準ライブラリが提供する例外クラスについて詳しく説明します。

標準ライブラリの例外クラス

C++標準ライブラリには、さまざまな例外クラスが用意されています。これらのクラスを活用することで、一般的なエラー状況に対処しやすくなります。ここでは、代表的な例外クラスとその使用方法について説明します。

std::exception

std::exceptionは、C++標準ライブラリにおける全ての例外クラスの基底クラスです。what()メソッドをオーバーライドして、エラーメッセージを提供します。

#include <iostream>
#include <exception>

void functionThatThrows() {
    throw std::exception();
}

int main() {
    try {
        functionThatThrows();
    } catch (const std::exception& e) {
        std::cerr << "捕捉された例外: " << e.what() << std::endl;
    }
    return 0;
}

std::runtime_error

std::runtime_errorは、実行時に発生するエラーを表します。std::exceptionを継承しており、通常の実行時エラーを表現するのに適しています。

#include <iostream>
#include <stdexcept>

void functionThatThrows() {
    throw std::runtime_error("ランタイムエラーが発生しました");
}

int main() {
    try {
        functionThatThrows();
    } catch (const std::runtime_error& e) {
        std::cerr << "捕捉されたランタイムエラー: " << e.what() << std::endl;
    }
    return 0;
}

std::logic_error

std::logic_errorは、プログラムのロジックに関連するエラーを表します。このクラスは、プログラムの設計ミスや不正な引数などを扱う際に便利です。

#include <iostream>
#include <stdexcept>

void functionThatThrows() {
    throw std::logic_error("論理エラーが発生しました");
}

int main() {
    try {
        functionThatThrows();
    } catch (const std::logic_error& e) {
        std::cerr << "捕捉された論理エラー: " << e.what() << std::endl;
    }
    return 0;
}

その他の標準例外クラス

C++標準ライブラリには他にも多くの例外クラスがあります。例えば、以下のようなものがあります:

std::out_of_range

範囲外のアクセスが行われた場合にスローされます。

std::invalid_argument

無効な引数が渡された場合にスローされます。

std::bad_alloc

メモリの動的確保に失敗した場合にスローされます。

これらの例外クラスを適切に使用することで、エラーの種類に応じた具体的な対処が可能になります。次のセクションでは、カスタム例外クラスの実装方法について説明します。

カスタム例外クラスの実装

C++では、独自のカスタム例外クラスを実装することで、特定のエラー状況に対してより具体的なエラーハンドリングが可能になります。ここでは、カスタム例外クラスの設計と実装方法について説明します。

カスタム例外クラスの基本設計

カスタム例外クラスは、通常std::exceptionまたはその派生クラスを継承します。what()メソッドをオーバーライドして、エラーメッセージを提供します。

#include <exception>
#include <string>

class CustomException : public std::exception {
private:
    std::string message;

public:
    explicit CustomException(const std::string& msg) : message(msg) {}

    const char* what() const noexcept override {
        return message.c_str();
    }
};

カスタム例外クラスの使用例

カスタム例外クラスを使うことで、特定のエラー条件に対応したメッセージを提供できます。

#include <iostream>
#include <exception>

class CustomException : public std::exception {
private:
    std::string message;

public:
    explicit CustomException(const std::string& msg) : message(msg) {}

    const char* what() const noexcept override {
        return message.c_str();
    }
};

void functionThatThrows() {
    throw CustomException("カスタム例外が発生しました");
}

int main() {
    try {
        functionThatThrows();
    } catch (const CustomException& e) {
        std::cerr << "捕捉されたカスタム例外: " << e.what() << std::endl;
    }
    return 0;
}

複数のカスタム例外クラス

必要に応じて、複数のカスタム例外クラスを設計することで、より具体的なエラーハンドリングが可能になります。

#include <iostream>
#include <exception>

class FileNotFoundException : public std::exception {
private:
    std::string message;

public:
    explicit FileNotFoundException(const std::string& msg) : message(msg) {}

    const char* what() const noexcept override {
        return message.c_str();
    }
};

class NetworkException : public std::exception {
private:
    std::string message;

public:
    explicit NetworkException(const std::string& msg) : message(msg) {}

    const char* what() const noexcept override {
        return message.c_str();
    }
};

void functionThatThrows() {
    throw FileNotFoundException("ファイルが見つかりません");
}

int main() {
    try {
        functionThatThrows();
    } catch (const FileNotFoundException& e) {
        std::cerr << "捕捉されたファイル例外: " << e.what() << std::endl;
    } catch (const NetworkException& e) {
        std::cerr << "捕捉されたネットワーク例外: " << e.what() << std::endl;
    } catch (const std::exception& e) {
        std::cerr << "捕捉された一般的な例外: " << e.what() << std::endl;
    }
    return 0;
}

このように、カスタム例外クラスを使うことで、エラーの種類ごとに適切なエラーメッセージを提供し、エラーハンドリングをより細かく制御できます。次のセクションでは、例外安全性の確保について詳しく説明します。

例外安全性の確保

例外安全性は、例外が発生した場合でもプログラムが安定して動作し、リソースリークを防ぐための重要な設計原則です。ここでは、例外安全性を確保するための設計パターンと手法について説明します。

RAII(Resource Acquisition Is Initialization)

RAIIは、リソース管理のための重要な設計パターンです。オブジェクトの寿命に基づいてリソースを管理し、例外が発生してもリソースリークを防ぎます。

#include <iostream>
#include <memory>

class Resource {
public:
    Resource() { std::cout << "Resource acquired\n"; }
    ~Resource() { std::cout << "Resource released\n"; }
};

void functionThatThrows() {
    std::unique_ptr<Resource> res(new Resource());
    throw std::runtime_error("例外が発生しました");
}

int main() {
    try {
        functionThatThrows();
    } catch (const std::exception& e) {
        std::cerr << "捕捉された例外: " << e.what() << std::endl;
    }
    return 0;
}

この例では、std::unique_ptrを使ってリソースを管理し、例外が発生してもリソースが適切に解放されるようにしています。

strong exception safety(強い例外保証)

強い例外保証とは、例外が発生した場合でもプログラムの状態が変更されないことを保証することです。操作が失敗しても、プログラムの状態が以前の状態に戻るようにします。

#include <iostream>
#include <vector>
#include <stdexcept>

void appendToVector(std::vector<int>& vec, int value) {
    std::vector<int> temp = vec;
    temp.push_back(value); // 操作を一時的なオブジェクトで行う
    vec.swap(temp);        // 成功したら元のオブジェクトに適用
}

int main() {
    std::vector<int> vec = {1, 2, 3};
    try {
        appendToVector(vec, 4);
        appendToVector(vec, 5);
    } catch (const std::exception& e) {
        std::cerr << "捕捉された例外: " << e.what() << std::endl;
    }

    for (int n : vec) {
        std::cout << n << " ";
    }

    return 0;
}

この例では、変更を一時的なオブジェクトに対して行い、成功した場合にのみ元のオブジェクトに変更を適用しています。

基本的な例外保証

基本的な例外保証は、例外が発生した場合でもプログラムが有効な状態にあることを保証します。全てのリソースが適切に解放され、プログラムが不整合な状態にならないようにします。

#include <iostream>
#include <vector>

class Widget {
public:
    Widget() { std::cout << "Widget created\n"; }
    ~Widget() { std::cout << "Widget destroyed\n"; }
};

void functionThatThrows() {
    std::vector<Widget> widgets;
    widgets.push_back(Widget());
    throw std::runtime_error("例外が発生しました");
}

int main() {
    try {
        functionThatThrows();
    } catch (const std::exception& e) {
        std::cerr << "捕捉された例外: " << e.what() << std::endl;
    }
    return 0;
}

この例では、例外が発生しても、widgetsに格納されたオブジェクトは適切に解放されます。

例外安全性を確保することは、堅牢で信頼性の高いプログラムを構築するために非常に重要です。次のセクションでは、例外を使用する設計パターンについて詳しく説明します。

例外を使用する設計パターン

例外を使用する設計パターンは、エラーハンドリングを効率的かつ効果的に行うための指針となります。ここでは、代表的な設計パターンとその利点・欠点について解説します。

RAII(Resource Acquisition Is Initialization)パターン

RAIIは、オブジェクトの寿命を利用してリソースを管理する設計パターンです。リソースの獲得と解放をコンストラクタとデストラクタで行うため、例外が発生してもリソースリークを防ぐことができます。

#include <iostream>
#include <fstream>

class File {
public:
    File(const std::string& filename) : file(filename) {
        if (!file.is_open()) {
            throw std::runtime_error("ファイルを開けません");
        }
    }

    ~File() {
        if (file.is_open()) {
            file.close();
        }
    }

private:
    std::fstream file;
};

int main() {
    try {
        File file("example.txt");
        // ファイル操作
    } catch (const std::exception& e) {
        std::cerr << "例外が発生: " << e.what() << std::endl;
    }
    return 0;
}

RAIIの利点は、例外安全性が高く、リソースリークを防ぐことができる点です。欠点としては、設計が複雑になる場合があることです。

関数レベルのエラーハンドリング

関数レベルのエラーハンドリングは、関数ごとに例外をキャッチして処理する方法です。これにより、エラーの発生箇所を特定しやすくなります。

#include <iostream>
#include <stdexcept>

void functionThatThrows() {
    throw std::runtime_error("エラーが発生しました");
}

void handleErrors() {
    try {
        functionThatThrows();
    } catch (const std::runtime_error& e) {
        std::cerr << "ランタイムエラー: " << e.what() << std::endl;
    } catch (const std::exception& e) {
        std::cerr << "一般的な例外: " << e.what() << std::endl;
    }
}

int main() {
    handleErrors();
    return 0;
}

このパターンの利点は、エラー処理が関数単位で行われるため、コードの可読性が高いことです。欠点としては、例外処理のコードが分散しやすく、全体のエラーハンドリングが複雑になる可能性があることです。

エラーコードとの併用

例外とエラーコードを併用することで、柔軟なエラーハンドリングが可能になります。例外は重大なエラーに対して使用し、エラーコードは軽微なエラーに対して使用します。

#include <iostream>
#include <stdexcept>

int functionWithErrorCode() {
    // 何らかのエラーが発生
    return -1;
}

void functionThatThrows() {
    throw std::runtime_error("重大なエラーが発生しました");
}

int main() {
    int errorCode = functionWithErrorCode();
    if (errorCode != 0) {
        std::cerr << "エラーコード: " << errorCode << std::endl;
    }

    try {
        functionThatThrows();
    } catch (const std::runtime_error& e) {
        std::cerr << "例外: " << e.what() << std::endl;
    }

    return 0;
}

このパターンの利点は、エラーの種類に応じた柔軟なハンドリングが可能になる点です。欠点としては、エラー処理の方法が複数になるため、コードが複雑化する可能性があります。

例外の伝播と制御

例外の伝播と制御は、例外が関数間をまたいで伝播する際の設計パターンです。例外を上位の関数でキャッチして処理することで、エラー処理を一元化できます。

#include <iostream>
#include <stdexcept>

void innerFunction() {
    throw std::runtime_error("内側の関数でエラーが発生しました");
}

void outerFunction() {
    try {
        innerFunction();
    } catch (const std::runtime_error& e) {
        std::cerr << "内側のエラーを再スロー: " << e.what() << std::endl;
        throw;  // 例外を再スロー
    }
}

int main() {
    try {
        outerFunction();
    } catch (const std::exception& e) {
        std::cerr << "最終的に捕捉された例外: " << e.what() << std::endl;
    }
    return 0;
}

このパターンの利点は、エラー処理を上位の関数に集約できるため、エラーハンドリングが一元化されることです。欠点としては、例外の伝播が不適切に行われると、エラーの原因を特定しにくくなることです。

これらの設計パターンを適切に活用することで、C++における効果的なエラーハンドリングが実現できます。次のセクションでは、例外の伝播と制御についてさらに詳しく説明します。

例外の伝播と制御

例外の伝播と制御は、関数間で発生する例外を適切に管理し、エラー処理を効率的に行うための重要な手法です。ここでは、例外が伝播するメカニズムと、その制御方法について詳しく説明します。

例外の伝播

例外は、スローされた関数からキャッチされるまでスタックフレームを巻き戻しながら伝播します。例外を適切な場所でキャッチしないと、プログラムはクラッシュします。

#include <iostream>
#include <stdexcept>

void innerFunction() {
    throw std::runtime_error("内側の関数でエラーが発生しました");
}

void middleFunction() {
    innerFunction(); // 例外をスロー
}

void outerFunction() {
    middleFunction(); // 例外を伝播
}

int main() {
    try {
        outerFunction(); // 例外をキャッチ
    } catch (const std::runtime_error& e) {
        std::cerr << "捕捉された例外: " << e.what() << std::endl;
    }
    return 0;
}

この例では、innerFunctionでスローされた例外がmiddleFunctionouterFunctionを経由してmain関数でキャッチされています。

例外の再スロー

例外をキャッチした後に再スローすることで、上位の関数に例外を伝播させることができます。これにより、エラーハンドリングを一元化することが可能です。

#include <iostream>
#include <stdexcept>

void innerFunction() {
    throw std::runtime_error("内側の関数でエラーが発生しました");
}

void middleFunction() {
    try {
        innerFunction();
    } catch (const std::runtime_error& e) {
        std::cerr << "middleFunctionで例外をキャッチ: " << e.what() << std::endl;
        throw; // 例外を再スロー
    }
}

void outerFunction() {
    try {
        middleFunction();
    } catch (const std::exception& e) {
        std::cerr << "outerFunctionで最終的に捕捉された例外: " << e.what() << std::endl;
    }
}

int main() {
    outerFunction();
    return 0;
}

この例では、middleFunctionでキャッチされた例外が再スローされ、最終的にouterFunctionでキャッチされています。

例外の変換

キャッチした例外を別の例外に変換して再スローすることも可能です。これにより、異なるエラーレベルやコンテキストに応じたエラーハンドリングができます。

#include <iostream>
#include <stdexcept>

void innerFunction() {
    throw std::runtime_error("内側の関数でエラーが発生しました");
}

void middleFunction() {
    try {
        innerFunction();
    } catch (const std::runtime_error& e) {
        std::cerr << "middleFunctionで例外をキャッチ: " << e.what() << std::endl;
        throw std::logic_error("middleFunctionで新たな論理エラーとして再スロー");
    }
}

void outerFunction() {
    try {
        middleFunction();
    } catch (const std::logic_error& e) {
        std::cerr << "outerFunctionで捕捉された論理エラー: " << e.what() << std::endl;
    }
}

int main() {
    outerFunction();
    return 0;
}

この例では、middleFunctionstd::runtime_errorがキャッチされ、std::logic_errorに変換して再スローされています。

例外の階層化

例外の階層化は、異なる種類の例外を階層構造にまとめて処理する手法です。これにより、特定の例外やその基底クラスをキャッチすることで、柔軟なエラーハンドリングが可能になります。

#include <iostream>
#include <stdexcept>

class BaseException : public std::exception {
public:
    const char* what() const noexcept override {
        return "BaseExceptionが発生しました";
    }
};

class DerivedException : public BaseException {
public:
    const char* what() const noexcept override {
        return "DerivedExceptionが発生しました";
    }
};

void functionThatThrows() {
    throw DerivedException();
}

int main() {
    try {
        functionThatThrows();
    } catch (const DerivedException& e) {
        std::cerr << "捕捉された例外: " << e.what() << std::endl;
    } catch (const BaseException& e) {
        std::cerr << "基底例外: " << e.what() << std::endl;
    }
    return 0;
}

この例では、DerivedExceptionをスローし、特定の例外をキャッチすることで柔軟なエラーハンドリングを実現しています。

例外の伝播と制御を適切に行うことで、堅牢で保守性の高いプログラムを構築できます。次のセクションでは、エラーハンドリングの応用例について説明します。

エラーハンドリングの応用例

エラーハンドリングの技術は、実際のプロジェクトで非常に役立ちます。ここでは、いくつかの応用例を紹介し、エラーハンドリングがどのように活用されているかを説明します。

ファイル操作でのエラーハンドリング

ファイル操作は、エラーが発生しやすい領域です。ファイルが存在しない、読み取り/書き込み権限がないなど、さまざまなエラーが考えられます。例外を使用してこれらのエラーを適切に処理します。

#include <iostream>
#include <fstream>
#include <stdexcept>

void readFile(const std::string& filename) {
    std::ifstream file(filename);
    if (!file) {
        throw std::runtime_error("ファイルを開けません: " + filename);
    }

    std::string line;
    while (std::getline(file, line)) {
        std::cout << line << std::endl;
    }

    if (file.bad()) {
        throw std::runtime_error("ファイル読み取り中にエラーが発生しました: " + filename);
    }
}

int main() {
    try {
        readFile("example.txt");
    } catch (const std::exception& e) {
        std::cerr << "エラー: " << e.what() << std::endl;
    }
    return 0;
}

この例では、ファイルを開く際と読み取る際のエラーを例外で処理しています。これにより、エラーが発生した場合でもプログラムが適切に対処できます。

ネットワーク通信でのエラーハンドリング

ネットワーク通信では、接続エラーやタイムアウトなどが発生する可能性があります。例外を使用してこれらのエラーを処理し、再試行やユーザー通知などの対応を行います。

#include <iostream>
#include <stdexcept>

class NetworkException : public std::runtime_error {
public:
    explicit NetworkException(const std::string& message) : std::runtime_error(message) {}
};

void connectToServer() {
    // 偽のエラーをスロー
    throw NetworkException("サーバーに接続できません");
}

int main() {
    try {
        connectToServer();
    } catch (const NetworkException& e) {
        std::cerr << "ネットワークエラー: " << e.what() << std::endl;
        // 再試行や他の処理
    }
    return 0;
}

この例では、ネットワークエラーをNetworkExceptionクラスで表現し、接続失敗時に例外をスローして処理しています。

データベース操作でのエラーハンドリング

データベース操作では、接続エラーやクエリエラーなどが発生する可能性があります。これらのエラーを例外で処理し、データ整合性を保ちます。

#include <iostream>
#include <stdexcept>

class DatabaseException : public std::runtime_error {
public:
    explicit DatabaseException(const std::string& message) : std::runtime_error(message) {}
};

void executeQuery() {
    // 偽のエラーをスロー
    throw DatabaseException("データベースクエリに失敗しました");
}

int main() {
    try {
        executeQuery();
    } catch (const DatabaseException& e) {
        std::cerr << "データベースエラー: " << e.what() << std::endl;
        // ロールバックや再試行
    }
    return 0;
}

この例では、データベースクエリエラーをDatabaseExceptionクラスで表現し、クエリ失敗時に例外をスローして処理しています。

ユーザー入力の検証でのエラーハンドリング

ユーザー入力の検証時には、不正なデータが入力される可能性があります。例外を使用して入力エラーを検出し、適切に対処します。

#include <iostream>
#include <stdexcept>

class InputValidationException : public std::runtime_error {
public:
    explicit InputValidationException(const std::string& message) : std::runtime_error(message) {}
};

void validateInput(int value) {
    if (value < 0) {
        throw InputValidationException("入力値が無効です: " + std::to_string(value));
    }
}

int main() {
    int userInput = -5; // 偽のユーザー入力
    try {
        validateInput(userInput);
    } catch (const InputValidationException& e) {
        std::cerr << "入力エラー: " << e.what() << std::endl;
        // ユーザーへの通知や再入力の要求
    }
    return 0;
}

この例では、入力検証エラーをInputValidationExceptionクラスで表現し、不正な入力が検出された場合に例外をスローして処理しています。

これらの応用例を通じて、実際のプロジェクトで例外を使用したエラーハンドリングの重要性と効果を理解できます。次のセクションでは、演習問題を提供し、読者が実際に手を動かして理解を深められるようにします。

演習問題

ここでは、例外を使ったエラーハンドリングに関するいくつかの演習問題を提供します。これらの問題に取り組むことで、実際に例外処理を実装し、理解を深めることができます。

演習問題1: ファイル読み取りエラーの処理

次のコードは、ファイルからデータを読み取るプログラムです。ファイルが存在しない場合や読み取り中にエラーが発生した場合に例外をスローし、適切に処理してください。

#include <iostream>
#include <fstream>
#include <stdexcept>

void readFile(const std::string& filename) {
    std::ifstream file(filename);
    if (!file) {
        // TODO: ファイルを開けない場合の例外をスロー
    }

    std::string line;
    while (std::getline(file, line)) {
        std::cout << line << std::endl;
    }

    if (file.bad()) {
        // TODO: ファイル読み取り中のエラーの例外をスロー
    }
}

int main() {
    try {
        readFile("example.txt");
    } catch (const std::exception& e) {
        std::cerr << "エラー: " << e.what() << std::endl;
    }
    return 0;
}

演習問題2: カスタム例外クラスの実装

次のコードには、カスタム例外クラスが不足しています。NetworkExceptionクラスを実装し、ネットワーク接続エラーを適切に処理してください。

#include <iostream>
#include <stdexcept>

// TODO: NetworkExceptionクラスを実装してください

void connectToServer() {
    // 偽のエラーをスロー
    throw NetworkException("サーバーに接続できません");
}

int main() {
    try {
        connectToServer();
    } catch (const NetworkException& e) {
        std::cerr << "ネットワークエラー: " << e.what() << std::endl;
        // 再試行や他の処理
    }
    return 0;
}

演習問題3: 入力検証エラーの処理

次のコードには、入力検証エラーの処理が不足しています。InputValidationExceptionクラスを実装し、ユーザー入力が無効な場合に例外をスローしてください。

#include <iostream>
#include <stdexcept>

// TODO: InputValidationExceptionクラスを実装してください

void validateInput(int value) {
    if (value < 0) {
        // TODO: 入力値が無効な場合の例外をスロー
    }
}

int main() {
    int userInput = -5; // 偽のユーザー入力
    try {
        validateInput(userInput);
    } catch (const InputValidationException& e) {
        std::cerr << "入力エラー: " << e.what() << std::endl;
        // ユーザーへの通知や再入力の要求
    }
    return 0;
}

演習問題4: 例外の再スロー

次のコードは、複数の関数を通じて例外を再スローするプログラムです。適切な場所で例外を再スローし、エラーハンドリングを行ってください。

#include <iostream>
#include <stdexcept>

void innerFunction() {
    // TODO: 例外をスロー
}

void middleFunction() {
    try {
        innerFunction();
    } catch (const std::runtime_error& e) {
        std::cerr << "middleFunctionで例外をキャッチ: " << e.what() << std::endl;
        // TODO: 例外を再スロー
    }
}

void outerFunction() {
    try {
        middleFunction();
    } catch (const std::exception& e) {
        std::cerr << "outerFunctionで最終的に捕捉された例外: " << e.what() << std::endl;
    }
}

int main() {
    outerFunction();
    return 0;
}

これらの演習問題に取り組むことで、例外処理の基本から応用までを実践的に学ぶことができます。次のセクションでは、この記事のまとめを行います。

まとめ

本記事では、C++における例外を使ったエラーハンドリングの基本概念から応用までを解説しました。以下に、主要なポイントをまとめます。

  1. エラーハンドリングの基本概念
    エラーハンドリングは、プログラムの信頼性と安定性を確保するために重要です。エラーの種類と対処方法を理解することで、効率的なエラーハンドリングが可能になります。
  2. 例外クラスの設計
    標準の例外クラスを活用し、必要に応じてカスタム例外クラスを実装することで、特定のエラー状況に対応した柔軟なエラーハンドリングが実現できます。
  3. 例外のスローとキャッチの方法
    例外をスローしてキャッチする方法を理解することで、エラーが発生した際に適切に対処できます。また、複数の例外をキャッチする方法や例外の再スローも重要な技術です。
  4. 標準ライブラリの例外クラス
    C++標準ライブラリが提供する例外クラスを活用することで、一般的なエラー状況に対処しやすくなります。
  5. カスタム例外クラスの実装
    独自のカスタム例外クラスを設計することで、エラーハンドリングをより具体的に、詳細に制御できます。
  6. 例外安全性の確保
    RAIIや強い例外保証などの設計パターンを活用することで、例外が発生した場合でもプログラムの状態を安定に保ち、リソースリークを防ぐことができます。
  7. 例外を使用する設計パターン
    例外を使用する代表的な設計パターンを理解することで、効果的なエラーハンドリングが可能になります。
  8. 例外の伝播と制御
    例外が関数間をまたいで伝播する際の注意点と制御方法を理解することで、エラーハンドリングを一元化し、効率的に管理できます。
  9. エラーハンドリングの応用例
    実際のプロジェクトでのエラーハンドリングの応用例を学ぶことで、実践的なスキルを習得できます。
  10. 演習問題
    演習問題に取り組むことで、例外処理の技術を実践的に学び、理解を深めることができます。

これらの知識と技術を駆使して、堅牢で信頼性の高いプログラムを開発することができます。エラーハンドリングを適切に設計し、実装することで、プログラムの品質とユーザーエクスペリエンスを向上させましょう。

コメント

コメントする

目次