「Qtの基礎 - シグナルとスロット」の版間の差分
細 文字列「__FORCETOC__」を「{{#seo: |title={{PAGENAME}} : Exploring Electronics and SUSE Linux | MochiuWiki |keywords=MochiuWiki,Mochiu,Wiki,Mochiu Wiki,Electric Circuit,Electric,pcb,Mathematics,AVR,TI,STMicro,AVR,ATmega,MSP430,STM,Arduino,Xilinx,FPGA,Verilog,HDL,PinePhone,Pine Phone,Raspberry,Raspberry Pi,C,C++,C#,Qt,Qml,MFC,Shell,Bash,Zsh,Fish,SUSE,SLE,Suse Enterprise,Suse Linux,openSUSE,open SUSE,Leap,Linux,uCLnux,Podman,電気回路,電子回路,基板,プリント基板 |description={{PAGENAME}} - 電子回路とSUSE Linuxに関する情報 | This pag… |
編集の要約なし |
||
| 1行目: | 1行目: | ||
== 概要 == | == 概要 == | ||
Qtのシグナル・スロット機構は、オブジェクト間の通信を実現する強力なメカニズムである。<br> | |||
シグナルは、特定のイベントが発生した時に発行され、スロットはシグナルを受信して処理を実行する関数である。<br> | |||
<br><br> | |||
== シグナルとスロットの基本概念 == | |||
<code>connect</code> 関数を使用して、シグナルとスロットの接続を行う。<br> | |||
<syntaxhighlight lang="c++"> | |||
connect(sender, SIGNAL(signal), receiver, SLOT(slot)); | |||
</syntaxhighlight> | |||
<br> | |||
下表に、<code>connect</code> 関数の引数の意味を示す。<br> | |||
<br> | |||
<center> | |||
{| class="wikitable" | |||
|+ 引数 (パラメータ) | |||
|- | |||
! 項目 !! 説明 | |||
|- | |||
| sender || シグナルが発生するコントロールIDまたはクラスのアドレスを渡す。 | |||
|- | |||
| SIGNAL<br>(signal) || signalにシグナルとする関数名を渡す。<br>例 : プッシュボタンの場合、<code>SIGNAL(clicked())</code> と記述する。 | |||
|- | |||
| receiver || シグナルを受信するコントロールIDまたはクラスのアドレスを渡す。 | |||
|- | |||
| SLOT(slot) || シグナルを受信した時に呼び出す関数名を渡す。 | |||
|} | |||
</center> | |||
<br><br> | <br><br> | ||
== connect関数 == | == connect関数 == | ||
==== Qt::ConnectionType ==== | ==== Qt::ConnectionType ==== | ||
<code>connect</code>関数の<code>Qt::ConnectionType</code>の設定により動作が異なる。<br> | <code>connect</code> 関数の <code>Qt::ConnectionType</code> の設定により動作が異なる。<br> | ||
<br> | |||
<center> | <center> | ||
{| class="wikitable | {| class="wikitable" | ||
|- | |- | ||
! | ! 接続タイプ(定数) !! 動作 | ||
! | |||
|- | |- | ||
| Qt::AutoConnection || デフォルトの設定である。<br>SenderとReceiverが同じスレッドに存在する場合は、<code>Qt:: | | Qt::AutoConnection || デフォルトの設定である。<br>SenderとReceiverが同じスレッドに存在する場合は、<code>Qt::DirectConnection</code>が使用される。<br>それ以外の場合は、<code>Qt::QueuedConnection</code>が使用される。<br><br>接続タイプはシグナルを<code>emit</code>する時に決定する。 | ||
|- | |- | ||
| Qt:: | | Qt::DirectConnection || シグナルを呼び出したスレッドから対象のスロットを呼び出す。(同期呼び出し) | ||
|- | |- | ||
| Qt::QueuedConnection || Receiverのスレッド上でスロット関数が呼び出される。<br>シグナルはReceiver側のキューに入れられて、Receiverのイベントルーパーからスロット関数が呼び出される。(非同期呼び出し) | | Qt::QueuedConnection || Receiverのスレッド上でスロット関数が呼び出される。<br>シグナルはReceiver側のキューに入れられて、Receiverのイベントルーパーからスロット関数が呼び出される。(非同期呼び出し) | ||
| 22行目: | 47行目: | ||
|- | |- | ||
| Qt::UniqueConnection || 他の接続と組み合わせてビットORで設定する接続タイプである。<br>この設定が指定されている場合、オブジェクト間のシグナルとスロット、または、シグナルとシグナルの接続の組み合わせは1度しかできなくなる。<br>2つ以上同じシグナル・スロットの組み合わせで<code>connect</code>関数を実行した場合、<code>connect</code>関数は失敗する。<br><br>この設定は、Qt 4.6で追加された。 | | Qt::UniqueConnection || 他の接続と組み合わせてビットORで設定する接続タイプである。<br>この設定が指定されている場合、オブジェクト間のシグナルとスロット、または、シグナルとシグナルの接続の組み合わせは1度しかできなくなる。<br>2つ以上同じシグナル・スロットの組み合わせで<code>connect</code>関数を実行した場合、<code>connect</code>関数は失敗する。<br><br>この設定は、Qt 4.6で追加された。 | ||
|- | |||
| Qt::SingleShotConnection || スロットはシグナルが発行されるたびに呼び出され、その後自動的に接続が切断される。<br><br>この設定は、Qt 6.0で追加された。 | |||
|} | |} | ||
</center> | </center> | ||
<br> | <br> | ||
==== | ==== connect関数のシンタックス ==== | ||
<code>connect</code>関数は大きく分けて、2種類、細かく分けて4種類の種類が存在する。<br> | <code>connect</code> 関数は大きく分けて、2種類、細かく分けて4種類の種類が存在する。<br> | ||
<br> | |||
<center> | <center> | ||
{| class="wikitable | {| class="wikitable" | ||
|- | |- | ||
! | ! connect関数のシンタックス !! スタイルの呼称 | ||
! | |||
|- | |- | ||
| Stringベース || Qt 4スタイル | | Stringベース || Qt 4スタイル | ||
| 38行目: | 65行目: | ||
| Qt 5スタイル | | Qt 5スタイル | ||
|- | |- | ||
| | | ラムダスタイル 1 | ||
|- | |- | ||
| | | ラムダスタイル 2 | ||
|} | |} | ||
</center> | </center> | ||
| 46行目: | 73行目: | ||
<syntaxhighlight lang="c++"> | <syntaxhighlight lang="c++"> | ||
// Qt 4スタイルのconnect関数の形式 | // Qt 4スタイルのconnect関数の形式 | ||
QMetaObject::Connection QObject::connect(Senderのポインタ, SIGNAL(シグナルの関数名(引数の型, ...)), Receiverのポインタ, SLOT(スロットの関数名(引数の型, ...)), Qt::ConnectionType type = Qt::AutoConnection) | QMetaObject::Connection QObject::connect(Senderのポインタ, SIGNAL(シグナルの関数名(引数の型, ...)), | ||
Receiverのポインタ, SLOT(スロットの関数名(引数の型, ...)), | |||
Qt::ConnectionType type = Qt::AutoConnection) | |||
// 例. | // 例. | ||
connect(sender, SIGNAL(value3Changed(int)), receiver, SLOT(setValue3(int))); | connect(sender, SIGNAL(value3Changed(int)), receiver, SLOT(setValue3(int))); | ||
// Qt 5スタイルのconnect関数の形式 | // Qt 5スタイルのconnect関数の形式 | ||
QMetaObject::Connection QObject::connect(Senderのポインタ, &シグナルのクラス名::シグナルの関数名, Receiverのポインタ, &スロットのクラス名::スロットの関数名, Qt::ConnectionType type = Qt::AutoConnection) | QMetaObject::Connection QObject::connect(Senderのポインタ, &シグナルのクラス名::シグナルの関数名, | ||
Receiverのポインタ, &スロットのクラス名::スロットの関数名, | |||
Qt::ConnectionType type = Qt::AutoConnection) | |||
// 例. | // 例. | ||
| 59行目: | 89行目: | ||
</syntaxhighlight> | </syntaxhighlight> | ||
<br> | <br> | ||
<code>connect</code>関数の第2引数と第4引数において、Qt 4スタイルでは<code>SIGNAL</code> | <code>connect</code> 関数の第2引数と第4引数において、Qt 4スタイルでは <code>SIGNAL</code> マクロ、<code>SLOT</code> マクロを使用する。<br> | ||
このマクロは、Qt側で文字列 (const char *signalのように) に変換される。<br> | |||
Qt | <br> | ||
Qt 5スタイルと異なる事柄を以下に示す。<br> | |||
* マクロの有無 | * マクロの有無 | ||
* クラス名を記述するかどうか | * クラス名を記述するかどうか | ||
| 67行目: | 98行目: | ||
<br> | <br> | ||
<syntaxhighlight lang="c++"> | <syntaxhighlight lang="c++"> | ||
// | // ラムダスタイル1のconnect関数の形式 | ||
QMetaObject::Connection connect(Senderのポインタ, &Senderのクラス名::シグナルの関数名, [=]() { 処理1; 処理2; ...; }); | QMetaObject::Connection connect(Senderのポインタ, &Senderのクラス名::シグナルの関数名, [=]() { 処理1; 処理2; ...; }); | ||
| 73行目: | 104行目: | ||
connect(sender, &Sender::valueChanged, [=]() { qDebug() << "Lambda Style1 signal received."; }); | connect(sender, &Sender::valueChanged, [=]() { qDebug() << "Lambda Style1 signal received."; }); | ||
// ラムダスタイル2のconnect関数の形式 | |||
// | QMetaObject::Connection connect(Senderのポインタ, &Senderのクラス名::シグナルの関数名, | ||
QMetaObject::Connection connect(Senderのポインタ, &Senderのクラス名::シグナルの関数名, コンテキストオブジェクト, [=]() { 処理1; 処理2; ...; }, Qt::QueuedConnection); | コンテキストオブジェクト, [=]() { 処理1; 処理2; ...; }, | ||
Qt::QueuedConnection); | |||
// 例. | // 例. | ||
connect(sender, &MyObject1::valueChanged, this, [=]() { qDebug() << "Lambda Style2 signal received."; }, Qt::QueuedConnection); | connect(sender, &MyObject1::valueChanged, | ||
this, [=]() { qDebug() << "Lambda Style2 signal received."; }, | |||
Qt::QueuedConnection); | |||
</syntaxhighlight> | </syntaxhighlight> | ||
<br> | <br> | ||
ラムダ形式は、Receiverのスロット関数を作成しなくてよいため、シグナルが発生しているかどうかログを出力する時に便利である。<br> | |||
<br> | |||
ラムダスタイル1は、引数に <code>Qt::ConnectionType</code> が無い。<br> | |||
また、<code>Qt::ConnectionType</code> が <code>Qt::DirectConnection</code> 固定となる。<br> | |||
<br> | <br> | ||
つまり、SenderとReceiverが同一スレッドで動作することが前提となる。<br> | つまり、SenderとReceiverが同一スレッドで動作することが前提となる。<br> | ||
<code>send</code>関数により、Senderを取得することもできない。<br> | <code>send</code> 関数により、Senderを取得することもできない。<br> | ||
<br> | |||
ラムダスタイル2は、Qt 5.2で追加されたシンタックスである。<br> | |||
ラムダスタイル1と比較すると、第3引数にコンテキストが追加されて、第5引数に <code>Qt::ConnectionType</code> が追加されている。<br> | |||
<code>Qt::ConnectionType</code> が指定できるため、<code>Qt::DirectConnection</code> 以外の動作も可能である。<br> | |||
<br> | <br> | ||
また、<code>QObject::sender</code> 関数も使用できる。<br> | |||
また、<code>QObject::sender</code>関数も使用できる。<br> | |||
<br> | <br> | ||
上記のラムダスタイル1 および ラムダスタイル2の例では、ラムダ式に <code>キャプチャ式 [=]</code> を使用しているが、<code>キャプチャ式 [=]</code> 以外のキャプチャ記法も使用できる。<br> | |||
<br> | <br> | ||
<center> | <center> | ||
{| class="wikitable | {| class="wikitable" | ||
|+ StringベースとFunctorベースの比較 | |+ StringベースとFunctorベースの比較 | ||
|- | |- | ||
! style=" | ! style="width:50%;" | | ||
! style=" | ! style="width:25%;" | Stringベース | ||
! style=" | ! style="width:25%;" | Functorベース | ||
|- | |- | ||
| 型チェックのタイミング | | 型チェックのタイミング | ||
| style="text-align: center;" | 実行時 | | style="text-align: center;" | 実行時 | ||
| style="text-align: center;" | コンパイル時 | | style="text-align: center;" | コンパイル時 | ||
|- | |- | ||
| 暗黙の型変換 | | 暗黙の型変換 | ||
| style="text-align: center;" | 不可 | | style="text-align: center;" | 不可 | ||
| style="text-align: center;" | 可能 | | style="text-align: center;" | 可能 | ||
|- | |- | ||
| シグナルをラムダ式で接続できる | | シグナルをラムダ式で接続できる | ||
| style="text-align: center;" | 不可 | | style="text-align: center;" | 不可 | ||
| style="text-align: center;" | 可能 | | style="text-align: center;" | 可能 | ||
|- | |- | ||
| シグナルより多くの引数を持つスロットに<br>シグナルを接続できる | | シグナルより多くの引数を持つスロットに<br>シグナルを接続できる | ||
| style="text-align: center;" | 可能 | | style="text-align: center;" | 可能 | ||
| style="text-align: center;" | 不可 | | style="text-align: center;" | 不可 | ||
|- | |- | ||
| QMLに接続できる | | QMLに接続できる | ||
| style="text-align: center;" | 可能 | | style="text-align: center;" | 可能 | ||
| style="text-align: center;" | 可能 | | style="text-align: center;" | 可能 | ||
|} | |} | ||
</center> | </center> | ||
<br> | |||
==== オーバーロードされたシグナルの処理 ==== | |||
シグナルがオーバーロードされている場合、<code>connect</code> 関数で適切なシグナルを指定するために、以下に示すいずれかの方法を使用する。<br> | |||
<br> | |||
===== qOverloadヘルパー関数 (C++ 14以降 推奨) ===== | |||
<syntaxhighlight lang="c++"> | |||
connect(sender, qOverload<int>(&Sender::valueChanged), receiver, &Receiver::setValue); | |||
</syntaxhighlight> | |||
<br> | |||
===== QOverload::of (C++ 11対応) ===== | |||
<syntaxhighlight lang="c++"> | |||
connect(sender, QOverload<int>::of(&Sender::valueChanged), receiver, &Receiver::setValue); | |||
</syntaxhighlight> | |||
<br> | |||
===== static_cast (C++ 03以前) ===== | |||
<syntaxhighlight lang="c++"> | |||
connect(sender, static_cast<void (Sender::*)(int)>(&Sender::valueChanged), receiver, &Receiver::setValue); | |||
</syntaxhighlight> | |||
<br><br> | <br><br> | ||
== | == disconnect関数 == | ||
シグナルとスロットの接続を切断する場合、<code>disconnect</code> 関数を使用する。<br> | |||
<br> | |||
==== QMetaObject::Connectionを使用した切断 (推奨) ==== | |||
<code>connect</code> 関数は <code>QMetaObject::Connection</code> オブジェクトを返すため、このオブジェクトを保持しておき、<code>disconnect</code> 関数で切断する。<br> | |||
<br> | |||
<syntaxhighlight lang="c++"> | |||
QMetaObject::Connection conn = connect(sender, &Sender::valueChanged, receiver, &Receiver::setValue); | |||
// 接続を切断 | |||
disconnect(conn); | |||
</syntaxhighlight> | |||
<br> | |||
==== 関数ポインタを使用した切断 ==== | |||
<code>connect</code> 関数と対称的に、関数ポインタを指定して切断する。<br> | |||
<br> | |||
<syntaxhighlight lang="c++"> | |||
disconnect(sender, &Sender::valueChanged, receiver, &Receiver::setValue); | |||
</syntaxhighlight> | |||
<br> | |||
==== 全ての接続を切断 ==== | |||
特定のオブジェクトの全ての接続を切断する場合、引数にオブジェクトのみを指定する。<br> | |||
<br> | |||
<syntaxhighlight lang="c++"> | <syntaxhighlight lang="c++"> | ||
// senderから発信される全ての接続を切断 | |||
disconnect(sender, nullptr, nullptr, nullptr); | |||
// receiverが受信する全ての接続を切断 | |||
disconnect(nullptr, nullptr, receiver, nullptr); | |||
</syntaxhighlight> | |||
<br> | |||
==== オブジェクト破棄時の自動切断 ==== | |||
<code>QObject</code> が破棄される時、そのオブジェクトに関連する全ての接続は自動的に切断される。<br> | |||
手動で <code>disconnect</code> 関数を呼び出す必要はない。<br> | |||
<br><br> | |||
== sender()関数 == | |||
スロット実行中に、シグナルを発信したオブジェクトを取得する場合、<code>QObject::sender</code>関数を使用する。<br> | |||
<syntaxhighlight lang="c++"> | |||
void MyClass::handleSignal() | |||
{ | |||
QObject *senderObj = sender(); | |||
if (senderObj) { | |||
qDebug() << "Signal sent from:" << senderObj->objectName(); | |||
} | |||
} | |||
</syntaxhighlight> | </syntaxhighlight> | ||
<br> | <br> | ||
==== sender関数の制限事項 ==== | |||
* | * スロット実行中のみ有効 | ||
*: | *: スロット関数の外部で呼び出すと、<code>nullptr</code> を返す。 | ||
* | * DirectConnectionでのみ安全 | ||
*: | *: <code>Qt::DirectConnection</code> では、正しくSenderオブジェクトを取得できる。 | ||
* QueuedConnectionでの使用時の注意 | |||
* | *: <code>Qt::QueuedConnection</code> では、スロットが実行される時点でSenderオブジェクトが既に破棄されている可能性がある。 | ||
*: | |||
<br><br> | <br><br> | ||
== | == スレッド間通信 == | ||
Qtのシグナル・スロット機構は、スレッド間の通信に安全に使用できる。<br> | |||
<br> | |||
==== スレッド間通信の動作 ==== | |||
異なるスレッド間でシグナル・スロットを接続する場合、以下の動作となる。<br> | |||
* <code>Qt::AutoConnection</code> (デフォルト) | |||
*: Senderと Receiverが異なるスレッドの場合、自動的に <code>Qt::QueuedConnection</code> が使用される。 | |||
*: スロットはReceiverのスレッドで非同期に実行される。 | |||
* <code>Qt::QueuedConnection</code> | |||
*: シグナルはReceiverのイベントキューに格納され、Receiverのスレッドで実行される。 | |||
*: 引数はコピーされるため、引数はコピー可能な型である必要がある。 | |||
* <code>Qt::BlockingQueuedConnection</code> | |||
*: Senderのスレッドは、Receiverのスロット実行が完了するまで待機する。 | |||
*: デッドロックのリスクがあるため、使用には注意が必要である。 | |||
<br> | |||
==== スレッド間通信のベストプラクティス ==== | |||
* GUI操作は常にメインスレッドから | |||
*: GUIウィジェットの操作は、メインスレッド (UIスレッド) からのみ実行する。 | |||
*: ワーカースレッドからGUIを直接操作してはならない。 | |||
*: <syntaxhighlight lang="c++"> | |||
// ワーカースレッドからシグナルを発信して、メインスレッドでGUI更新 | |||
connect(worker, &Worker::resultReady, this, &MainWindow::updateUI, Qt::QueuedConnection); | |||
</syntaxhighlight> | |||
*: <br> | |||
* QObject::moveToThreadメソッドの使用 | |||
*: オブジェクトを別のスレッドに移動する場合、<code>moveToThread</code> 関数を使用する。 | |||
*: <syntaxhighlight lang="c++"> | |||
QThread *thread = new QThread; | |||
Worker *worker = new Worker; | |||
worker->moveToThread(thread); | |||
connect(thread, &QThread::started, worker, &Worker::process); | |||
connect(worker, &Worker::finished, thread, &QThread::quit); | |||
connect(thread, &QThread::finished, thread, &QThread::deleteLater); | |||
thread->start(); | |||
</syntaxhighlight> | |||
*: <br> | |||
* deleteLaterメソッドによる安全な破棄 | |||
*: スレッド上のオブジェクトを破棄する場合、<code>deleteLater</code> メソッドを使用する。 | |||
*: <code>delete</code> 演算子を直接使用すると、スレッド実行中に破棄される可能性がある。 | |||
*: <syntaxhighlight lang="c++"> | |||
connect(worker, &Worker::finished, worker, &QObject::deleteLater); | |||
</syntaxhighlight> | |||
*: <br> | |||
* カスタム型のqRegisterMetaType登録 | |||
*: カスタム型をシグナル・スロットの引数として使用する場合、<code>qRegisterMetaType</code> 関数で登録する必要がある。 | |||
*: <syntaxhighlight lang="c++"> | |||
qRegisterMetaType<MyCustomType>("MyCustomType"); | |||
connect(sender, &Sender::customSignal, receiver, &Receiver::customSlot, Qt::QueuedConnection); | |||
</syntaxhighlight> | |||
<br><br> | |||
== connect関数の使用例 == | |||
==== 基本的な使用例 ==== | |||
<syntaxhighlight lang="c++"> | <syntaxhighlight lang="c++"> | ||
#include "mainwindow.h" | #include "mainwindow.h" | ||
| 161行目: | 308行目: | ||
} | } | ||
</syntaxhighlight> | </syntaxhighlight> | ||
<br> | |||
==== 静的メンバ関数のconnect ==== | |||
== 静的メンバ関数のconnect == | |||
静的メンバ関数は、インスタンスを生成せずに実行することができる。<br> | 静的メンバ関数は、インスタンスを生成せずに実行することができる。<br> | ||
通常のメンバ関数の呼び出しと同様、<code>this</code> ポインタからシグナルを発信して、<code>this</code> ポインタでシグナルを受ければよい。<br> | |||
<br> | <br> | ||
また、送信側と受信側の両方がthisの場合、受信側インスタンスの指定を省略できる <code>connect</code> 関数のオーバーロード関数も存在する。<br> | |||
<br> | <br> | ||
以下の例では、コンストラクタでシグナルとスロットを接続して、画面が表示された時、showEventメソッドでシグナルをemitしている。<br> | 以下の例では、コンストラクタでシグナルとスロットを接続して、画面が表示された時、showEventメソッドでシグナルをemitしている。<br> | ||
また、送信側のクラスのインスタンスは生成していない。 | また、送信側のクラスのインスタンスは生成していない。<br> | ||
<br> | |||
<syntaxhighlight lang="c++"> | <syntaxhighlight lang="c++"> | ||
// MainWindow.cpp | // MainWindow.cpp | ||
| 250行目: | 397行目: | ||
== Qt Designerで設定したシグナルおよびスロット == | == Qt Designerで設定したシグナルおよびスロット == | ||
Qt Designerで設定したシグナルおよびスロットは、<code>connect</code>関数を使用せずに呼び出すことができる。<br> | Qt Designerで設定したシグナルおよびスロットは、<code>connect</code> 関数を使用せずに呼び出すことができる。<br> | ||
<br> | |||
ソースコード上で <code>connect</code> 関数を使用せずにスロット関数が呼び出される理由を以下に示す。<br> | |||
まず、<code>QWidget</code> クラス あるいは <code>QMainWindow</code> クラス等を継承した派生クラスにおいて、コンストラクタに自動生成されるソースコードで、以下に示す記述がある。<br> | |||
<br> | <br> | ||
<syntaxhighlight lang="c++"> | |||
ui->setupUi(this); | ui->setupUi(this); | ||
</syntaxhighlight> | |||
<br> | |||
次に、<code>setupUi</code> メソッドの定義の最後に、以下の記述がある。<br> | |||
以下に示す <code>QMetaObject::connectSlotsByName</code> メソッドは、オブジェクト (引数) が持つ全てのスロットに対して、<br> | |||
<code>on_<子オブジェクト名>_<子オブジェクトのシグナル名></code> を満たすスロット名の存在を確認する。<br> | |||
<br> | <br> | ||
<syntaxhighlight lang="c++"> | <syntaxhighlight lang="c++"> | ||
void setupUi(QMainWindow *MainWindow) | void setupUi(QMainWindow *MainWindow) | ||
| 268行目: | 419行目: | ||
</syntaxhighlight> | </syntaxhighlight> | ||
<br><br> | <br><br> | ||
== トラブルシューティング == | |||
==== Q_OBJECTマクロの確認 ==== | |||
シグナル・スロット機構を使用するクラスには、<code>Q_OBJECT</code> マクロが必要である。<br> | |||
<code>Q_OBJECT</code> マクロが無い場合、コンパイルエラーが発生する。<br> | |||
<br> | |||
<syntaxhighlight lang="c++"> | |||
class MyClass : public QObject | |||
{ | |||
Q_OBJECT // このマクロが必要 | |||
public: | |||
MyClass(QObject *parent = nullptr); | |||
signals: | |||
void mySignal(); | |||
}; | |||
</syntaxhighlight> | |||
<br> | |||
==== connect関数の戻り値の確認 ==== | |||
<code>connect</code> 関数は、接続が成功した場合に有効な <code>QMetaObject::Connection</code> を返し、失敗した場合は無効な接続を返す。<br> | |||
接続が失敗した場合、コンソールに警告メッセージが出力される。<br> | |||
<br> | |||
<syntaxhighlight lang="c++"> | |||
QMetaObject::Connection conn = connect(sender, &Sender::signal, receiver, &Receiver::slot); | |||
if (!conn) { | |||
qWarning() << "Connection failed!"; | |||
} | |||
</syntaxhighlight> | |||
<br> | |||
==== ラムダ式でのメモリリーク防止 ==== | |||
ラムダ式を使用する場合、コンテキストオブジェクトを指定することで、メモリリークを防止できる。<br> | |||
コンテキストオブジェクトが破棄されると、接続が自動的に切断される。<br> | |||
<br> | |||
<syntaxhighlight lang="c++"> | |||
// コンテキストオブジェクトを指定 (推奨) | |||
connect(sender, &Sender::signal, this, [this]() { | |||
// thisが破棄されると、この接続も自動的に切断される | |||
}); | |||
// コンテキストオブジェクトを指定しない (非推奨) | |||
connect(sender, &Sender::signal, [this]() { | |||
// thisが破棄されても、この接続は残り続ける (メモリリーク) | |||
}); | |||
</syntaxhighlight> | |||
<br> | |||
==== シグナル・スロットが呼ばれない場合の確認項目 ==== | |||
* <code>Q_OBJECT</code> マクロがクラス定義に含まれているか | |||
*: <code>Q_OBJECT</code> マクロが無いと、MOC (Meta-Object Compiler) が実行されず、シグナル・スロット機構が動作しない。 | |||
* <code>connect</code> 関数の戻り値が有効か | |||
*: 接続が失敗している場合、コンソールに警告メッセージが出力されるため、確認する。 | |||
* シグナルとスロットの引数が一致しているか | |||
*: Functorベースでは、引数の型が厳密にチェックされる。 | |||
* オブジェクトが破棄されていないか | |||
*: SenderまたはReceiverが破棄されると、接続は自動的に切断される。 | |||
* イベントループが実行されているか | |||
*: <code>Qt::QueuedConnection</code> では、イベントループが実行されていないと、スロットが呼ばれない。 | |||
<br><br> | |||
{{#seo: | {{#seo: | ||
2026年2月1日 (日) 10:29時点における版
概要
Qtのシグナル・スロット機構は、オブジェクト間の通信を実現する強力なメカニズムである。
シグナルは、特定のイベントが発生した時に発行され、スロットはシグナルを受信して処理を実行する関数である。
シグナルとスロットの基本概念
connect 関数を使用して、シグナルとスロットの接続を行う。
connect(sender, SIGNAL(signal), receiver, SLOT(slot));
下表に、connect 関数の引数の意味を示す。
| 項目 | 説明 |
|---|---|
| sender | シグナルが発生するコントロールIDまたはクラスのアドレスを渡す。 |
| SIGNAL (signal) |
signalにシグナルとする関数名を渡す。 例 : プッシュボタンの場合、 SIGNAL(clicked()) と記述する。
|
| receiver | シグナルを受信するコントロールIDまたはクラスのアドレスを渡す。 |
| SLOT(slot) | シグナルを受信した時に呼び出す関数名を渡す。 |
connect関数
Qt::ConnectionType
connect 関数の Qt::ConnectionType の設定により動作が異なる。
| 接続タイプ(定数) | 動作 |
|---|---|
| Qt::AutoConnection | デフォルトの設定である。 SenderとReceiverが同じスレッドに存在する場合は、 Qt::DirectConnectionが使用される。それ以外の場合は、 Qt::QueuedConnectionが使用される。接続タイプはシグナルを emitする時に決定する。
|
| Qt::DirectConnection | シグナルを呼び出したスレッドから対象のスロットを呼び出す。(同期呼び出し) |
| Qt::QueuedConnection | Receiverのスレッド上でスロット関数が呼び出される。 シグナルはReceiver側のキューに入れられて、Receiverのイベントルーパーからスロット関数が呼び出される。(非同期呼び出し) |
| Qt::BlockingQueuedConnection | 基本的な動作は、Qt::QueuedConnectionと同様であるが、 シグナルの呼び出した側は、受信側のスロットの実行終了を待ち合わせる。 多用するとデッドロックを招きやすくなる。 例えば、Receiver側がSender側と同じスレッドに存在する場合には、必ず、デッドロックすることに注意する。 |
| Qt::UniqueConnection | 他の接続と組み合わせてビットORで設定する接続タイプである。 この設定が指定されている場合、オブジェクト間のシグナルとスロット、または、シグナルとシグナルの接続の組み合わせは1度しかできなくなる。 2つ以上同じシグナル・スロットの組み合わせで connect関数を実行した場合、connect関数は失敗する。この設定は、Qt 4.6で追加された。 |
| Qt::SingleShotConnection | スロットはシグナルが発行されるたびに呼び出され、その後自動的に接続が切断される。 この設定は、Qt 6.0で追加された。 |
connect関数のシンタックス
connect 関数は大きく分けて、2種類、細かく分けて4種類の種類が存在する。
| connect関数のシンタックス | スタイルの呼称 |
|---|---|
| Stringベース | Qt 4スタイル |
| Functorベース | Qt 5スタイル |
| ラムダスタイル 1 | |
| ラムダスタイル 2 |
// Qt 4スタイルのconnect関数の形式
QMetaObject::Connection QObject::connect(Senderのポインタ, SIGNAL(シグナルの関数名(引数の型, ...)),
Receiverのポインタ, SLOT(スロットの関数名(引数の型, ...)),
Qt::ConnectionType type = Qt::AutoConnection)
// 例.
connect(sender, SIGNAL(value3Changed(int)), receiver, SLOT(setValue3(int)));
// Qt 5スタイルのconnect関数の形式
QMetaObject::Connection QObject::connect(Senderのポインタ, &シグナルのクラス名::シグナルの関数名,
Receiverのポインタ, &スロットのクラス名::スロットの関数名,
Qt::ConnectionType type = Qt::AutoConnection)
// 例.
connect(sender, &Sender::value3Changed, receiver, &Receiver::setValue3);
connect 関数の第2引数と第4引数において、Qt 4スタイルでは SIGNAL マクロ、SLOT マクロを使用する。
このマクロは、Qt側で文字列 (const char *signalのように) に変換される。
Qt 5スタイルと異なる事柄を以下に示す。
- マクロの有無
- クラス名を記述するかどうか
- 仮引数を明示的に記述するかどうか
// ラムダスタイル1のconnect関数の形式
QMetaObject::Connection connect(Senderのポインタ, &Senderのクラス名::シグナルの関数名, [=]() { 処理1; 処理2; ...; });
// 例.
connect(sender, &Sender::valueChanged, [=]() { qDebug() << "Lambda Style1 signal received."; });
// ラムダスタイル2のconnect関数の形式
QMetaObject::Connection connect(Senderのポインタ, &Senderのクラス名::シグナルの関数名,
コンテキストオブジェクト, [=]() { 処理1; 処理2; ...; },
Qt::QueuedConnection);
// 例.
connect(sender, &MyObject1::valueChanged,
this, [=]() { qDebug() << "Lambda Style2 signal received."; },
Qt::QueuedConnection);
ラムダ形式は、Receiverのスロット関数を作成しなくてよいため、シグナルが発生しているかどうかログを出力する時に便利である。
ラムダスタイル1は、引数に Qt::ConnectionType が無い。
また、Qt::ConnectionType が Qt::DirectConnection 固定となる。
つまり、SenderとReceiverが同一スレッドで動作することが前提となる。
send 関数により、Senderを取得することもできない。
ラムダスタイル2は、Qt 5.2で追加されたシンタックスである。
ラムダスタイル1と比較すると、第3引数にコンテキストが追加されて、第5引数に Qt::ConnectionType が追加されている。
Qt::ConnectionType が指定できるため、Qt::DirectConnection 以外の動作も可能である。
また、QObject::sender 関数も使用できる。
上記のラムダスタイル1 および ラムダスタイル2の例では、ラムダ式に キャプチャ式 [=] を使用しているが、キャプチャ式 [=] 以外のキャプチャ記法も使用できる。
| Stringベース | Functorベース | |
|---|---|---|
| 型チェックのタイミング | 実行時 | コンパイル時 |
| 暗黙の型変換 | 不可 | 可能 |
| シグナルをラムダ式で接続できる | 不可 | 可能 |
| シグナルより多くの引数を持つスロットに シグナルを接続できる |
可能 | 不可 |
| QMLに接続できる | 可能 | 可能 |
オーバーロードされたシグナルの処理
シグナルがオーバーロードされている場合、connect 関数で適切なシグナルを指定するために、以下に示すいずれかの方法を使用する。
qOverloadヘルパー関数 (C++ 14以降 推奨)
connect(sender, qOverload<int>(&Sender::valueChanged), receiver, &Receiver::setValue);
QOverload::of (C++ 11対応)
connect(sender, QOverload<int>::of(&Sender::valueChanged), receiver, &Receiver::setValue);
static_cast (C++ 03以前)
connect(sender, static_cast<void (Sender::*)(int)>(&Sender::valueChanged), receiver, &Receiver::setValue);
disconnect関数
シグナルとスロットの接続を切断する場合、disconnect 関数を使用する。
QMetaObject::Connectionを使用した切断 (推奨)
connect 関数は QMetaObject::Connection オブジェクトを返すため、このオブジェクトを保持しておき、disconnect 関数で切断する。
QMetaObject::Connection conn = connect(sender, &Sender::valueChanged, receiver, &Receiver::setValue);
// 接続を切断
disconnect(conn);
関数ポインタを使用した切断
connect 関数と対称的に、関数ポインタを指定して切断する。
disconnect(sender, &Sender::valueChanged, receiver, &Receiver::setValue);
全ての接続を切断
特定のオブジェクトの全ての接続を切断する場合、引数にオブジェクトのみを指定する。
// senderから発信される全ての接続を切断
disconnect(sender, nullptr, nullptr, nullptr);
// receiverが受信する全ての接続を切断
disconnect(nullptr, nullptr, receiver, nullptr);
オブジェクト破棄時の自動切断
QObject が破棄される時、そのオブジェクトに関連する全ての接続は自動的に切断される。
手動で disconnect 関数を呼び出す必要はない。
sender()関数
スロット実行中に、シグナルを発信したオブジェクトを取得する場合、QObject::sender関数を使用する。
void MyClass::handleSignal()
{
QObject *senderObj = sender();
if (senderObj) {
qDebug() << "Signal sent from:" << senderObj->objectName();
}
}
sender関数の制限事項
- スロット実行中のみ有効
- スロット関数の外部で呼び出すと、
nullptrを返す。
- スロット関数の外部で呼び出すと、
- DirectConnectionでのみ安全
Qt::DirectConnectionでは、正しくSenderオブジェクトを取得できる。
- QueuedConnectionでの使用時の注意
Qt::QueuedConnectionでは、スロットが実行される時点でSenderオブジェクトが既に破棄されている可能性がある。
スレッド間通信
Qtのシグナル・スロット機構は、スレッド間の通信に安全に使用できる。
スレッド間通信の動作
異なるスレッド間でシグナル・スロットを接続する場合、以下の動作となる。
Qt::AutoConnection(デフォルト)- Senderと Receiverが異なるスレッドの場合、自動的に
Qt::QueuedConnectionが使用される。 - スロットはReceiverのスレッドで非同期に実行される。
- Senderと Receiverが異なるスレッドの場合、自動的に
Qt::QueuedConnection- シグナルはReceiverのイベントキューに格納され、Receiverのスレッドで実行される。
- 引数はコピーされるため、引数はコピー可能な型である必要がある。
Qt::BlockingQueuedConnection- Senderのスレッドは、Receiverのスロット実行が完了するまで待機する。
- デッドロックのリスクがあるため、使用には注意が必要である。
スレッド間通信のベストプラクティス
- GUI操作は常にメインスレッドから
- GUIウィジェットの操作は、メインスレッド (UIスレッド) からのみ実行する。
- ワーカースレッドからGUIを直接操作してはならない。
// ワーカースレッドからシグナルを発信して、メインスレッドでGUI更新 connect(worker, &Worker::resultReady, this, &MainWindow::updateUI, Qt::QueuedConnection);
- QObject::moveToThreadメソッドの使用
- オブジェクトを別のスレッドに移動する場合、
moveToThread関数を使用する。 QThread *thread = new QThread; Worker *worker = new Worker; worker->moveToThread(thread); connect(thread, &QThread::started, worker, &Worker::process); connect(worker, &Worker::finished, thread, &QThread::quit); connect(thread, &QThread::finished, thread, &QThread::deleteLater); thread->start();
- オブジェクトを別のスレッドに移動する場合、
- deleteLaterメソッドによる安全な破棄
- スレッド上のオブジェクトを破棄する場合、
deleteLaterメソッドを使用する。 delete演算子を直接使用すると、スレッド実行中に破棄される可能性がある。connect(worker, &Worker::finished, worker, &QObject::deleteLater);
- スレッド上のオブジェクトを破棄する場合、
- カスタム型のqRegisterMetaType登録
- カスタム型をシグナル・スロットの引数として使用する場合、
qRegisterMetaType関数で登録する必要がある。 qRegisterMetaType<MyCustomType>("MyCustomType"); connect(sender, &Sender::customSignal, receiver, &Receiver::customSlot, Qt::QueuedConnection);
- カスタム型をシグナル・スロットの引数として使用する場合、
connect関数の使用例
基本的な使用例
#include "mainwindow.h"
#include <QApplication>
#include <QPushButton>
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
QPushButton* button = new QPushButton("Quit");
QObject::connect(button, SIGNAL(clicked()), &app, SLOT(quit()));
button->show();
return app.exec();
}
静的メンバ関数のconnect
静的メンバ関数は、インスタンスを生成せずに実行することができる。
通常のメンバ関数の呼び出しと同様、this ポインタからシグナルを発信して、this ポインタでシグナルを受ければよい。
また、送信側と受信側の両方がthisの場合、受信側インスタンスの指定を省略できる connect 関数のオーバーロード関数も存在する。
以下の例では、コンストラクタでシグナルとスロットを接続して、画面が表示された時、showEventメソッドでシグナルをemitしている。
また、送信側のクラスのインスタンスは生成していない。
// MainWindow.cpp
#include "MainWindow.h"
#include "ui_MainWindow.h"
MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWindow)
{
ui->setupUi(this);
connect(this, &MainWindow::testSignal1, this, &QObjectEx::staticSlot);
connect(this, &MainWindow::testSignal2, this, &QObjectEx::staticSlot);
}
MainWindow::~MainWindow()
{
delete ui;
}
void MainWindow::showEvent(QShowEvent *event)
{
QMainWindow::showEvent(event);
emit testSignal1(10);
emit testSignal2(20);
}
// MainWindow.h
#pragma once
#include <QMainWindow>
#include "QObjectEx.h"
namespace Ui {class MainWindow;}
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
explicit MainWindow(QWidget *parent = nullptr);
~MainWindow();
signals:
void testSignal1(int);
void testSignal2(int);
protected:
void showEvent(QShowEvent *) override;
private:
Ui::MainWindow *ui;
};
// QObjectEx.h
#pragma once
#include <QObject>
class QObjectEx : public QObject
{
Q_OBJECT
public slots:
static void staticSlot(int i)
{
qDebug() << i;
}
};
Qt Designerで設定したシグナルおよびスロット
Qt Designerで設定したシグナルおよびスロットは、connect 関数を使用せずに呼び出すことができる。
ソースコード上で connect 関数を使用せずにスロット関数が呼び出される理由を以下に示す。
まず、QWidget クラス あるいは QMainWindow クラス等を継承した派生クラスにおいて、コンストラクタに自動生成されるソースコードで、以下に示す記述がある。
ui->setupUi(this);
次に、setupUi メソッドの定義の最後に、以下の記述がある。
以下に示す QMetaObject::connectSlotsByName メソッドは、オブジェクト (引数) が持つ全てのスロットに対して、
on_<子オブジェクト名>_<子オブジェクトのシグナル名> を満たすスロット名の存在を確認する。
void setupUi(QMainWindow *MainWindow)
{
// ...略
QMetaObject::connectSlotsByName(MainWindow);
}
トラブルシューティング
Q_OBJECTマクロの確認
シグナル・スロット機構を使用するクラスには、Q_OBJECT マクロが必要である。
Q_OBJECT マクロが無い場合、コンパイルエラーが発生する。
class MyClass : public QObject
{
Q_OBJECT // このマクロが必要
public:
MyClass(QObject *parent = nullptr);
signals:
void mySignal();
};
connect関数の戻り値の確認
connect 関数は、接続が成功した場合に有効な QMetaObject::Connection を返し、失敗した場合は無効な接続を返す。
接続が失敗した場合、コンソールに警告メッセージが出力される。
QMetaObject::Connection conn = connect(sender, &Sender::signal, receiver, &Receiver::slot);
if (!conn) {
qWarning() << "Connection failed!";
}
ラムダ式でのメモリリーク防止
ラムダ式を使用する場合、コンテキストオブジェクトを指定することで、メモリリークを防止できる。
コンテキストオブジェクトが破棄されると、接続が自動的に切断される。
// コンテキストオブジェクトを指定 (推奨)
connect(sender, &Sender::signal, this, [this]() {
// thisが破棄されると、この接続も自動的に切断される
});
// コンテキストオブジェクトを指定しない (非推奨)
connect(sender, &Sender::signal, [this]() {
// thisが破棄されても、この接続は残り続ける (メモリリーク)
});
シグナル・スロットが呼ばれない場合の確認項目
Q_OBJECTマクロがクラス定義に含まれているかQ_OBJECTマクロが無いと、MOC (Meta-Object Compiler) が実行されず、シグナル・スロット機構が動作しない。
connect関数の戻り値が有効か- 接続が失敗している場合、コンソールに警告メッセージが出力されるため、確認する。
- シグナルとスロットの引数が一致しているか
- Functorベースでは、引数の型が厳密にチェックされる。
- オブジェクトが破棄されていないか
- SenderまたはReceiverが破棄されると、接続は自動的に切断される。
- イベントループが実行されているか
Qt::QueuedConnectionでは、イベントループが実行されていないと、スロットが呼ばれない。