【Qt入門】QElapsedTimer::secsTo()を使った処理時間計測の基本

2025-05-27

QElapsedTimerは、高精度な時間計測を行うためのQtのクラスです。特に、アプリケーションのパフォーマンス測定や、特定の処理にかかった時間を計測する際などに使用されます。

secsTo()メソッドは、あるQElapsedTimerオブジェクトが表す時点から、別のQElapsedTimerオブジェクトが表す時点までの秒数を計算して返します。戻り値の型はqint64で、これは64ビット符号付き整数を意味し、非常に長い時間(理論上、数億年)を秒単位で表現できます。

構文

qint64 QElapsedTimer::secsTo(const QElapsedTimer &other) const
  • other: 比較対象となる別のQElapsedTimerオブジェクトです。

動作の仕組み

  1. まず、QElapsedTimerオブジェクトは、start()を呼び出すことでタイマーを開始し、現在の時刻を記録します。

  2. 次に、別のQElapsedTimerオブジェクトを作成し、同様にstart()を呼び出すか、あるいは既存のQElapsedTimerオブジェクトのstart()を呼び出し直します。

  3. secsTo()を呼び出すと、呼び出し元のQElapsedTimerオブジェクトが記録している時刻と、引数として渡されたotherが記録している時刻との間の差分を秒単位で計算します。

    • もしotherの時刻が呼び出し元の時刻よりも未来であれば、正の値(経過秒数)を返します。
    • もしotherの時刻が呼び出し元の時刻よりも過去であれば、負の値(呼び出し元からotherまでの逆方向の秒数)を返します。
    • もしどちらかのタイマーが開始されていない場合、secsTo()は常に0を返します。

使用例

ある処理にかかった時間を秒単位で計測する一般的な例を挙げます。

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

int main() {
    QElapsedTimer timer1; // 最初のタイマー
    QElapsedTimer timer2; // 2番目のタイマー

    // タイマー1を開始
    timer1.start();

    // 何らかの処理をシミュレート (例: 2秒待つ)
    QThread::msleep(2000);

    // タイマー2を開始 (この時点でタイマー1からの経過時間が記録される)
    timer2.start();

    // timer1からtimer2までの秒数を計算
    qint64 elapsedSecs = timer1.secsTo(timer2);

    qDebug() << "timer1からtimer2までの経過秒数:" << elapsedSecs << "秒";
    // 出力例: timer1からtimer2までの経過秒数: 2 秒

    // 別の例: timer2からtimer1までの秒数を計算(負の値になる)
    qint64 reversedSecs = timer2.secsTo(timer1);
    qDebug() << "timer2からtimer1までの経過秒数:" << reversedSecs << "秒";
    // 出力例: timer2からtimer1までの経過秒数: -2 秒

    return 0;
}

この例では、timer1.start()からtimer2.start()までの間に2秒間の遅延を意図的に入れています。そのため、timer1.secsTo(timer2)2を返します。逆に、timer2.secsTo(timer1)-2を返します。



QElapsedTimerは高精度な時間計測に非常に便利ですが、誤った使い方をすると予期せぬ結果を招くことがあります。特にsecsTo()に関連する一般的な問題とその解決策は以下の通りです。

タイマーが開始されていない(start()が呼び出されていない)

エラー/症状
secsTo()が常に0を返す、または期待通りの値が返ってこない。

原因
QElapsedTimerオブジェクトは、start()メソッドを呼び出して初めて現在の時刻を記録し、有効な状態になります。start()が呼び出されていないタイマーに対してsecsTo()を呼び出すと、そのタイマーは「無効 (invalid)」な状態と見なされ、結果が0になるか、あるいは未定義の動作を引き起こす可能性があります。

トラブルシューティング
secsTo()を使用する前に、比較対象となる両方のQElapsedTimerオブジェクトに対してstart()(またはrestart())が呼び出されていることを確認してください。

