Qtタイマー徹底解説:QTimerの基本から活用、よくあるエラーと解決法
Qtでタイマーを扱う主要なクラスは QTimer
です。
QTimer
クラスの主な機能
QTimer
は、高レベルなタイマー機能を提供します。
-
- 指定した時間間隔(ミリ秒単位)で繰り返し
timeout()
シグナルを発行します。 - このシグナルをスロットに接続することで、定期的な処理を実行できます。
- 例: アナログ時計の秒針の更新、ゲームのフレーム更新など。
- 指定した時間間隔(ミリ秒単位)で繰り返し
-
シングルショットタイマー (Single-Shot Timers)
- 一度だけ
timeout()
シグナルを発行し、その後は自動的に停止します。 - 特定の遅延後に一度だけ処理を実行したい場合に便利です。
- 例: スプラッシュスクリーンの表示後、一定時間でメインウィンドウを表示する、ユーザーが操作を停止してからしばらくして自動保存する、など。
- 一度だけ
QTimer
を使う最も一般的な方法は、以下の手順です。
-
QTimer オブジェクトの作成
QTimer *timer = new QTimer(this); // thisは親オブジェクト (例: QWidget)
親オブジェクトを指定することで、親が破棄されるときにタイマーも自動的に破棄され、メモリ管理が容易になります。
-
timeout() シグナルとスロットの接続
QTimer
のtimeout()
シグナルを、実行したい処理を含むスロットに接続します。connect(timer, &QTimer::timeout, this, &MyClass::mySlotFunction);
mySlotFunction
は、タイマーがタイムアウトするたびに呼び出されるメソッドです。 -
タイマーの開始
タイマーを開始し、タイムアウト間隔を設定します。timer->start(1000); // 1000ミリ秒(1秒)ごとにタイムアウト
または、コンストラクタで間隔を指定することもできます (Qt 6.8以降の
QChronoTimer
でより自然な表現が可能です)。 -
シングルショットタイマーの設定 (任意)
一度だけ実行したい場合は、setSingleShot(true)
を呼び出すか、静的メソッドQTimer::singleShot()
を使用します。// 方法1: setSingleShot() を使う QTimer *singleShotTimer = new QTimer(this); singleShotTimer->setSingleShot(true); connect(singleShotTimer, &QTimer::timeout, this, &MyClass::runOnceFunction); singleShotTimer->start(5000); // 5秒後に一度だけ実行 // 方法2: QTimer::singleShot() 静的メソッドを使う(より簡潔) QTimer::singleShot(5000, this, &MyClass::runOnceFunction); // 5秒後に一度だけ実行
-
タイマーの停止
タイマーを停止するにはstop()
を呼び出します。timer->stop();
- QChronoTimer (Qt 6.8以降)
QTimer
の最大間隔が約24日であるのに対し、QChronoTimer
はより広い間隔(約292年)とより高い精度(std::chrono::nanoseconds
)をサポートします。ミリ秒単位の解像度と24日程度の範囲で十分な場合はQTimer
を引き続き使用できます。 - QObject::timerEvent() と QBasicTimer
QTimer
は高レベルなインターフェースですが、より低レベルなタイマー機能としてQObject::startTimer()
を呼び出してタイマーIDを取得し、QObject::timerEvent()
メソッドをオーバーライドしてイベントを処理する方法や、QBasicTimer
を使用する方法もあります。ただし、これらの方法はシグナル/スロットやシングルショットタイマーといった高レベルな機能がサポートされていないため、通常はQTimer
が推奨されます。 - ゼロミリ秒タイマー (Zero-millisecond timers)
start(0)
でタイマーを開始すると、OSのイベントキューにある他のイベントがすべて処理された後、可能な限り早くタイムアウトします。これにより、UIの応答性を保ちながら、GUIスレッドで重い処理を小分けにして実行するのに役立ちます。ただし、最近ではより適切なマルチスレッドソリューション (QThread
など) が推奨されます。 - 精度とオーバーラン
- タイマーの精度は、基盤となるOSやハードウェアに依存します。多くの場合、ミリ秒単位の精度がサポートされていますが、システムがビジー状態の場合など、必ずしも正確な時間でタイムアウトするとは限りません。
Qt::TimerType
を設定することで、タイマーの精度を調整できます(Qt::PreciseTimer
,Qt::CoarseTimer
,Qt::VeryCoarseTimer
)。- システムがビジーでタイムアウトが遅れる場合(オーバーラン)、Qtは複数のタイムアウトが期限切れになったとしても、
timeout()
シグナルを一度だけ発行し、元の間隔で再開します。
- スレッドアフィニティ
QTimer
は、それが作成されたスレッドのイベントループで動作します。したがって、タイマーを開始・停止する際は、そのタイマーが属するスレッドから行う必要があります。GUIスレッド以外のスレッドでタイマーを使用する場合は、そのスレッドでQThread::exec()
を呼び出してイベントループを開始する必要があります。 - イベントループ
Qtのタイマーは、アプリケーションのイベントループ(QApplication::exec()
またはQCoreApplication::exec()
)が動作しているときにのみ機能します。イベントループがないと、タイマーイベントは処理されません。
タイマーが全く動作しない
原因とトラブルシューティング
-
タイマーの間隔が非常に長い、または非常に短い(実質的に見えない)
- 原因
start()
で設定した間隔が意図せず非常に長く(例: 1時間)、まだタイムアウトしていない、または非常に短く(例: 0ms)て、処理が速すぎて認識できない、という場合があります。 - トラブルシューティング
タイマーの間隔が正しいか確認してください。0msタイマーの場合、他のイベント処理が完了するまで待機するため、すぐに実行されないことがあります。
- 原因
-
タイマーオブジェクトが破棄されている
- 原因
QTimer
オブジェクトがスコープ外に出てしまったり、親オブジェクトが破棄されたりして、タイマー自体が破棄されてしまっている可能性があります。 - トラブルシューティング
QTimer
をヒープ (new QTimer(this)
) に作成し、適切な親オブジェクトを持たせるか、寿命が十分長い場所に配置されているかを確認してください。ローカル変数として関数内でQTimer timer;
のように作成すると、関数が終了すると同時にタイマーも破棄されます。
- 原因
-
タイマーが開始されていない、またはすぐに停止している
- 原因
timer->start(interval);
が呼び出されていないか、タイマーを作成した直後(またはコールバック内などで)にtimer->stop();
が呼び出されている可能性があります。 - トラブルシューティング
start()
が適切に呼び出されているか、そして意図しないstop()
呼び出しがないかを確認してください。
- 原因
-
シグナルとスロットの接続ミス
- 原因
QTimer::timeout()
シグナルが、実行したいスロットに正しく接続されていない可能性があります。 - トラブルシューティング
connect()
ステートメントのスペルミス、引数の型不一致、またはスロットのアクセス指定子(public slots:
またはprivate slots:
)が適切でないことなどを確認してください。 - Qt 5以降の新しい
connect
構文 (&QTimer::timeout
,&MyClass::mySlot
) を使用している場合、コンパイル時にエラーが見つかりやすいですが、古い文字列ベースの構文 (SIGNAL(timeout())
,SLOT(mySlot())
) の場合は実行時エラーになることがあります。
- 原因
-
- 原因
Qtのタイマーは、アプリケーションのイベントループが動作している場合にのみイベントを発行します。QApplication::exec()
(GUIアプリケーションの場合) またはQCoreApplication::exec()
(非GUIアプリケーションの場合) が呼び出されていないか、途中で終了している可能性があります。 - トラブルシューティング
main()
関数でreturn a.exec();
のように、イベントループが適切に開始され、アプリケーションの終了まで維持されていることを確認してください。
- 原因
タイマーの動作が不安定、または遅延する
原因とトラブルシューティング
-
OSのスケジューリング
- 原因
タイマーの精度は、基盤となるOSのスケジューリングによって影響を受けます。OSが他の高優先度タスクを実行している場合、タイマーイベントの処理が遅れることがあります。特に、WindowsのようなデスクトップOSでは、ミリ秒単位の厳密な精度は保証されません。 - トラブルシューティング
QTimer
の精度タイプ (Qt::PreciseTimer
,Qt::CoarseTimer
,Qt::VeryCoarseTimer
) を調整してみる。ただし、Qt::PreciseTimer
を設定しても、OSの制約を超えることはできません。厳密なタイミングが必要な場合は、リアルタイムOSや組み込みシステムでの特殊なタイマー機能の利用を検討する必要があります。
- 原因
-
UIスレッドでの重い処理
- 原因
timeout()
シグナルに接続されたスロット内で、時間のかかる(CPUバウンドな)処理を実行している場合、イベントループがブロックされ、他のイベント(タイマーイベントも含む)の処理が遅延します。これはGUIアプリケーションのフリーズにもつながります。 - トラブルシューティング
- 時間のかかる処理は、別のスレッド (
QThread
やQtConcurrent
) で実行するようにしてください。 - 処理を小さなチャンクに分割し、0msタイマーなどを使ってイベントループに処理を戻すようにします(ただし、これも複雑になりがちなのでマルチスレッドの方が推奨されます)。
- 時間のかかる処理は、別のスレッド (
- 原因
タイマー停止後も動作し続ける
原因とトラブルシューティング
-
シングルショットタイマーではないのに timeout() 後に停止しない
- 原因
setSingleShot(true)
を設定していない繰り返しタイマーの場合、一度タイムアウトしても自動的には停止しません。 - トラブルシューティング
一度だけ実行したい場合は、setSingleShot(true)
を呼び出すか、QTimer::singleShot()
静的メソッドを使用してください。繰り返しタイマーの場合は、必要に応じてスロット内でtimer->stop();
を明示的に呼び出す必要があります。
- 原因
-
異なるタイマーオブジェクトを操作している
- 原因
stop()
を呼び出しているタイマーオブジェクトが、実際にstart()
されたタイマーオブジェクトとは異なる可能性があります(ポインタの指し間違いなど)。 - トラブルシューティング
start()
とstop()
が同じQTimer
インスタンスに対して呼び出されていることを確認してください。
- 原因
特定のスレッドでのみ動作しない
原因とトラブルシューティング
- スレッドにイベントループがない
- 原因
QTimer
は、そのタイマーが作成されたスレッドのイベントループで動作します。GUIスレッド以外のカスタムスレッドでタイマーを使用する場合、そのスレッドでQThread::exec()
を呼び出してイベントループを開始する必要があります。 - トラブルシューティング
カスタムスレッドでQThread::exec()
が呼び出されていることを確認してください。また、タイマーオブジェクトがそのスレッドにアフィニティを持っていることを確認してください(通常、タイマーが作成されたスレッドにアフィニティを持ちます)。
- 原因
原因とトラブルシューティング
-
deleteLater() の誤用
- 原因
スロット内でtimer->deleteLater();
を呼び出すと、現在のイベントループが終了した後にタイマーオブジェクトが安全に削除されます。しかし、その後に同じタイマーオブジェクトへのポインタを使用しようとすると、ダングリングポインタになりクラッシュの原因になります。 - トラブルシューティング
deleteLater()
を呼び出した後は、そのタイマーオブジェクトへのポインタを使用しないように注意してください。可能であれば、QPointer
を使用して、オブジェクトが削除されたことを検出し、ダングリングポインタの使用を防ぐことができます。
- 原因
-
親オブジェクトなしでの QTimer の作成と破棄忘れ
- 原因
QTimer *timer = new QTimer();
のように親オブジェクトを指定せずにヒープにタイマーを作成し、そのタイマーが不要になったときにdelete timer;
を呼び忘れるとメモリリークが発生します。 - トラブルシューティング
ほとんどの場合、new QTimer(this);
のように親オブジェクトを指定するのが最も安全です。親オブジェクトが破棄されるときに、子オブジェクト(タイマー)も自動的に破棄されます。親オブジェクトを持たせない場合は、必ずdelete
を呼び出して手動でメモリを解放してください。
- 原因
例1: 基本的な繰り返しタイマー (コンソールアプリケーション)
この例では、1秒ごとにメッセージをコンソールに出力するタイマーを作成します。
#include <QCoreApplication>
#include <QTimer>
#include <QDebug> // qDebug() を使うために必要
class MyTimerObject : public QObject
{
Q_OBJECT // シグナル/スロットのために必要
public:
explicit MyTimerObject(QObject *parent = nullptr) : QObject(parent)
{
// QTimerオブジェクトを作成
// 親をthis (MyTimerObjectインスタンス) にすることで、MyTimerObjectが破棄されるときに
// タイマーも自動的に破棄される
QTimer *timer = new QTimer(this);
// タイマーのtimeout()シグナルをmySlotFunction()スロットに接続
connect(timer, &QTimer::timeout, this, &MyTimerObject::mySlotFunction);
// 1000ミリ秒 (1秒) 間隔でタイマーを開始
timer->start(1000);
qDebug() << "タイマーが開始されました。1秒ごとにメッセージが表示されます。";
qDebug() << "5秒後にアプリケーションを終了します...";
// アプリケーションを一定時間後に終了させるシングルショットタイマー
QTimer::singleShot(5000, qApp, &QCoreApplication::quit);
}
private slots:
// タイマーがタイムアウトするたびに呼び出されるスロット
void mySlotFunction()
{
qDebug() << "タイマーがタイムアウトしました!";
}
};
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
MyTimerObject myObject; // MyTimerObjectのインスタンスを作成
return a.exec(); // イベントループを開始
}
#include "main.moc" // MOC (Meta-Object Compiler) が生成するファイル
解説
QTimer::singleShot(5000, qApp, &QCoreApplication::quit);
は、5秒後にアプリケーションを終了させるための便利な静的メソッドです。timer->start(1000)
で1秒間隔の繰り返しタイマーを開始しています。- コンストラクタ内で
QTimer
を作成し、connect()
でtimeout()
シグナルをmySlotFunction()
スロットに接続しています。 MyTimerObject
クラスはQObject
を継承し、Q_OBJECT
マクロを含んでいるため、シグナルとスロットを使用できます。QCoreApplication
を使用しているため、これはGUIなしのコンソールアプリケーションです。
例2: GUIアプリケーションでのタイマーとUI更新
この例では、GUIアプリケーションのウィンドウにラベルを配置し、タイマーを使って数値を1秒ごとに更新します。
mainwindow.h
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow>
#include <QTimer>
#include <QLabel> // QLabel を使うために必要
QT_BEGIN_NAMESPACE
namespace Ui { class MainWindow; }
QT_END_NAMESPACE
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
MainWindow(QWidget *parent = nullptr);
~MainWindow();
private slots:
void updateCounter(); // タイマーのタイムアウト時に呼び出されるスロット
private:
Ui::MainWindow *ui;
QTimer *m_timer; // QTimerのポインタ
int m_counter; // カウンターの値
QLabel *m_label; // 表示用のQLabel
};
#endif // MAINWINDOW_H
mainwindow.cpp
#include "mainwindow.h"
#include "./ui_mainwindow.h" // UIファイルをインクルード
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
, ui(new Ui::MainWindow)
, m_counter(0) // カウンターを0で初期化
{
ui->setupUi(this);
// QLabelを作成し、中央に配置 (レイアウトは簡略化)
m_label = new QLabel("カウント: 0", this);
setCentralWidget(m_label); // MainWindowの中央にラベルを配置
m_label->setAlignment(Qt::AlignCenter);
m_label->setStyleSheet("font-size: 24pt; color: blue;");
// QTimerオブジェクトを作成
m_timer = new QTimer(this); // 親をMainWindowに設定
// タイマーのtimeout()シグナルをupdateCounter()スロットに接続
connect(m_timer, &QTimer::timeout, this, &MainWindow::updateCounter);
// 1000ミリ秒 (1秒) 間隔でタイマーを開始
m_timer->start(1000);
}
MainWindow::~MainWindow()
{
delete ui;
// m_timerは親オブジェクトが破棄されるときに自動的に破棄されるため、
// ここでdelete m_timer; は不要(むしろ二重解放の危険がある)
}
void MainWindow::updateCounter()
{
m_counter++; // カウンターをインクリメント
m_label->setText(QString("カウント: %1").arg(m_counter)); // ラベルを更新
}
main.cpp
#include "mainwindow.h"
#include <QApplication>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
MainWindow w;
w.show();
return a.exec(); // イベントループを開始
}
解説
m_timer
をメンバー変数として保持し、new QTimer(this)
で親をMainWindow
に設定しているため、MainWindow
が閉じられるときにタイマーも自動的にクリーンアップされます。updateCounter()
スロットが1秒ごとに呼び出され、m_counter
をインクリメントし、m_label
のテキストを更新しています。- コンストラクタで
QTimer
を作成し、start(1000)
で1秒間隔のタイマーを開始。 QMainWindow
を継承したクラスで、QLabel
を使ってカウンターを表示しています。
例3: シングルショットタイマー (遅延実行)
この例では、アプリケーション起動時にスプラッシュスクリーンを表示し、5秒後にメインウィンドウを表示するようなシミュレーションをします。
#include <QApplication>
#include <QWidget>
#include <QLabel>
#include <QTimer>
#include <QVBoxLayout> // レイアウトのために必要
class SplashScreen : public QWidget
{
Q_OBJECT
public:
explicit SplashScreen(QWidget *parent = nullptr) : QWidget(parent)
{
setFixedSize(400, 200); // スプラッシュスクリーンのサイズを設定
setWindowTitle("スプラッシュスクリーン");
setStyleSheet("background-color: lightblue;");
QVBoxLayout *layout = new QVBoxLayout(this);
QLabel *label = new QLabel("アプリケーションをロード中...", this);
label->setAlignment(Qt::AlignCenter);
label->setStyleSheet("font-size: 20pt; color: white;");
layout->addWidget(label);
}
};
class MainWindow : public QWidget
{
Q_OBJECT
public:
explicit MainWindow(QWidget *parent = nullptr) : QWidget(parent)
{
setFixedSize(600, 400);
setWindowTitle("メインウィンドウ");
setStyleSheet("background-color: lightgreen;");
QVBoxLayout *layout = new QVBoxLayout(this);
QLabel *label = new QLabel("ようこそ!", this);
label->setAlignment(Qt::AlignCenter);
label->setStyleSheet("font-size: 30pt; color: darkgreen;");
layout->addWidget(label);
}
};
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
SplashScreen splash;
splash.show(); // スプラッシュスクリーンを表示
// 5秒後に一度だけ実行されるシングルショットタイマー
// スプラッシュスクリーンを隠し、メインウィンドウを表示するラムダ式をスロットとして使用
QTimer::singleShot(5000, [&]() {
splash.hide(); // スプラッシュスクリーンを隠す
MainWindow mainWin;
mainWin.show(); // メインウィンドウを表示
// メインウィンドウがイベントループのスコープ外にあるため、
// mainWin.exec() のようなことはできない。
// ここでは、mainWinが破棄される前にイベントループが終了しないように注意が必要。
// 通常は、メインウィンドウをヒープに作成し、アプリの終了時に破棄する。
// 例: QObject::connect(&a, &QApplication::aboutToQuit, &mainWin, &QWidget::deleteLater);
// または、mainWinをmain関数スコープ外の変数にするか、MainWindowを動的確保して所有権を保持する。
// ここでは、QApplicationのイベントループが継続するため、mainWinは表示され続ける。
// しかし、mainWinがローカル変数なので、このスコープを抜けるとデストラクタが呼ばれる可能性がある。
// 正しい実装では、mainWinをmain関数のスコープ外(例: グローバルまたはヒープ)に置くか、
// アプリケーションが終了するまでその生存を保証する。
// 簡単化のために、今回はこのまま進めます。
});
return a.exec(); // イベントループを開始
}
解説
- この例では、Qt 5で導入されたラムダ式(クロージャ)をスロットとして使用しており、スプラッシュスクリーンのインスタンス (
splash
) にアクセスしてhide()
を呼び出しています。 - 第一引数はミリ秒単位の間隔、第二引数はシグナルを受け取るオブジェクト、第三引数は実行されるスロットです。
QTimer::singleShot(interval, receiver, slot)
の静的メソッドを使用しています。これは非常に便利で、一度だけ実行したい処理に最適です。
注意
この方法は、UIスレッドでの重い処理を「小分けにする」ためのテクニックですが、現代のQtプログラミングでは、時間のかかる処理は別のスレッド (QThread
や QtConcurrent
) で実行することが強く推奨されます。これはあくまで概念的な理解のための例です。
#include <QApplication>
#include <QWidget>
#include <QLabel>
#include <QPushButton>
#include <QProgressBar>
#include <QVBoxLayout>
#include <QTimer>
#include <QDebug>
class WorkerObject : public QObject
{
Q_OBJECT
public:
explicit WorkerObject(QObject *parent = nullptr) : QObject(parent) {}
// 処理の進行度をUIに通知するシグナル
signals:
void progressUpdated(int value);
void finished();
public slots:
void startHeavyProcess()
{
m_currentIndex = 0;
m_totalSteps = 100; // 全体のステップ数
// ゼロミリ秒タイマーを開始
// イベントキューに他のイベントがない場合に、すぐにtimeout()が発行される
QTimer::singleShot(0, this, &WorkerObject::doNextStep);
qDebug() << "重い処理を開始します...";
}
private slots:
void doNextStep()
{
if (m_currentIndex < m_totalSteps) {
// 一部の処理を実行
// ここでは、計算のシミュレーションとして短い遅延を入れる
// 実際には、ファイルの読み書き、複雑な計算など
// QApplication::processEvents() は不要 (イベントループが自動で処理)
QThread::msleep(10); // UIをブロックせずに10ms待機 (デモンストレーション用)
m_currentIndex++;
emit progressUpdated(m_currentIndex); // UIに進行度を通知
// 次のステップのために、再びゼロミリ秒タイマーをセット
// これにより、UIイベントが処理される機会が与えられる
QTimer::singleShot(0, this, &WorkerObject::doNextStep);
} else {
qDebug() << "重い処理が完了しました!";
emit finished(); // 完了を通知
}
}
private:
int m_currentIndex;
int m_totalSteps;
};
class MainWindow : public QWidget
{
Q_OBJECT
public:
MainWindow(QWidget *parent = nullptr) : QWidget(parent)
{
setWindowTitle("ゼロミリ秒タイマーの例");
QVBoxLayout *layout = new QVBoxLayout(this);
m_progressBar = new QProgressBar(this);
m_progressBar->setRange(0, 100);
m_progressBar->setValue(0);
layout->addWidget(m_progressBar);
m_startButton = new QPushButton("処理を開始", this);
layout->addWidget(m_startButton);
// WorkerObjectをGUIスレッドで作成
m_worker = new WorkerObject(this);
// ボタンがクリックされたらワーカーの処理を開始
connect(m_startButton, &QPushButton::clicked, m_worker, &WorkerObject::startHeavyProcess);
// ワーカーの進行度更新シグナルをプログレスバーのスロットに接続
connect(m_worker, &WorkerObject::progressUpdated, m_progressBar, &QProgressBar::setValue);
// ワーカーが完了したらメッセージを表示
connect(m_worker, &WorkerObject::finished, this, [&]() {
m_startButton->setText("処理が完了しました!");
m_startButton->setEnabled(false);
qDebug() << "UIが更新されました: 処理完了";
});
}
private:
QProgressBar *m_progressBar;
QPushButton *m_startButton;
WorkerObject *m_worker;
};
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
MainWindow w;
w.show();
return a.exec();
}
#include "main.moc" // mocファイル
- この例では、メインスレッド内で処理を分割していますが、実際のプロダクションコードでは、このような重い処理は必ず別スレッドにオフロードすることを強く推奨します。 UIの応答性を保つ最善の方法は、GUIスレッドをブロックしないことです。
- 各ステップの後に
progressUpdated
シグナルを発行し、UIのプログレスバーを更新しています。これにより、UIがフリーズすることなく進行状況が表示されます。 doNextStep()
内でQThread::msleep(10);
を呼び出しているのは、処理の重さをシミュレートするためです。実際には、この部分に時間のかかるコードが入ります。QTimer::singleShot(0, ...)
を使って、doNextStep()
メソッドを繰り返し呼び出しています。start(0)
は「イベントキューに他のイベントがなければすぐに」という意味です。WorkerObject
クラスは、本来別のスレッドで行うべき「重い処理」を模倣しています。
QObject::startTimer() と QObject::timerEvent()
これは、QTimer
が内部的に使用している、より低レベルなタイマーの仕組みです。
特徴
- シングルショットタイマーの機能は提供されません(自分で実装する必要があります)。
QTimer
のようにシグナル/スロットを自動的に提供しないため、イベントの種類をevent->timerId()
で判別する必要があります。- タイマーがタイムアウトすると、そのクラスの
timerEvent(QTimerEvent *event)
メソッドが呼び出されます。このメソッドをオーバーライドして処理を記述します。 startTimer(int interval)
を呼び出すと、タイマーIDが返されます。QObject
を継承する任意のクラスで使用できます。
利点
- 同じオブジェクト内で複数のタイマーを管理する場合に、タイマーIDで区別できます。
- オーバーヘッドがわずかに少ない可能性があります(しかし、通常は体感できるほどの差はありません)。
欠点
- シングルショットタイマーの機能がないため、複雑なロジックを自分で書く必要があります。
- シグナル/スロット機構が使えないため、コードが冗長になりがちです。
使用例
#include <QCoreApplication>
#include <QObject>
#include <QTimerEvent>
#include <QDebug>
class MyCustomTimer : public QObject
{
Q_OBJECT
public:
explicit MyCustomTimer(QObject *parent = nullptr) : QObject(parent)
{
// 1秒間隔のタイマーを開始し、タイマーIDを保持
m_timerId = startTimer(1000);
qDebug() << "カスタムタイマーが開始されました (ID:" << m_timerId << ")";
// 3秒後に一度だけ実行するタイマー (timerEvent内でIDをチェックして停止)
m_singleShotTimerId = startTimer(3000);
qDebug() << "シングルショット風タイマーが開始されました (ID:" << m_singleShotTimerId << ")";
}
protected:
void timerEvent(QTimerEvent *event) override
{
if (event->timerId() == m_timerId) {
qDebug() << "通常タイマーがタイムアウトしました!";
} else if (event->timerId() == m_singleShotTimerId) {
qDebug() << "シングルショット風タイマーがタイムアウトしました!";
killTimer(m_singleShotTimerId); // 一度だけ実行したら停止
}
}
private:
int m_timerId;
int m_singleShotTimerId;
};
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
MyCustomTimer myTimer;
QTimer::singleShot(5000, qApp, &QCoreApplication::quit); // 5秒後にアプリケーションを終了
return a.exec();
}
#include "main.moc"
いつ使うか
非常に低レベルなタイマー制御が必要な場合や、特定の古いコードベースでこのパターンが使われている場合に限られます。通常は QTimer
が推奨されます。
QBasicTimer
QBasicTimer
は、QObject::startTimer()
のようにタイマーIDを扱う代わりに、タイマーの状態をカプセル化した軽量なクラスです。
特徴
QTimer
よりも高速で、より多くのタイマーを扱う場合に適しているとされています(ただし、これも体感できるほどの差は稀です)。timerEvent()
メソッド内でイベントを処理します。isActive()
でタイマーがアクティブか確認できます。start()
とstop()
メソッドを持ちます。
利点
QTimer
よりわずかに軽量です。QObject::startTimer()
よりもオブジェクト指向的です。
欠点
- シングルショットタイマーの機能はありません。
QTimer
のようにシグナル/スロットを自動的に提供しないため、timerEvent()
をオーバーライドする必要があります。
使用例
#include <QCoreApplication>
#include <QObject>
#include <QBasicTimer>
#include <QTimerEvent>
#include <QDebug>
class MyBasicTimerObject : public QObject
{
Q_OBJECT
public:
explicit MyBasicTimerObject(QObject *parent = nullptr) : QObject(parent)
{
// QBasicTimerオブジェクトを作成
m_basicTimer.start(1000, this); // 1秒間隔で、このオブジェクトのtimerEventを呼び出す
qDebug() << "QBasicTimerが開始されました。";
}
protected:
void timerEvent(QTimerEvent *event) override
{
if (event->timerId() == m_basicTimer.timerId()) {
qDebug() << "QBasicTimerがタイムアウトしました!";
}
// 他のタイマーイベントも処理する場合、else if などで判別
}
private:
QBasicTimer m_basicTimer; // QBasicTimerはQObjectを継承しないため、ポインタではなく直接持つことが多い
};
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
MyBasicTimerObject myBasicTimer;
QTimer::singleShot(5000, qApp, &QCoreApplication::quit);
return a.exec();
}
#include "main.moc"
いつ使うか
QObject::timerEvent()
を使うのと似ていますが、タイマーの管理が少しだけ楽になります。やはり、シグナル/スロットが必要ない、またはパフォーマンスが非常にクリティカルな場合に検討されることがあります。
イベントループ内での遅延処理 (非推奨)
これは厳密にはタイマーではありませんが、イベントループをブロックせずに一時停止する方法として誤解されることがあります。
方法
QCoreApplication::processEvents()
(非GUIアプリケーションの場合)QApplication::processEvents()
(GUIアプリケーションの場合)QThread::msleep(milliseconds)
特徴
processEvents()
は、現在キューにあるイベントをすべて処理します。これにより、UIの更新や他のイベントが一時的に処理されますが、指定した時間だけ「待つ」というタイマーの機能とは異なります。ループ内でこれを使用すると、ポーリングのような形になり、CPUリソースを無駄に消費する可能性があります。QThread::msleep()
は現在のスレッドを指定した時間だけブロックします。これはGUIスレッドで行うとアプリケーションがフリーズします。
利点
- デバッグや非常にシンプルな一時的な処理(例: ユニットテストでの短い待ち時間)に限定的に使用できる。
欠点
- CPU使用率が高くなる可能性があります。
- リアルタイム性や正確な時間間隔は保証されません。
- GUIスレッドをブロックする危険性が非常に高いため、アプリケーションの応答性を著しく低下させます。
いつ使うか
通常のアプリケーション開発では、これらの方法をGUIスレッドで定期的な遅延やタイムアウトを実現するために使用すべきではありません。 デバッグやテスト、非常に特殊な同期処理に限定されます。時間のかかる処理は必ず別スレッドで行うべきです。
スレッド (QThread) との組み合わせ
これはタイマーの直接の代替ではありませんが、GUIをブロックしないタイマーのような動作を実現する最も堅牢な方法です。
方法
- 処理の進捗や結果は、スレッドからメインスレッドにシグナルで通知します。
- そのスレッド内で、
QTimer
を使用するか、または直接std::this_thread::sleep_for()
やOSネイティブのタイマー機能を使用して、時間のかかる処理を実行します。 - メインスレッド(GUIスレッド)とは別の
QThread
を作成します。
利点
- より複雑なバックグラウンド処理に対応できます。
- 重い処理がメインスレッドから分離されるため、アプリケーション全体の安定性が向上します。
- UIの応答性を完全に維持できます。
欠点
QThread
の正しい使い方(オブジェクトをスレッドに移動するmoveToThread()
など)を理解する必要があります。- マルチスレッドプログラミングの複雑さ(スレッドセーフなアクセス、同期など)が伴います。
いつ使うか
時間のかかる計算、ファイルI/O、ネットワーク通信など、メインスレッドを長時間ブロックする可能性のあるすべての処理で推奨されます。これは、GUIアプリケーションでタイマーのような「定期的なバックグラウンド処理」を実現する際の最も現代的で推奨されるアプローチです。
これは QTimer
の後継として導入された、より高精度で広範囲のタイマーです。
特徴
QTimer
と同様にシグナル/スロットを使用します。QTimer
よりも広い時間範囲(約292年 vs 約24日)と、より細かい精度(ナノ秒単位)をサポートします。std::chrono
ライブラリの概念に基づいています。
利点
std::chrono
の型システムを利用して、より型安全な時間指定が可能です。- 非常に長い期間のタイマーや、非常に高い精度が必要な場合に適しています。
欠点
- 一般的な用途では
QTimer
で十分な場合が多いです。 - Qt 6.8以降のバージョンでしか利用できません。
いつ使うか
Qt 6.8以降を使用しており、QTimer
の最大間隔や精度では不十分な、特殊な要件がある場合に検討します。
ほとんどのQtアプリケーションでは、シンプルで安全な QTimer
がタイマー機能の主要な選択肢となります。
- Qt 6.8以降で、より高精度・広範囲のタイマーが必要な場合
QChronoTimer
を検討します。 - 非常に低レベルなタイマー制御や、パフォーマンスが極めてクリティカルな場合(稀)
QObject::startTimer()
/QObject::timerEvent()
またはQBasicTimer
を検討することもあります。 - 重い処理をバックグラウンドで実行し、GUIをブロックしたくない場合
QThread
とQTimer
の組み合わせ、またはQtConcurrent
を使用すべきです。 - GUIの応答性を保ちつつ、定期的な処理や遅延実行を行いたい場合
QTimer
が第一の選択肢です。