QElapsedTimer::start()だけじゃない!Qtで経過時間を測る代替方法を徹底比較

2025-05-27

void QElapsedTimer::start() の説明

この関数は、QElapsedTimer オブジェクトがタイマーの計測を開始することを指示します。start() が呼び出された時点が、時間計測の「開始点」となります。

主な機能

  1. 計測開始
    start() を呼び出すことで、タイマーが内部的に現在の時刻を記録し、そこから経過時間の計算が始まります。
  2. タイマーの有効化
    QElapsedTimer オブジェクトは、start() が呼び出されるまでは「無効な状態」です。start() を呼び出すことで「有効な状態」になり、elapsed() などの関数で経過時間を取得できるようになります。
  3. モノトニッククロックの使用
    QElapsedTimer は、可能な限りシステム上のモノトニッククロック(単調増加するクロック)を使用しようとします。これにより、システムの時刻変更(ユーザーによる手動変更やサマータイムの調整など)に影響されずに、正確な経過時間を計測できます。
  4. 人間が読める時間への変換不可
    モノトニッククロックを使用するため、QElapsedTimer で取得した時間は、特定の日時(例:2025年5月25日 19:25:45)のような人間が読める形式に直接変換することはできません。あくまで「ある時点からどれくらいの時間が経過したか」を計測するために設計されています。

ある処理の実行時間を計測したい場合、以下のように使います。

#include <QElapsedTimer>
#include <QDebug> // デバッグ出力用

void someSlowOperation() {
    // 時間がかかる可能性のある処理
    for (volatile int i = 0; i < 100000000; ++i) {
        // 何もしないループで時間を稼ぐ
    }
}

int main() {
    QElapsedTimer timer; // QElapsedTimer オブジェクトを作成

    timer.start();       // ここでタイマー計測を開始

    someSlowOperation(); // 計測したい処理を実行

    qint64 elapsed_ms = timer.elapsed(); // 処理が終わった後、経過時間を取得 (ミリ秒単位)

    qDebug() << "The slow operation took" << elapsed_ms << "milliseconds";

    // また、残り時間を計算するような使い方もできます
    int totalTimeout = 500; // 500ミリ秒のタイムアウト
    timer.restart(); // タイマーを再開し、前回の経過時間をリセット

    // 別の処理をタイムアウト内で実行
    while (!timer.hasExpired(totalTimeout)) {
        // 何らかの処理
        // ...
    }
    qDebug() << "Operations completed or timed out.";

    return 0;
}
  • restart() メソッドもあり、これは elapsed() を呼び出して現在の経過時間を取得し、同時にタイマーをリセット(新しい開始点を設定)するのに便利です。
  • QElapsedTimer には stop() メソッドは存在しません。これは、start() で開始点を記録し、elapsed() でその開始点からの差分を計算するというシンプルな仕組みだからです。


QElapsedTimer が無効な状態で elapsed() を呼び出す

エラーの症状
elapsed()nsecsElapsed() を呼び出したときに、予期しない非常に大きな値や負の値が返される、または未定義の動作が発生する。

原因
QElapsedTimer オブジェクトは、コンストラクタで作成された直後や invalidate() が呼び出された後など、初期状態では「無効 (invalid)」です。無効なタイマーに対して elapsed() などの時間を取得する関数を呼び出すと、結果は保証されません。isValid() メソッドでタイマーが有効かどうかを確認できます。

トラブルシューティング

  • isValid() で確認する
    デバッグ時に if (!timer.isValid()) { /* エラー処理 */ } のようにチェックを追加すると、この種の問題を早期に発見できます。
  • 必ず start() を呼び出す
    QElapsedTimer を使って時間を計測する前に、必ず timer.start() を呼び出してタイマーを有効にしてください。
QElapsedTimer timer;
// timer.start(); // これを忘れると問題が発生する
qDebug() << timer.elapsed(); // 未定義の動作、または奇妙な値

エラーの症状
QElapsedTimer で計測した時間が、システム時刻を巻き戻したり進めたりしても変化しない、または期待と異なる結果になる。

原因
QElapsedTimer は、可能な限りモノトニッククロック(単調増加するクロック)を使用します。これは、システム時刻(ユーザーが変更したり、NTPで同期されたりする時刻)の変更に影響されずに、物理的な時間の経過を正確に測定することを目的としています。そのため、QElapsedTimer の結果を人間が読める特定の日時に直接変換しようとすると誤解が生じます。