QElapsedTimer timer1;
QElapsedTimer timer2;

// BAD: timer1.start() が呼ばれていない
// qint64 secs = timer1.secsTo(timer2); // 0 が返る可能性が高い

// GOOD: 両方のタイマーを開始する
timer1.start();
// 何らかの処理
timer2.start();
qint64 secs = timer1.secsTo(timer2);

また、isValid()メソッドを使ってタイマーが有効かどうかを確認することもできます。

QElapsedTimer timer;
qDebug() << "Is timer valid?" << timer.isValid(); // 通常 false

timer.start();
qDebug() << "Is timer valid after start?" << timer.isValid(); // true

時間の順序の誤解(負の戻り値)

エラー/症状
secsTo()が負の値を返す。

原因
secsTo(const QElapsedTimer &other)は、this(メソッドを呼び出しているオブジェクト)の時刻からotherの時刻までの時間差を計算します。

  • otherthisより過去の時刻を表す場合、負の値を返します。
  • otherthisより未来の時刻を表す場合、正の値を返します。

負の値が返ることは、必ずしもエラーではありませんが、意図しない場合は、比較するタイマーの順序を間違えている可能性があります。

トラブルシューティング
期待する時間差が常に正の値であることを望むのであれば、常に過去の時刻を記録したタイマーから未来の時刻を記録したタイマーへのsecsTo()呼び出しを行ってください。

QElapsedTimer startTime;
QElapsedTimer endTime;

startTime.start();
// 長い処理
endTime.start();

// 処理にかかった時間を正の値で取得
qint64 durationSecs = startTime.secsTo(endTime); // これが通常期待される動作

// 逆に呼び出すと負の値になる
// qint64 reversedDurationSecs = endTime.secsTo(startTime); // 負の値

異なるスレッドでのQElapsedTimerの使用と同期の問題

エラー/症状
複数のスレッドでQElapsedTimerを使用した場合に、計測値が不安定になったり、期待通りに動作しない。

原因
QElapsedTimerオブジェクト自体はスレッドセーフではありません。つまり、複数のスレッドから同時に同じQElapsedTimerオブジェクトのメソッド(start(), secsTo()など)を呼び出すと、競合状態が発生し、未定義の動作や不正な計測値が生じる可能性があります。

トラブルシューティング

  • 共有するQElapsedTimerへのアクセスを同期する
    もし何らかの理由で単一のQElapsedTimerインスタンスを複数のスレッドで共有する必要がある場合(稀なケースですが)、QMutexなどの同期メカニズムを使用して、QElapsedTimerへのアクセスを保護する必要があります。
  • 各スレッドで独自のQElapsedTimerインスタンスを使用する
    これが最も安全で推奨される方法です。各スレッドが独立して時間を計測できます。

<!-- end list -->

// 異なるスレッドで共有する場合の例 (非推奨ですが、同期の概念を示す)
// QMutex mutex;
// QElapsedTimer sharedTimer;

// Thread A:
// mutex.lock();
// sharedTimer.start();
// mutex.unlock();

// Thread B (後で時間を比較する場合):
// QElapsedTimer localTimerB;
// localTimerB.start(); // 各スレッドで独自のタイマーを持つのが理想

// mutex.lock();
// qint64 secs = sharedTimer.secsTo(localTimerB); // こういう比較は難しい
// mutex.unlock();

一般的には、各スレッドで自身の処理の開始時刻をQElapsedTimerで記録し、そのスレッド内での処理完了時にまたQElapsedTimerを記録して、そのスレッド内でsecsTo()を計算するのが最もシンプルで安全です。

他のAPIとの比較におけるクロックの不一致

エラー/症状
QElapsedTimerで計測した時間と、他のライブラリやOSのAPI(例: C++標準ライブラリの<chrono>GetTickCountなど)で計測した時間とで、大きな誤差がある。

