Qtの基礎 - 終了処理

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

概要

Qtアプリケーションの終了は、いくつかの段階を経て行われる。

まず、アプリケーションの終了が開始される時、QCoreApplication::aboutToQuit()シグナルが発行される。
このシグナルは、アプリケーションが終了する直前に発生するため、重要なクリーンアップ作業を行うのに適している。

次に、QApplication::exec()メソッドから制御が戻る。
これにより、メインイベントループが終了して、アプリケーションの実行が停止する。

GUIでは、この時点で、Qtは自動的に全ての子ウィジェットを削除する。
これには、トップレベルウィンドウやダイアログも含まれる。

その後、グローバルなstaticオブジェクトのデストラクタが呼び出される。
これらのオブジェクトは、逆構築順序で破棄される。つまり、最後に作成されたオブジェクトが最初に破棄される。

最後に、メイン関数からreturn文が実行されて、プログラムが完全に終了する。

Qtアプリケーションの終了をカスタマイズする方法もある。
例えば、QApplication::setQuitOnLastWindowClosed(false)を呼び出すことにより、
最後のウインドウが閉じられてもアプリケーションが終了しないようにすることができる。

また、QObject::deleteLater()メソッドを使用して、イベントループの次の反復時にオブジェクトを安全に削除することもできる。

メモリリークを防ぐために、動的に割り当てられたリソースを適切に解放することが重要である。
Qtのオブジェクト所有システムを活用すると、多くの場合、明示的なメモリ管理が不要になる。

エラーハンドリングも終了処理の重要である。
例外が捕捉されずにメイン関数を抜ける場合、プログラムがクラッシュする可能性がある。
そのため、適切な例外処理を実装することが推奨される。


コンソールアプリケーション

QCoreApplication::quit()メソッドおよびQCoreApplication::exit()において、
両方ともアプリケーションを終了するために使用するが、動作と使用場面が異なる。

使用の指針を以下に示す。

  • 一般的な状況では、QCoreApplication::quit()メソッドの使用を推奨する。
    これは、イベントループを適切に終了して、保留中のイベントを処理する機会を与える。

  • QCoreApplication::exit()メソッドは、
    即座にアプリケーションを終了する必要がある場合 (例: クリティカルなエラーが発生した場合) や特定の終了コードを返す必要がある場合に使用する。

  • GUIアプリケーションでは、QCoreApplication::quit()メソッドの使用を推奨する。
    これにより、ウインドウを適切に閉じて、リソースをクリーンアップする機会が与えられる。

  • コンソールアプリケーションでは、QCoreApplication::quit()メソッドでもよいが、
    特定の終了コードが必要な場合は、QCoreApplication::exit()メソッドを使用することがある。


QCoreApplication::quit()

QCoreApplication::quit()メソッドは、シグナルとして実装されている。
非同期で動作して、イベントループを安全に終了させることができる。
(イベントループの次の反復時に処理される)

戻り値はない。

一般的に、スロットとして接続、または、QTimer::singleShot()と組み合わせて使用する。

以下の例では、QCoreApplication::quit()メソッドはシグナルとして動作するため、関数呼び出しは即座に戻り、次の行が実行される。
アプリケーションは次のイベントループの反復時に終了する。

 // 使用例
 
 #include <QCoreApplication>
 #include <QTimer>
 #include <iostream>
 
 void useQuit()
 {
    std::cout << "Calling quit()..." << std::endl;
    QCoreApplication::quit();
    std::cout << "This line will be executed." << std::endl;
 }
 
 int main(int argc, char *argv[])
 {
    QCoreApplication a(argc, argv);
 
    // QCoreApplication::quit()の例
    QTimer::singleShot(1000, useQuit);
 
    return a.exec();
 }


QCoreApplication::exit()

QCoreApplication::exit(int returnCode = 0)は、通常のメソッドとして実装されている。
同期的に動作して、イベントループを即座に終了させることができる。
(呼び出された時点で即座に処理される)

整数の戻り値を受け取り、それをアプリケーションの終了コードとして使用する。

特定の終了コードでアプリケーションを終了させる場合に使用する。

以下の例では、QCoreApplication::exit(0)メソッドが呼び出された直後にアプリケーションが終了するため、2番目のcout文は実行されない。

 #include <QCoreApplication>
 #include <iostream>
 
 void useExit()
 {
    std::cout << "Calling exit()..." << std::endl;
    QCoreApplication::exit(0);
    std::cout << "This line will NOT be executed." << std::endl;
 }
 
 int main(int argc, char *argv[])
 {
    QCoreApplication a(argc, argv);
 
    // QCoreApplication::exit()の例
    QTimer::singleShot(1000, useExit);
 
    return a.exec();
 }



QWidgetsアプリケーション

GUIアプリケーションの終了処理は、ユーザがアプリケーションのメインウィンドウを閉じる、または、[終了]ボタンを押下することにより開始される。
この時点で、アプリケーションの終了プロセスが実行される。

終了プロセスの最初のステップとして、QApplication::aboutToQuit()シグナルが発行される。
このシグナルは、GUIアプリケーションが終了する直前に発生するため、重要なクリーンアップを行うのに適したタイミングである。