例1: 単純な処理時間の計測

最も基本的な使い方です。特定の関数やコードブロックの実行時間を計測します。

#include <QCoreApplication>
#include <QElapsedTimer>
#include <QDebug>
#include <QThread> // QThread::msleep() を使うため

// 時間がかかる可能性のあるダミー関数
void performSomeTask() {
    qDebug() << "タスクを開始します...";
    // ここで何か重い処理をシミュレートする(例: 500ミリ秒待つ)
    QThread::msleep(500);
    qDebug() << "タスクが完了しました。";
}

int main(int argc, char *argv[]) {
    QCoreApplication a(argc, argv);

    QElapsedTimer timer; // QElapsedTimer オブジェクトを作成

    qDebug() << "計測を開始します。";
    timer.start();       // ★ ここでタイマー計測を開始

    performSomeTask();   // 計測したい処理を実行

    // elapsed() で経過時間をミリ秒単位で取得
    qint64 elapsed_ms = timer.elapsed();
    qDebug() << "処理にかかった時間 (ミリ秒):" << elapsed_ms << "ms";

    // nsecsElapsed() でナノ秒単位で取得 (より高精度な場合)
    qint64 elapsed_ns = timer.nsecsElapsed();
    qDebug() << "処理にかかった時間 (ナノ秒):" << elapsed_ns << "ns";

    return a.exec(); // QCoreApplication を使用する場合、通常はイベントループを回します
}

解説

  1. QElapsedTimer timer; でタイマーオブジェクトを宣言します。この時点ではタイマーは「無効」な状態です。
  2. timer.start(); を呼び出すと、タイマーが内部的に現在の高精度な時刻を記録し、計測が開始されます。タイマーは「有効」な状態になります。
  3. performSomeTask(); が実行されます。この間、タイマーはバックグラウンドで時間を計測し続けます。
  4. timer.elapsed(); または timer.nsecsElapsed(); を呼び出すと、start() が呼び出されてからの経過時間が取得されます。

例2: タイムアウト処理の実装

特定の処理が一定時間内に完了するかどうかをチェックするタイムアウト処理に QElapsedTimer を利用できます。

#include <QCoreApplication>
#include <QElapsedTimer>
#include <QDebug>
#include <QThread>

// 時間がかかる可能性のあるダミー関数 (途中で終了する可能性がある)
bool tryToFinishWithinTime(int maxAttempts, QElapsedTimer& timer, int timeoutMs) {
    qDebug() << "タイムアウト処理を開始します。制限時間:" << timeoutMs << "ms";

    for (int i = 0; i < maxAttempts; ++i) {
        qDebug() << "  試行回数:" << (i + 1);
        QThread::msleep(100); // 各試行で100ミリ秒かかる
        
        // hasExpired() でタイムアウトしたかどうかをチェック
        if (timer.hasExpired(timeoutMs)) {
            qDebug() << "  !!! タイムアウトしました !!!";
            return false; // タイムアウト
        }
    }
    qDebug() << "タイムアウトせずに完了しました。";
    return true; // タイムアウトせずに完了
}

int main(int argc, char *argv[]) {
    QCoreApplication a(argc, argv);

    QElapsedTimer timer;
    timer.start(); // ★ タイムアウト計測の開始点

    int timeoutLimit = 350; // 350ミリ秒のタイムアウト
    int maxAttempts = 5;    // 最大5回試行

    if (tryToFinishWithinTime(maxAttempts, timer, timeoutLimit)) {
        qDebug() << "処理は時間内に正常に完了しました。";
    } else {
        qDebug() << "処理は時間内に完了しませんでした。";
    }

    return a.exec();
}

解説

  1. timer.start(); でタイムアウト計測の基準となる時間を設定します。
  2. tryToFinishWithinTime 関数内で、ループごとに timer.hasExpired(timeoutMs) を呼び出します。
  3. hasExpired(timeoutMs) は、start() が呼び出されてから timeoutMs ミリ秒以上が経過したかどうかを返します。これにより、処理がタイムアウトしたかどうかを簡単に判断できます。