原因
QElapsedTimerは、OSが提供する高精度なモノトニック(単調増加)なクロックを使用しようとします。しかし、OSやシステムによっては、使用されるクロックの種類が異なったり、一部の非モノトニックなクロック(システム時刻の変更に影響されるクロック)が使用される場合があります。他のAPIが異なるクロックを使用している場合、比較が困難になります。

トラブルシューティング

  • タイムゾーンや夏時間の影響
    QElapsedTimerはタイムゾーンや夏時間の変更に影響されません。これはQTimeとの大きな違いです。もしQTimeのような壁時計時刻と比較している場合は、その違いを考慮する必要があります。
  • 同じクロックを使用する
    可能な限り、同じクロックソース(QElapsedTimerを使うか、C++11のstd::chrono::steady_clockなど、モノトニッククロックを提供するAPIを使うか)で時間を計測するように統一してください。

タイマーのリセット忘れ

エラー/症状
複数回時間計測を行う際に、最初の計測は正しいが、2回目以降の計測値がおかしい。

原因
QElapsedTimerstart()が呼び出された時点を基準とします。もし、計測を再開する際にstart()を呼び出し忘れると、前回のstart()からの時間が計測されてしまいます。

トラブルシューティング
繰り返し計測を行う場合は、各計測サイクルの開始時にrestart()メソッドを使用するのが便利です。restart()は、elapsed()を呼び出して経過時間を取得し、同時にタイマーを再開するアトミックな操作です。

QElapsedTimer timer;
timer.start();

// 最初の処理
QThread::msleep(1000);
qDebug() << "最初の処理にかかった時間:" << timer.elapsed() << "ms";

// 次の処理を開始する前にタイマーをリスタートする
timer.restart(); // ここでタイマーが再開される

// 2番目の処理
QThread::msleep(500);
qDebug() << "2番目の処理にかかった時間:" << timer.elapsed() << "ms";

QElapsedTimer::secsTo()を含むQElapsedTimerの利用においては、以下の点を常に意識することが重要です。

  • 他の時間計測APIとの比較には、クロックの種類の一致を考慮する。
  • マルチスレッド環境での使用には注意し、必要ならスレッドごとに独立したタイマーを使用する。
  • 時間の順序(過去から未来へ)を意識する。
  • 比較する両方のタイマーが有効であること。
  • タイマーはstart()で開始する。


QElapsedTimerは、高精度な時間計測に特化したQtのクラスです。特に、ある時点から別の時点までの経過時間を秒単位で取得する際にsecsTo()が役立ちます。

以下に、いくつかの典型的な使用例とその解説を示します。

例1: 単一の処理にかかった時間を秒単位で計測する

これは最も一般的な使用方法です。ある処理の開始時刻と終了時刻をそれぞれQElapsedTimerで記録し、その差分を計算します。

#include <QCoreApplication>
#include <QElapsedTimer>
#include <QDebug>
#include <QThread> // 例としてQThread::msleep()を使用

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

    // 処理の開始時刻を記録するタイマー
    QElapsedTimer startTime;
    startTime.start(); // タイマーを開始(現在の時刻を記録)

    qDebug() << "長時間の処理を開始します...";

    // ここで何か時間のかかる処理を行う(例として2.5秒待機)
    QThread::msleep(2500); // 2500ミリ秒 = 2.5秒

    // 処理の終了時刻を記録するタイマー
    QElapsedTimer endTime;
    endTime.start(); // タイマーを開始(現在の時刻を記録)

    // startTime から endTime までの秒数を計算
    qint64 elapsedSecs = startTime.secsTo(endTime);

    qDebug() << "処理が完了しました。";
    qDebug() << "かかった秒数 (近似値):" << elapsedSecs << "秒";

    // 補足: QElapsedTimer::elapsed() を使ってミリ秒精度で確認
    qint64 elapsedMsecs = startTime.elapsed();
    qDebug() << "かかったミリ秒数:" << elapsedMsecs << "ミリ秒";

    return a.exec();
}

