C++の例外クラスを使いこなす7つの方法

C++の例外クラスを使ったプログラミングのイメージC++
この記事は約16分で読めます。

 

【サイト内のコードはご自由に個人利用・商用利用いただけます】

この記事では、プログラムの基礎知識を前提に話を進めています。

説明のためのコードや、サンプルコードもありますので、もちろん初心者でも理解できるように表現してあります。

基本的な知識があればカスタムコードを使って機能追加、目的を達成できるように作ってあります。

※この記事は、一般的にプロフェッショナルの指標とされる『実務経験10,000時間以上』を凌駕する現役のプログラマチームによって監修されています。

サイト内のコードを共有する場合は、参照元として引用して下さいますと幸いです

※Japanシーモアは、常に解説内容のわかりやすさや記事の品質に注力しております。不具合、分かりにくい説明や不適切な表現、動かないコードなど気になることがございましたら、記事の品質向上の為にお問い合わせフォームにてご共有いただけますと幸いです。
(送信された情報は、プライバシーポリシーのもと、厳正に取扱い、処分させていただきます。)

はじめに

C++におけるプログラミングの世界は奥深く、特に例外クラスの扱いは初心者にとっても中級者にとっても重要なポイントです。

この記事では、C++での例外クラスの基本から応用までを、具体的なサンプルコードと共に分かりやすく解説します。

例外処理を理解し、効率的かつ堅牢なコードを書く方法を身につけることで、プログラミングスキルの向上につながります。

●例外クラスの基本的な使い方

例外クラスは、C++プログラムにおいて予期しないエラーが発生した際に、プログラムを適切に制御するために使用されます。

例外クラスを使うことで、エラー処理をより簡潔かつ効果的に行うことが可能になります。

基本的には、tryブロック内でコードを実行し、catchブロックで特定の例外を捕捉します。

○サンプルコード1:基本的な例外処理

下記のサンプルコードは、基本的な例外処理の流れを表しています。

このコードでは、tryブロック内で配列のインデックスにアクセスし、インデックスが範囲外であれば例外を投げます。

catchブロックではその例外を捕捉し、エラーメッセージを表示します。

#include <iostream>
#include <stdexcept>

int main() {
    try {
        int myArray[5] = {1, 2, 3, 4, 5};
        int index;
        std::cin >> index;

        if (index < 0 || index >= 5) {
            throw std::out_of_range("インデックスが範囲外です");
        }

        std::cout << "値: " << myArray[index] << std::endl;
    }
    catch (const std::out_of_range& e) {
        std::cout << "エラー: " << e.what() << std::endl;
    }

    return 0;
}

このコードでは、ユーザーが入力したインデックスが配列の範囲内かどうかをチェックし、範囲外であればstd::out_of_range例外を投げます。

例外が投げられると、catchブロックに制御が移り、エラーメッセージが表示されます。

これにより、プログラムが予期しない方法で終了するのを防ぎます。

○サンプルコード2:独自例外クラスの作成

C++では、独自の例外クラスを作成することも可能です。

独自の例外クラスを作成することで、より詳細なエラー情報を提供したり、例外の種類に応じた特別な処理を行うことができます。

下記のサンプルコードでは、独自の例外クラスを作成し、使用方法を表しています。

#include <iostream>
#include <exception>

// 独自の例外クラスを定義
class MyException : public std::exception {
    const char* what() const throw() {
        return "独自の例外が発生しました";
    }
};

int main() {
    try {
        throw MyException();
    }
    catch (const MyException& e) {
        std::cout << "エラー: " << e.what() << std::endl;
    }

    return 0;
}

このコードでは、MyExceptionという名前の独自例外クラスを定義し、std::exceptionクラスから派生させています。

whatメソッドをオーバーライドして、例外発生時のメッセージをカスタマイズしています。

tryブロック内でMyExceptionを投げると、catchブロックでこの独自例外を捕捉し、エラーメッセージを表示します。

●例外クラスの応用的な使い方

C++の例外クラスは基本的な使い方を超え、さまざまな応用が可能です。

応用的な使い方には、例外クラスとクラス継承の組み合わせや、例外情報を利用したエラーログの生成などが含まれます。

これらのテクニックをマスターすることで、C++プログラミングの幅が大きく広がります。

○サンプルコード3:例外処理とクラス継承

例外クラスとオブジェクト指向の特徴を組み合わせることで、より柔軟かつ詳細なエラー管理が可能になります。

下記のサンプルコードは、クラス継承を使った例外処理の一例を表しています。

#include <iostream>
#include <exception>

// 基本の例外クラス
class BaseException : public std::exception {
    const char* what() const throw() {
        return "基本の例外";
    }
};

// 派生した例外クラス
class DerivedException : public BaseException {
    const char* what() const throw() {
        return "派生した例外";
    }
};