重要なクリーンアップの例として、以下に示すようなものがある。

  • ユーザデータの保存
    未保存の作業内容やアプリケーションの状態を保存する。
    これには、ドキュメントの内容、ウインドウの位置やサイズ、ユーザ設定等が含まれている。
  • 開いているファイルやデータベース接続の終了
    ファイルハンドルやデータベース接続を適切に閉じることにより、データの整合性を保ち、リソースリークを防ぐ。
  • ネットワーク接続の終了
    アクティブなネットワーク接続を適切に終了して、リモートサーバにグレースフルな切断を通知する。
  • テンポラリファイルの削除
    GUIアプリケーションが作成した一時ファイルを削除して、不要なディスク使用を避ける。
  • バックグラウンドタスクの終了
    実行中のバックグラウンドスレッドやタイマを適切に停止する。


これらのクリーンアップを実行するために、QObject::connect()メソッドを使用してaboutToQuit()シグナルにスロットを接続する。
クリーンアップの完了後は、QApplication::exec()メソッドから制御が戻り、メインイベントループが終了する。
この時に、Qtは自動的に全ての子ウィジェットを削除する。

最後に、グローバルなstaticオブジェクトのデストラクタが呼び出されて、メイン関数からreturn文が実行されてプログラムが完全に終了する。

以下の例では、QWidgetsアプリケーションの終了処理を示している。
基本的な終了処理とエラーハンドリングを実装しているが、実務ではより具体的なクリーンアップ処理を追加する必要がある。

 // MainWindow.hファイル
 
 #include <QApplication>
 #include <QMainWindow>
 #include <QMenu>
 #include <QMenuBar>
 #include <QAction>
 #include <QCloseEvent>
 #include <QMessageBox>
 #include <QDebug>
 
 class MainWindow : public QMainWindow
 {
    Q_OBJECT
 
 public:
    MainWindow(QWidget *parent = nullptr) : QMainWindow(parent)
    {
       setWindowTitle("Qt Application");
 
       QMenu *fileMenu = menuBar()->addMenu("&File");
       QAction *exitAction = fileMenu->addAction("E&xit");
       connect(exitAction, &QAction::triggered, this, &MainWindow::onExit);
 
       // アプリケーション終了時のクリーンアップ処理を接続
       connect(qApp, &QApplication::aboutToQuit, this, &MainWindow::performCleanup);
    }
 
 protected:
    // closeEventメソッドをオーバーライドして、アプリケーション終了時の動作をカスタマイズする
    void closeEvent(QCloseEvent *event) override
    {
       if (saveChanges()) {
          event->accept();
       }
       else {
          event->ignore();
       }
    }
 
 private slots:
    void onExit()
    {
       close();
    }
 
    // アプリケーション終了時のクリーンアップ処理を行う
    void performCleanup()
    {
       qDebug() << "Performing cleanup...";
       // リソースの解放やその他のクリーンアップ処理を実装
    }
 
 private:
    // 未保存の変更がある場合にユーザに確認を求める
    bool saveChanges()
    {
       // ここで未保存の変更をチェックし、必要に応じて保存ダイアログを表示
       QMessageBox::StandardButton reply;
       reply = QMessageBox::question(this, "Unsaved Changes", 
                                     "Do you want to save changes before exiting?",
                                     QMessageBox::Yes|QMessageBox::No|QMessageBox::Cancel);
 
       if (reply == QMessageBox::Yes) {
          // 変更を保存する処理をここに実装
          qDebug() << "Saving changes...";
          return true;
       }
       else if (reply == QMessageBox::No) {
          return true;
       }
       else {
          return false;
       }
    }
 };


 // main.cppファイル
 
 #include <QFile>
 #include <QTextStream>
 #include "MainWindow.h"
 
 // カスタムのメッセージハンドラを定義して、アプリケーションのログをファイルに出力する
 void myMessageHandler(QtMsgType type, const QMessageLogContext &context, const QString &msg)
 {
    QFile file("app_log.txt");
    if (!file.open(QIODevice::WriteOnly | QIODevice::Append)) {
        return;
    }

    QTextStream stream(&file);
    stream << QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss.zzz ");
 
    switch (type) {
    case QtDebugMsg:
        stream << "Debug: ";
        break;
    case QtInfoMsg:
        stream << "Info: ";
        break;
    case QtWarningMsg:
        stream << "Warning: ";
        break;
    case QtCriticalMsg:
        stream << "Critical: ";
        break;
    case QtFatalMsg:
        stream << "Fatal: ";
        break;
    }
    stream << msg << " (" << context.file << ":" << context.line << ")" << Qt::endl;
    file.close();
 }
 
 int main(int argc, char *argv[])
 {
    qInstallMessageHandler(myMessageHandler);
 
    // 例外処理を実装して、予期しないエラーをキャッチしてログに記録する
    try {
       QApplication app(argc, argv);
 
       MainWindow mainWindow;
       mainWindow.show();
 
       QObject::connect(&app, &QApplication::aboutToQuit, []() {
          qDebug() << "Application is about to quit";
          // 追加のクリーンアップ処理
       });
 
       return app.exec();
    }
    catch (const std::exception& e) {
       QMessageBox::critical(nullptr, "Error", QString("An unexpected error occurred: %1").arg(e.what()));
       qCritical() << "Unhandled exception:" << e.what();
 
       return -1;
    }
    catch (...) {
        QMessageBox::critical(nullptr, "Error", "An unknown error occurred");
        qCritical() << "Unknown exception occurred";
 
        return -1;
    }
 }