解説

  1. startTime.start(); で、処理が開始される直前の時刻をstartTimeに記録します。
  2. QThread::msleep(2500); で、実際に時間のかかる処理をシミュレートします。
  3. endTime.start(); で、処理が完了した直後の時刻をendTimeに記録します。
  4. startTime.secsTo(endTime); を呼び出すことで、startTimeが記録した時刻からendTimeが記録した時刻までの時間差を秒単位で取得します。この例では、約2秒が返されるでしょう(小数点以下は切り捨てられます)。
  5. startTime.elapsed() は、startTimestart()されてからの経過時間をミリ秒単位で返します。より詳細な時間を確認したい場合に便利です。

例2: 特定のイベント間の時間差を計測する(異なるイベントのタイミング)

この例では、ユーザーの操作やシステムイベントなど、異なるタイミングで発生するイベント間の時間差を計測します。

#include <QCoreApplication>
#include <QElapsedTimer>
#include <QDebug>
#include <QTimer> // QTimer::singleShot() を使用

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

    QElapsedTimer eventATimer;
    QElapsedTimer eventBTimer;

    qDebug() << "イベントAの発生をシミュレートします。";
    eventATimer.start(); // イベントAが発生した時刻を記録

    // 5秒後にイベントBを発生させることをシミュレート
    QTimer::singleShot(5000, [&]() {
        qDebug() << "イベントBの発生をシミュレートします。";
        eventBTimer.start(); // イベントBが発生した時刻を記録

        // イベントAからイベントBまでの秒数を計算
        qint64 secsBetweenEvents = eventATimer.secsTo(eventBTimer);
        qDebug() << "イベントAからイベントBまでの経過秒数:" << secsBetweenEvents << "秒";

        // アプリケーションを終了
        a.quit();
    });

    return a.exec();
}

解説

  1. eventATimer.start(); で、最初のイベント(イベントA)が発生した時刻を記録します。
  2. QTimer::singleShot(5000, ...); を使用して、5000ミリ秒(5秒)後に一度だけ実行されるラムダ関数を設定します。これにより、イベントBの発生をシミュレートします。
  3. ラムダ関数内でeventBTimer.start(); を呼び出し、イベントBが発生した時刻を記録します。
  4. eventATimer.secsTo(eventBTimer); で、イベントAからイベントBまでの時間差を秒単位で取得します。この例では、約5秒が返されるでしょう。

例3: secsTo()が負の値を返すケース

secsTo()は、呼び出し元のタイマーが引数のタイマーよりも未来の時刻を指す場合に、負の値を返します。この挙動を理解しておくことは重要です。

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

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

    QElapsedTimer earlyTimer;
    earlyTimer.start(); // 最初に開始

    QThread::msleep(1000); // 1秒待つ

    QElapsedTimer lateTimer;
    lateTimer.start(); // 後で開始

    qDebug() << "lateTimer.secsTo(earlyTimer) の結果:";
    // lateTimer は earlyTimer より後に開始しているので、
    // lateTimer の視点から見ると earlyTimer は過去の時刻。
    // そのため、結果は負の値になる。
    qint64 secsBackward = lateTimer.secsTo(earlyTimer);
    qDebug() << "遅いタイマーから早いタイマーへの経過秒数:" << secsBackward << "秒";
    // 出力例: 遅いタイマーから早いタイマーへの経過秒数: -1 秒

    qDebug() << "\nearlyTimer.secsTo(lateTimer) の結果:";
    // earlyTimer は lateTimer より先に開始しているので、
    // earlyTimer の視点から見ると lateTimer は未来の時刻。
    // そのため、結果は正の値になる。
    qint64 secsForward = earlyTimer.secsTo(lateTimer);
    qDebug() << "早いタイマーから遅いタイマーへの経過秒数:" << secsForward << "秒";
    // 出力例: 早いタイマーから遅いタイマーへの経過秒数: 1 秒

    return a.exec();
}