int main() {
    try {
        throw DerivedException();
    }
    catch (const BaseException& e) {
        std::cout << "エラー: " << e.what() << std::endl;
    }

    return 0;
}

このコードでは、BaseExceptionクラスとそれを継承するDerivedExceptionクラスを定義しています。

DerivedExceptionを投げた場合でも、BaseException型で捕捉することができ、柔軟なエラー処理が行えます。

クラス継承を活用することで、エラーの種類に応じて異なる処理を実装することも容易になります。

○サンプルコード4:例外クラスを使ったエラーログの生成

例外が発生した際にエラーログを生成することは、デバッグや後の分析に非常に役立ちます。

下記のサンプルコードは、例外処理を使ってエラーログを生成する方法を表しています。

#include <iostream>
#include <fstream>
#include <exception>

// エラーログを生成する例外クラス
class LoggingException : public std::exception {
    const char* what() const throw() {
        return "エラーログ生成例外";
    }
};

void generateError() {
    throw LoggingException();
}

int main() {
    std::ofstream logFile("error_log.txt", std::ios::app);

    try {
        generateError();
    }
    catch (const LoggingException& e) {
        logFile << "エラー発生: " << e.what() << std::endl;
        std::cout << "エラーログを生成しました" << std::endl;
    }

    logFile.close();
    return 0;
}

このコードでは、LoggingExceptionクラスを用いてエラーを生成し、そのエラーを捕捉した際にエラーログファイルに記録しています。

エラーログはプログラムの実行状態を追跡し、問題の原因を特定する際に非常に有用です。

●例外クラスでよくあるエラーと対処法

C++で例外クラスを使用する際、特に注意すべき一般的なエラーケースがあります。

これらを理解し、適切に対処することで、より安定したプログラムを開発することができます。

○エラーケース1:不適切な例外捕捉

不適切な例外捕捉は、例外が予期せずプログラムを終了させる原因になることがあります。

例外を適切に捕捉し処理しない場合、プログラムは予期せぬ状態で停止してしまう可能性があります。

この問題を解決するには、すべての可能な例外を正確に捕捉し、適切に処理することが重要です。

下記のサンプルコードは、特定の例外を捕捉する方法を表しています。

#include <iostream>
#include <exception>

int main() {
    try {
        // 例外を発生させるコード
    }
    catch (const std::runtime_error& e) {
        std::cout << "実行時エラー: " << e.what() << std::endl;
    }
    catch (const std::exception& e) {
        std::cout << "一般的なエラー: " << e.what() << std::endl;
    }
    catch (...) {
        std::cout << "未知のエラーが発生しました" << std::endl;
    }

    return 0;
}

このコードでは、まずstd::runtime_error型の例外を捕捉し、次にstd::exception型の例外を捕捉し、最後に任意の型の例外を捕捉しています。

このように例外を階層的に捕捉することで、予期せぬ例外によるプログラムの終了を防ぐことができます。

○エラーケース2:リソースリークの問題

例外が投げられたときにリソースが適切に解放されない場合、リソースリークが発生することがあります。

例外が発生した場合でも、開いているファイルや確保したメモリなどのリソースが正しく解放されるようにすることが重要です。

C++11以降では、スマートポインタやRAII(Resource Acquisition Is Initialization)パターンを使用することで、この問題を解決できます。

下記のサンプルコードは、スマートポインタを使用してリソースリークを防ぐ方法を表しています。

#include <iostream>
#include <memory>

class Resource {
public:
    Resource() { std::cout << "リソースを確保しました" << std::endl; }
    ~Resource() { std::cout << "リソースを解放しました" << std::endl; }
};

void useResource() {
    std::unique_ptr<Resource> res(new Resource());
    throw std::runtime_error("例外発生");
    // リソースは自動的に解放される
}

int main() {
    try {
        useResource();
    }
    catch (const std::exception& e) {
        std::cout << "エラー: " << e.what() << std::endl;
    }

    return 0;
}

このコードでは、std::unique_ptrを使用してリソースを確保しています。

例外が発生した場合でも、スマートポインタのデストラクタが呼び出され、リソースが適切に解放されます。

●例外クラスの実践的な応用例

C++における例外クラスの使用は多岐にわたり、様々な応用例が考えられます。

具体的な応用シナリオとして、ファイル操作、ネットワーク通信、GUIアプリケーションなどが挙げられます。

これらのシナリオでは、例外クラスを活用することでエラー処理を効果的に行い、より堅牢なプログラムを実現できます。

○サンプルコード5:ファイル操作時の例外処理

ファイル操作では、ファイルが存在しない、アクセス権限がないなど、様々なエラーが発生する可能性があります。

下記のサンプルコードでは、ファイル操作時の例外処理を行っています。

#include <iostream>
#include <fstream>
#include <exception>

