Qt StateMachineによる認証フロー制御

開発環境の準備

Qtの状態マシン機能を利用するには、プロジェクトファイル(.pro)に以下のモジュールを追加する。

QT += statemachine

ヘッダファイルでは、必要に応じて以下をインクルードする。

#include <QStateMachine>
#include <QState>
#include <QFinalState>
#include <QSignalTransition>

状態マシンの構成要素

QStateの階層構造

Qtの状態マシンは階層型有限状態機として実装されている。QAbstractStateを基底とし、QStateが基本クラスとなる。子状態を持つ親状態を構成することで、複雑な状態遷移を整理できる。

状態の主な特性:

  • addTransition() で遷移条件を設定
  • onEntry / onExit でライフサイクルイベントを捕捉
  • 親状態に所属する状態マシンを machine() で取得

遷移の実装パターン

遷移設定には2通りの方法がある。

// パターンA: シグナル駆動での簡易設定
sourceState->addTransition(emitter, SIGNAL(signalName()), targetState);

// パターンB: 遷移オブジェクトの明示的構築
auto *tx = new QSignalTransition(someButton, &QPushButton::clicked);
tx->setTargetState(anotherState);
currentState->addTransition(tx);

カスタム遷移クラス

複雑な判定ロジックが必要な場合、QAbstractTransitionを継承して実装する。

class ConditionalTransition : public QAbstractTransition
{
    Q_OBJECT
public:
    using GuardFunc = std::function<bool(QEvent*)>;
    using ActionFunc = std::function<void(QEvent*)>;

    explicit ConditionalTransition(GuardFunc guard, ActionFunc action, 
                                  QState *target = nullptr)
        : guard_(std::move(guard)), action_(std::move(action)) 
    {
        if (target) setTargetState(target);
    }

protected:
    bool eventTest(QEvent *ev) override 
    {
        return guard_ ? guard_(ev) : true;
    }

    void onTransition(QEvent *ev) override 
    {
        if (action_) action_(ev);
    }

private:
    GuardFunc guard_;
    ActionFunc action_;
};

ログインシステムの実装

状態設計

以下の4状態を定義する:

状態名役割
Idle初期状態、入力待ち
Authenticatingサーバ通信中
Authenticated認証成功(終了状態)
Failed認証失敗、再入力へ

実装コード

class LoginController : public QObject
{
    Q_OBJECT
public:
    explicit LoginController(QObject *parent = nullptr);
    void initiate();

    // 外部からトリガーされる操作
    void submitCredentials(const QString &user, const QString &pass);
    void cancelOperation();

signals:
    void credentialSubmitted();
    void authSuccess();
    void authRejected();
    void resetRequested();

private:
    void setupStateGraph();
    void performRemoteAuth();

    QStateMachine *engine_;
    QString pendingUser_;
    QString pendingPass_;
};

LoginController::LoginController(QObject *parent)
    : QObject(parent)
    , engine_(new QStateMachine(this))
{
    setupStateGraph();
}

void LoginController::setupStateGraph()
{
    // 状態定義
    auto *idle = new QState(engine_);
    auto *verifying = new QState(engine_);
    auto *success = new QFinalState(engine_);
    auto *rejected = new QState(engine_);

    // プロパティ設定によるUI連動
    idle->assignProperty(this, "status", QString("入力待ち"));
    verifying->assignProperty(this, "status", QString("認証中..."));
    rejected->assignProperty(this, "status", QString("認証失敗"));

    // 遷移定義
    idle->addTransition(this, &LoginController::credentialSubmitted, verifying);
    verifying->addTransition(this, &LoginController::authSuccess, success);
    verifying->addTransition(this, &LoginController::authRejected, rejected);
    rejected->addTransition(this, &LoginController::resetRequested, idle);

    // エントリ/エグジット処理
    connect(verifying, &QState::entered, this, &LoginController::performRemoteAuth);

    // 初期状態設定
    engine_->setInitialState(idle);
}

void LoginController::submitCredentials(const QString &user, const QString &pass)
{
    pendingUser_ = user;
    pendingPass_ = pass;
    emit credentialSubmitted();
}

void LoginController::performRemoteAuth()
{
    // 非同期処理を模擬
    QTimer::singleShot(1500, this, [this]() {
        bool valid = (pendingUser_ == "admin" && pendingPass_ == "secret");
        valid ? emit authSuccess() : emit authRejected();
    });
}

エラーハンドリングの拡張

ネットワーク異常時のリトライ対応を追加する例:

class RetryableTransition : public QAbstractTransition
{
public:
    explicit RetryableTransition(int maxAttempts, QState *fallback)
        : maxRetry_(maxAttempts), fallbackState_(fallback), attemptCount_(0) {}

protected:
    bool eventTest(QEvent *) override 
    { 
        return attemptCount_ < maxRetry_; 
    }

    void onTransition(QEvent *) override 
    {
        ++attemptCount_;
    }

private:
    int maxRetry_;
    QState *fallbackState_;
    int attemptCount_;
};

// 使用例:verifying状態からの分岐
auto *retryTx = new RetryableTransition(3, idle);
retryTx->setTargetState(verifying);  // 再試行
verifying->addTransition(retryTx);

状態マシンの起動と制御

int main(int argc, char *argv[])
{
    QApplication app(argc, argv);

    LoginController controller;
    
    // UI構築(省略)
    QWidget window;
    QPushButton *btnLogin = new QPushButton("ログイン", &window);
    
    QObject::connect(btnLogin, &QPushButton::clicked, [&]() {
        controller.submitCredentials("admin", "secret");
    });

    controller.initiate();  // 状態マシン開始
    return app.exec();
}

QStateMachine::start() を呼び出すと、initialState から評価が開始される。停止時は stop() を呼び出し、必要に応じて deleteLater() で資源を解放する。

タグ: Qt StateMachine QState QAbstractTransition シグナル・スロット

8月17日 01:28 投稿