解説

  1. earlyTimer.start(); が先に呼び出され、lateTimer.start(); が1秒後に呼び出されます。
  2. lateTimer.secsTo(earlyTimer); では、lateTimerの視点から見てearlyTimerは1秒過去の時刻を指しています。そのため、-1が返されます。
  3. earlyTimer.secsTo(lateTimer); では、earlyTimerの視点から見てlateTimerは1秒未来の時刻を指しています。そのため、1が返されます。

このように、secsTo()の戻り値の符号は、呼び出し元のタイマーが引数に渡されたタイマーよりも「早い」か「遅い」かを示します。



QElapsedTimer::secsTo()は、二つのQElapsedTimerインスタンスが記録した時刻間の差を秒単位で取得するのに便利ですが、同様の目的を達成するための他の方法や、より適切な場合がある代替手段がいくつか存在します。

QElapsedTimer::elapsed()と手動計算

これは最も一般的で柔軟な代替方法です。QElapsedTimer::elapsed()は、タイマーがstart()またはrestart()されてからの経過時間をミリ秒単位で返します。このミリ秒値を秒に変換することで、secsTo()と類似の結果を得ることができます。

利点

  • 柔軟性
    ミリ秒、マイクロ秒(nsecsElapsed())、またはカスタムの単位に簡単に変換できます。
  • より高い精度
    elapsed()はミリ秒単位の精度を提供するため、秒単位での切り捨てを避けることができます(必要に応じて手動で秒に変換)。
  • 単一のタイマーで完結する
    複数のQElapsedTimerオブジェクトを用意する必要がない場合が多いです。

欠点

  • 二つの異なる時点間の差を直接は計算しない
    elapsed()は常に「タイマーが開始されてから現在までの時間」を返します。二つの特定のイベント時刻間の差を計算するには、それぞれのイベントでelapsed()を呼び出し、その差を計算する必要があります。

コード例

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

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

    QElapsedTimer timer;
    timer.start(); // 処理開始時刻を記録

    qDebug() << "最初の処理を開始します...";
    QThread::msleep(1500); // 1.5秒待機
    qint64 elapsedAtMidpoint = timer.elapsed(); // 中間地点での経過ミリ秒

    qDebug() << "2番目の処理を開始します...";
    QThread::msleep(1000); // さらに1秒待機

    qint64 totalElapsedMsecs = timer.elapsed(); // 全体の経過ミリ秒

    // 最初の処理にかかった時間 (近似値)
    qint64 firstProcessSecs = elapsedAtMidpoint / 1000;
    qDebug() << "最初の処理にかかった秒数:" << firstProcessSecs << "秒";

    // 2番目の処理にかかった時間 (近似値)
    qint64 secondProcessSecs = (totalElapsedMsecs - elapsedAtMidpoint) / 1000;
    qDebug() << "2番目の処理にかかった秒数:" << secondProcessSecs << "秒";

    // 全体の処理にかかった時間 (近似値)
    qint64 totalProcessSecs = totalElapsedMsecs / 1000;
    qDebug() << "全体の処理にかかった秒数:" << totalProcessSecs << "秒";

    return a.exec();
}

この例では、secsTo()を使わずに、一つのQElapsedTimerelapsed()によって同様の計測を行っています。

C++標準ライブラリの<chrono>

C++11以降で導入された<chrono>ライブラリは、高精度な時間計測のための標準的なフレームワークを提供します。プラットフォーム非依存であり、QElapsedTimerと同様にモノトニッククロック(std::chrono::steady_clock)を使用できます。

利点

  • 柔軟な単位変換
    std::chrono::duration_castを使って、必要な単位に簡単に変換できます。
  • 強力な型安全性
    時間の単位(秒、ミリ秒、マイクロ秒など)が型によって保証されるため、誤った単位での計算ミスを防ぎやすいです。
  • 標準準拠
    Qtに依存しないため、Qt以外のC++プロジェクトでも再利用可能です。

