Qtの基礎 - 例外処理

提供:MochiuWiki : SUSE, EC, PCB
ナビゲーションに移動 検索に移動

概要

例外処理は、プログラムの実行中に発生する予期しないエラーや異常な状況を適切に管理するための重要な機能である。

Qtでは、C++の標準例外機構を基本として、Qt独自の拡張が加えられている。
try-catchブロックを使用して、潜在的に例外を投げる可能性のあるコードを囲み、発生した例外を捕捉する。

QExceptionクラスは、Qtフレームワーク内で使用される基本的な例外クラスである。
このクラスはstd::exceptionを継承しており、Qtに特化した例外を定義するための基盤となっている。

例外を投げる場合は、throwキーワードを使用する。

 if (<条件式>) {
    throw QException();
 }


これを捕捉するには、以下に示すように記述する。

 try {
    // 例外が発生する可能性のあるコード
 }
 catch (const QException &e) {
    // QExceptionの処理
 }
 catch (const std::exception &e) {
    // その他の標準C++例外の処理
 }
 catch (...) {
    // あらゆる例外の処理
 }


QExceptionクラスには、raiseメソッドがある。
これは、例外を再スローするのに便利であり、例外の連鎖を作成する場合に使用される。

また、Qtは、より具体的な例外クラスも提供している。
例えば、QNoMemory等があり、特定のエラー状況に対応している。

例外処理を適切に使用することにより、エラーの発生箇所と処理箇所を分離して、コードの可読性と保守性を向上させることができる。
ただし、過度な使用は避け、本当に必要な場合にのみ例外を使用することが推奨される。

また、Qtでは例外を完全に無効化することもできる。
これは、例外処理のオーバーヘッドを避ける場合や、例外をサポートしていないプラットフォームで開発する場合に有効である。


QExceptionクラス

QExceptionクラスとは

QExceptionクラスは、スレッド間で転送可能な例外の基底クラスを提供する。

QtConcurrentクラスは、QExceptionクラスを継承しており、2つのヘルパー関数を実装することにより、
スレッドの境界を越えて例外をスローおよびキャッチすることをサポートする。
QExceptionクラスを継承したサブクラスは、値によってスローされ、参照によってキャッチする必要がある。

 class SampleException : public QException
 {
 public:
    void raise() const override
    {
       throw *this;
    }
 
    SampleException *clone() const override
    {
       return new SampleException(*this);
    }
 };


 try
 {
    QtConcurrent::blockingMap(list, throwFunction); // throwFunctionはSampleExceptionをスローする関数とする
 }
 catch (SampleException &e)
 {
    // handle exception
    // ...略
 }


QExceptionクラスを継承したサブクラスではない例外を投げる場合、Qtの関数は受信側のスレッドでQUnhandledExceptionクラスを投げる。

QFutureクラスを使用する場合、以下に示す関数を呼び出すと、転送された例外がスローされる。

  • QFuture::waitForFinished()
  • QFuture::result()
  • QFuture::resultAt()
  • QFuture::results()


QExceptionクラスのメンバ関数

  • clone関数
 QException *QException::clone() const


QExceptionクラスを継承したサブクラスにおいて、clone関数を以下のようにオーバーライドする。

 SampleException *SampleException::clone() const
 {
    return new MyException(*this);
 }


  • raise関数
 void QException::raise() const


QExceptionクラスを継承したサブクラスにおいて、raise関数を以下のようにオーバーライドする。

 void MyException::raise() const
 {
    throw *this;
 }



QExceptionの使用例 : 0除算

以下の例では、QExceptionクラスを継承して、0除算で例外をスローするクラスを定義している。
また、safeDivide関数を定義して、0で除算した場合に、QDivideByZeroExceptionをスローする例を示している。

このクラスは、以下に示すような特徴を持つ。

  • コンストラクタでエラーメッセージを受け取る。 (デフォルトのメッセージもある)
  • raiseメソッドをオーバーライドして、例外を再スローできる。
  • cloneメソッドをオーバーライドして、例外オブジェクトの複製を可能にする。
  • whatメソッドをオーバーライドして、エラーメッセージを返す。


 // QDivideByZeroException.hファイル
 
 #include <QCoreApplication>
 #include <QException>
 
 class QDivideByZeroException : public QException
 {
 private:
    QString m_message;
 
 public:
    QDivideByZeroException(const QString &message = "ゼロ除算が発生") : m_message(message)
    {}
 
    void raise() const override
    {
       throw *this;
    }
 
    QDivideByZeroException *clone() const override
    {
       return new QDivideByZeroException(*this);
    }
 
    const char* what() const noexcept override
    {
       return m_message.toLocal8Bit().constData();
    }
 };


 // main.cppファイル
 
 #include "QDivideByZeroException.h"
 
 double safeDivide(double numerator, double denominator)
 {
    if (denominator == 0.0f) {
        throw QDivideByZeroException("ゼロで除算しようとしました");
    }
 
    return numerator / denominator;
 }
 
 int main()
 {
    try {
       double result = safeDivide(10.0, 0.0);
    }
    catch (const QDivideByZeroException &e) {
       qDebug() << "Caught exception:" << e.what();
    }
 
    return 0;
 }



QExceptionの使用例 : ファイル操作

