Qtの基礎 - 例外処理
概要
例外処理は、プログラムの実行中に発生する予期しないエラーや異常な状況を適切に管理するための重要な機能である。
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
フラグを追加する。- これにより、例外処理が明示的に有効化される。