restart() は、現在の経過時間を取得し、同時にタイマーをリセット(新しい開始点を設定)するのに便利です。例えば、一連のステップのそれぞれの時間を計測する場合などに役立ちます。

#include <QCoreApplication>
#include <QElapsedTimer>
#include <QDebug>
#include <QThread>

int main(int argc, char *argv[]) {
    QCoreApplication a(argc, argv);

    QElapsedTimer timer;

    // ステップ1の開始
    qDebug() << "--- ステップ1を開始 ---";
    timer.start(); // ★ 計測開始
    QThread::msleep(120); // 処理1
    qint64 step1_time = timer.elapsed(); // ステップ1の経過時間を取得
    qDebug() << "ステップ1にかかった時間:" << step1_time << "ms";

    // ステップ2の開始 (timer.restart() を使用)
    qDebug() << "--- ステップ2を開始 ---";
    // restart() は、timer.elapsed() を呼び出した後、直ちに timer.start() を呼び出すのと同じ効果があります。
    timer.restart(); // ★ タイマーをリセットし、同時に計測を再開
    QThread::msleep(80); // 処理2
    qint64 step2_time = timer.elapsed(); // ステップ2の経過時間を取得
    qDebug() << "ステップ2にかかった時間:" << step2_time << "ms";

    // ステップ3の開始 (再度 restart() を使用)
    qDebug() << "--- ステップ3を開始 ---";
    timer.restart(); // ★ 再度リセット&再開
    QThread::msleep(250); // 処理3
    qint64 step3_time = timer.elapsed(); // ステップ3の経過時間を取得
    qDebug() << "ステップ3にかかった時間:" << step3_time << "ms";

    qDebug() << "全てのステップが完了しました。";

    return a.exec();
}
  1. 最初の timer.start(); で全体の計測を開始します。
  2. 各ステップの終わりに timer.elapsed(); でそのステップまでの時間を取得します。


QTime クラスを使用する

QTime は日中の時刻(時、分、秒、ミリ秒)を扱うクラスで、QElapsedTimer と同様に経過時間を計測するための start()restart()elapsed() メソッドを持っています。

メリット

  • 日中の時刻操作(例:午前9時から午後5時までの計算)にも使用できます。
  • QElapsedTimer が導入される以前の古いQtバージョンでも利用可能でした。

デメリット

  • 精度
    オペレーティングシステムによって精度が異なり、ミリ秒未満の精度は期待できない場合があります。
  • 24時間でラップアラウンド
    start() または restart() が最後に呼び出されてから24時間が経過すると、カウンターがゼロに戻ります。そのため、24時間を超えるような長時間にわたる計測には適していません。
  • システム時刻の変更に影響される
    最も重要な注意点です。ユーザーがシステムの時刻を変更したり、サマータイムが適用されたりすると、QTime::elapsed() の結果が不正確になる可能性があります。

使用例

#include <QCoreApplication>
#include <QTime>
#include <QDebug>
#include <QThread>

void performSomeTaskQTime() {
    qDebug() << "QTime タスクを開始します...";
    QThread::msleep(300);
    qDebug() << "QTime タスクが完了しました。";
}

int main(int argc, char *argv[]) {
    QCoreApplication a(argc, argv);

    QTime timer;
    timer.start(); // QTime で計測開始

    performSomeTaskQTime();

    qint64 elapsed_ms = timer.elapsed();
    qDebug() << "QTime 処理にかかった時間:" << elapsed_ms << "ms";

    return a.exec();
}

QDateTime クラスを使用する

QDateTime は日付と時刻の両方を扱うクラスです。特定の時点のタイムスタンプを取得し、それらの差を計算することで経過時間を求めることができます。

メリット

  • currentMSecsSinceEpoch() のように、エポック(1970年1月1日UTC)からのミリ秒数を取得できるため、異なるシステム間での比較が容易です。
  • タイムゾーンや夏時間(DST)を考慮した日時計算が可能です。
  • 絶対的な日時(例:2025年5月25日 19:30:00)を扱うことができます。

デメリット

  • QElapsedTimer のようなモノトニッククロックではないため、システム時刻の変更に影響を受けます。
  • パフォーマンス計測には非推奨
    QElapsedTimerQTime に比べて、経過時間を計算するためのオーバーヘッドが大きくなる可能性があります。短い処理のベンチマークには適していません。