int main() {
    std::ifstream file;

    try {
        file.open("example.txt");
        if (!file.is_open()) {
            throw std::runtime_error("ファイルを開けませんでした");
        }

        // ファイル操作

    }
    catch (const std::exception& e) {
        std::cerr << "エラー: " << e.what() << std::endl;
    }

    file.close();
    return 0;
}

このコードでは、ifstreamオブジェクトを使用してファイルを開き、ファイルが開けなかった場合にはstd::runtime_error例外を投げています。

この例外はcatchブロックで捕捉され、エラーメッセージが表示されます。

○サンプルコード6:ネットワーク通信の例外処理

ネットワーク通信では、接続の失敗やタイムアウトなど、様々な例外が発生することがあります。

下記のサンプルコードでは、ネットワーク通信時の例外処理を行っています。

// ネットワーク通信のライブラリを想定
#include <iostream>
#include <exception>
#include "network_library.h"

int main() {
    try {
        NetworkConnection conn;
        conn.connect("example.com");
        // ネットワーク通信処理

    }
    catch (const NetworkException& e) {
        std::cerr << "ネットワークエラー: " << e.what() << std::endl;
    }
    catch (const std::exception& e) {
        std::cerr << "一般エラー: " << e.what() << std::endl;
    }

    return 0;
}

このコードでは、NetworkConnectionクラスを使用してネットワークに接続し、エラーが発生した場合にはNetworkExceptionを投げています。

この例外は専用のcatchブロックで捕捉され、エラーメッセージが表示されます。

○サンプルコード7:GUIアプリケーションの例外処理

GUIアプリケーションでは、ユーザーの入力や外部のイベントによって様々な例外が発生する可能性があります。

下記のサンプルコードでは、GUIアプリケーションにおける例外処理を表しています。

#include <iostream>
#include <exception>
#include "gui_library.h"

int main() {
    try {
        GuiApplication app;
        app.run();
    }
    catch (const GuiException& e) {
        std::cerr << "GUIエラー: " << e.what() << std::endl;
    }
    catch (const std::exception& e) {
        std::cerr << "一般エラー: " << e.what() << std::endl;
    }

    return 0;
}

このコードでは、GuiApplicationクラスを使用してGUIアプリケーションを実行し、エラーが発生した場合にはGuiExceptionを投げています。

この例外は専用のcatchブロックで捕捉され、エラーメッセージが表示されます。

●C++プログラミングの豆知識

C++を深く理解し効果的に使用するためには、いくつかの重要なコンセプトを知っておく必要があります。

ここでは、例外安全性とRAIIという二つの重要な概念について解説します。

これらの概念は、C++のプログラミングにおいて、より安全かつ効率的なコードを書くために役立ちます。

○豆知識1:例外安全性とは

例外安全性は、プログラムが例外を適切に処理し、例外が発生してもリソースが漏れたり破壊されたりしないことを保証する概念です。

C++では、例外が発生した際にも、メモリリークや不正な状態のオブジェクトを残さないようにすることが重要です。

例外安全性の高いコードを書くためには、下記の三つの保証のレベルが考えられます。

  1. 基本保証 -> 例外が発生してもプログラムが不正な状態にならないこと
  2. 強い保証 -> 例外が発生してもプログラムがその操作を行う前の状態に戻ること
  3. noexcept保証 -> その関数が例外を発生させないことを保証する

これらの保証を意識することで、より堅牢なプログラムを作成できます。

○豆知識2:RAIIと例外処理

RAII(Resource Acquisition Is Initialization)は、リソースの取得をオブジェクトの初期化に結びつけ、オブジェクトのデストラクタでリソースを解放する手法です。

C++では、スマートポインタなどRAIIに基づいたライブラリが豊富に提供されており、これらを利用することで、例外が発生してもリソースが適切に解放されるようにすることができます。

下記のサンプルコードは、RAIIを使用したメモリ管理の例です。

#include <iostream>
#include <memory>

int main() {
    try {
        std::unique_ptr<int> myInt(new int(10));
        // 何かの処理
    }
    catch (...) {
        std::cout << "例外が発生しました" << std::endl;
    }
    // myIntは自動的に解放される
    return 0;
}

このコードでは、std::unique_ptrを使用して動的にメモリを確保しています。

例外が発生しても、unique_ptrのデストラクタが自動的に呼ばれ、確保したメモリは適切に解放されます。

このようにRAIIを活用することで、メモリリークのリスクを減らし、例外安全なプログラムを作成することができます。

まとめ

この記事を通じて、C++の例外クラスの基本から応用、そしてそれに関連する重要なコンセプトについて詳しく解説しました。

初心者から中級者まで、C++における効果的なエラー処理の方法とその実装について理解を深めることができたことでしょう。

例外安全性の確保やRAIIの活用は、C++プログラミングの品質を高める上で不可欠な要素です。

これらの知識を活用して、より堅牢で信頼性の高いプログラムを開発してください。