以下の例では、ファイル操作に関連する複数の例外クラスを定義している。

  • QFileOperationException
    ファイル操作に関する基本的な例外クラス。
  • QFileNotFoundException
    ファイルが見つからない場合の例外クラス。
  • QFilePermissionException
    ファイルへのアクセス権限がない場合の例外クラス。


これらの例外クラスを使用することにより、ファイル操作時に発生する可能性のある様々なエラーを明確に区別して、適切に処理することができる。
readFile関数では、これらの例外を使用してファイルの存在チェックとオープン操作を行っている。
ファイルが存在しない場合はQFileNotFoundExceptionクラス、オープンできない場合はQFilePermissionExceptionをスローする。

このアプローチには、以下に示すメリットがある。

  • エラーの種類ごとに異なる処理を行うことができる。
  • エラーメッセージをより具体的かつ情報量の多いものにできる。
  • コードの可読性と保守性が向上する。


 // QFileOperationException.hファイル
 
 #include <QCoreApplication>
 #include <QFile>
 #include <QException>
 #include <QDebug>
 
 class QFileOperationException : public QException
 {
 protected:
    QString m_message;
 
 public:
    QFileOperationException(const QString &message) : m_message(message)
    {}
 
    void raise() const override
    {
       throw *this;
    }
 
    QFileOperationException *clone() const override
    {
       return new QFileOperationException(*this);
    }
 
    const char* what() const noexcept override
    {
       return m_message.toLocal8Bit().constData();
    }
 };
 
 class QFileNotFoundException : public QFileOperationException
 {
 public:
    QFileNotFoundException(const QString &fileName) : QFileOperationException(QString("File not found: %1").arg(fileName))
    {}
 };
 
 class QFilePermissionException : public QFileOperationException
 {
 public:
    QFilePermissionException(const QString &fileName) : QFileOperationException(QString("Permission denied: %1").arg(fileName))
    {}
 };


 // main.cppファイル
 
 #include "QFileOperationException.h"
 
 // ファイル操作を行う関数
 void readFile(const QString &fileName)
 {
    QFile file(fileName);
    if (!file.exists()) {
       throw QFileNotFoundException(fileName);
    }
 
    if (!file.open(QIODevice::ReadOnly)) {
       throw QFilePermissionException(fileName);
    }
 
    // ファイルの読み込み処理
    qDebug() << "File opened successfully: " << fileName;
 
    file.close();
 }
 
 int main()
 {
    try {
        readFile("nonexistent.txt");
    }
    catch (const QFileNotFoundException &e) {
        qDebug() << "File not found error:" << e.what();
    }
    catch (const QFilePermissionException &e) {
        qDebug() << "Permission error:" << e.what();
    }
    catch (const QFileOperationException &e) {
        qDebug() << "General file operation error:" << e.what();
    }
    catch (const QException &e) {
        qDebug() << "Unknown Qt exception occurred:" << e.what();
    }
    catch (const std::exception &e) {
        qDebug() << "Standard exception occurred:" << e.what();
    }
    catch (...) {
        qDebug() << "Unknown exception occurred";
    }

    return 0;
 }



全ての例外をキャッチする方法

標準の設定では、NULLポインタのアクセスや0除算等で発生する例外をキャッチすることができない。

 char *p = nullptr;
 try
 {
    qDebug() << "try";
    *p = 'A';
 }
 catch(...)
 {
    qDebug() << "catch";
 }


Qtプロジェクトファイル (.pro) の場合

qmakeで生成されたMakefileを見ると、-EHscオプションが付加されている。
/EHsまたは/EHscを使用する場合、catch句では、非同期構造化例外がキャッチされない。

構造化例外とは、ハードウェア例外を含むシステム的な例外のことで、
Windowsでは、SEH(Structured Exception Handling : 構造化例外処理)という仕組みが提供されている。

全ての例外をキャッチするには、Qtプロジェクトファイル(.pro)に、以下の設定を追記する。

 win*
 {
    QMAKE_CXXFLAGS_EXCEPTIONS_ON = /EHa
    QMAKE_CXXFLAGS_STL_ON        = /EHa
 }
 
 linux*
 {
    QMAKE_CXXFLAGS_EXCEPTIONS_ON = /EHa
    QMAKE_CXXFLAGS_STL_ON        = /EHa
 }


ビルドして生成されたMakefileには、上記で設定した-EHaオプションが付加される。
この状態で、上記のサンプルコードを実行すると、例外をキャッチすることができる。

CMakeLists.txtファイルの場合

以下に示す設定を、CMakeLists.txtファイルの適切な位置 (一般的には、プロジェクトとターゲットの設定の後) に追加することにより、
プロジェクト全体で全ての例外をキャッチする準備が整う。

 # 例外処理の設定
 if(MSVC)
    # Windows (MSVC)の場合
    add_compile_options(/EHa)
 elseif(CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang")
    # GCC または Clang の場合
    add_compile_options(-fexceptions)
 endif()


  • Windows(MSVC)の場合
    /EHaフラグを追加する。
    これにより、非同期例外を含むすべての例外をキャッチできる。
  • Linux (GCC / Clang) の場合
    -fexceptionsフラグを追加する。
    これにより、例外処理が明示的に有効化される。