欠点

  • Qtの他の時間関連クラス(QDateTime, QDate, QTime)との連携は、手動での変換が必要になる場合があります。
  • Qtのシグナル/スロットやイベントループとの直接的な統合はQTimerなどのQtクラスを使う方が自然です。

コード例

#include <iostream>
#include <chrono> // C++標準の時関連ライブラリ
#include <thread>   // std::this_thread::sleep_for を使用

int main() {
    // 処理開始時刻を記録
    auto start = std::chrono::steady_clock::now();

    std::cout << "時間のかかる処理を開始します..." << std::endl;

    // 何らかの処理をシミュレート (例: 2.5秒待機)
    std::this_thread::sleep_for(std::chrono::milliseconds(2500));

    // 処理終了時刻を記録
    auto end = std::chrono::steady_clock::now();

    // 期間を計算 (デフォルトはナノ秒単位)
    auto duration = end - start;

    // 秒単位に変換
    auto duration_seconds = std::chrono::duration_cast<std::chrono::seconds>(duration);
    std::cout << "かかった秒数:" << duration_seconds.count() << "秒" << std::endl;

    // ミリ秒単位に変換
    auto duration_milliseconds = std::chrono::duration_cast<std::chrono::milliseconds>(duration);
    std::cout << "かかったミリ秒数:" << duration_milliseconds.count() << "ミリ秒" << std::endl;

    // マイクロ秒単位に変換
    auto duration_microseconds = std::chrono::duration_cast<std::chrono::microseconds>(duration);
    std::cout << "かかったマイクロ秒数:" << duration_microseconds.count() << "マイクロ秒" << std::endl;

    return 0;
}

QDateTimeまたはQTime (msecsTo()またはsecsTo())

QDateTimeQTimeも時間計測の機能を持っていますが、これらは壁時計時間(wall-clock time)を扱うため、システム時刻の変更(NTP同期、手動変更、サマータイムなど)の影響を受けます。したがって、厳密な経過時間計測(パフォーマンス測定など)には不向きですが、特定のカレンダー上の日付や時刻間の差を計算する場合には適しています。

利点

  • msecsTo()やsecsTo()メソッドも持つ
    QElapsedTimerと同様に、二つのインスタンス間の差を計算するメソッドがあります。
  • 人間が理解しやすい時間
    日付や時刻を扱うため、ログ記録などには適しています。

欠点

  • 精度が低い
    通常、QElapsedTimer<chrono>ほど高精度ではありません。
  • 非モノトニック
    システム時刻の変更によって、計測値が不連続になる可能性があります。これが最大の欠点です。
#include <QCoreApplication>
#include <QDateTime>
#include <QDebug>
#include <QThread>

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

    QDateTime startDateTime = QDateTime::currentDateTime(); // 現在の日時を取得

    qDebug() << "処理を開始します...";
    QThread::msleep(2500); // 2.5秒待機

    QDateTime endDateTime = QDateTime::currentDateTime(); // 処理終了時の日時を取得

    // QDateTime::secsTo() を使用して秒単位の差を計算
    qint64 elapsedSecs = startDateTime.secsTo(endDateTime);
    qDebug() << "QDateTimeで計測した経過秒数:" << elapsedSecs << "秒";

    // QDateTime::msecsTo() を使用してミリ秒単位の差を計算
    qint64 elapsedMsecs = startDateTime.msecsTo(endDateTime);
    qDebug() << "QDateTimeで計測した経過ミリ秒数:" << elapsedMsecs << "ミリ秒";

    // QTimeでも同様のメソッドがあります
    QTime startTime = QTime::currentTime();
    QThread::msleep(1500);
    QTime endTime = QTime::currentTime();
    qint64 elapsedSecsTime = startTime.secsTo(endTime);
    qDebug() << "QTimeで計測した経過秒数:" << elapsedSecsTime << "秒";


    return a.exec();
}