使用例

#include <QCoreApplication>
#include <QDateTime>
#include <QDebug>
#include <QThread>

void performSomeTaskQDateTime() {
    qDebug() << "QDateTime タスクを開始します...";
    QThread::msleep(400);
    qDebug() << "QDateTime タスクが完了しました。";
}

int main(int argc, char *argv[]) {
    QCoreApplication a(argc, argv);

    QDateTime start_time = QDateTime::currentDateTime(); // 現在の時刻を取得

    performSomeTaskQDateTime();

    QDateTime end_time = QDateTime::currentDateTime(); // 処理後の時刻を取得

    // msecsTo() で2つのQDateTime間のミリ秒差を取得
    qint64 elapsed_ms = start_time.msecsTo(end_time);
    qDebug() << "QDateTime 処理にかかった時間:" << elapsed_ms << "ms";

    // または、エポックからのミリ秒数で計算
    qint64 start_epoch_ms = QDateTime::currentMSecsSinceEpoch();
    performSomeTaskQDateTime();
    qint64 end_epoch_ms = QDateTime::currentMSecsSinceEpoch();
    qDebug() << "QDateTime (epoch) 処理にかかった時間:" << (end_epoch_ms - start_epoch_ms) << "ms";


    return a.exec();
}

C++標準ライブラリの <chrono> を使用する (C++11以降)

C++11以降、標準ライブラリに時間操作のための <chrono> ヘッダが導入されました。これはQtに依存しないクロスプラットフォームな方法で、特に高精度な時間計測が必要な場合や、Qt以外のC++コードとの互換性を重視する場合に非常に強力な選択肢となります。

メリット

  • 標準準拠
    Qtに依存しないため、プロジェクトのポータビリティが向上します。
  • モノトニッククロック
    std::chrono::steady_clock はシステム時刻の変更に影響されないモノトニッククロックを提供します。これは QElapsedTimer と同様の特性です。
  • 高い精度
    ナノ秒単位の精度までサポートされており、ほとんどのユースケースで十分な精度を提供します。

デメリット

  • Qtの特定のタイマー機能(例:QTimer のイベントループとの統合)とは目的が異なります。
  • Qtのシグナル/スロットシステムとの直接的な連携は提供されません(手動でラップする必要がある)。
#include <QCoreApplication>
#include <chrono> // C++標準ライブラリのchrono
#include <QDebug>
#include <QThread>

void performSomeTaskChrono() {
    qDebug() << "Chrono タスクを開始します...";
    QThread::msleep(300);
    qDebug() << "Chrono タスクが完了しました。";
}

int main(int argc, char *argv[]) {
    QCoreApplication a(argc, argv);

    // std::chrono::steady_clock::time_point を使用して開始時刻を記録
    auto start_time = std::chrono::steady_clock::now(); // ★ 計測開始

    performSomeTaskChrono();

    auto end_time = std::chrono::steady_clock::now(); // 処理後の時刻を記録

    // 2つの時間点間の差を計算
    auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(end_time - start_time);

    qDebug() << "Chrono 処理にかかった時間:" << duration.count() << "ms";

    // ナノ秒単位で取得する場合
    auto duration_ns = std::chrono::duration_cast<std::chrono::nanoseconds>(end_time - start_time);
    qDebug() << "Chrono 処理にかかった時間:" << duration_ns.count() << "ns";

    return a.exec();
}
  • Qtに依存しないC++コードを書きたい、または非常に高精度な時間計測と標準準拠を重視する場合

    • C++標準ライブラリの <chrono> を使用します。
  • 絶対的な日時情報が必要な場合や、日付と時刻の複雑な操作が必要な場合

    • QDateTime を使用します。ただし、短い処理のパフォーマンス計測には適していません。
  • Qt 4系など古いQtバージョンを使用しており、QElapsedTimer が利用できない場合

    • QTime を使用します。ただし、24時間でラップアラウンドする点と、システム時刻変更の影響に注意が必要です。
  • ほとんどの場合、QElapsedTimer がベストな選択です。

    • クロスプラットフォームで高精度な経過時間計測が可能です。
    • システム時刻の変更に影響されません(モノトニッククロック)。
    • タイムアウト判定など、便利な機能が揃っています。