「Qtの基礎 - シグナルとスロット」の版間の差分

提供: MochiuWiki : SUSE, EC, PCB

文字列「__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行目: 1行目:
== 概要 ==
== 概要 ==
シグナルおよびスロットの詳細は、以下のWebサイトを参照すること。<br>
Qtのシグナル・スロット機構は、オブジェクト間の通信を実現するQt独自のコールバックメカニズムである。<br>
https://blog.qt.io/jp/2010/07/20/create-signals-and-slots-2<br>
シグナルは、特定のイベントが発生した時に発行される通知であり、スロットはシグナルを受信して処理を実行する関数である。<br>
<br>
シグナル・スロット機構の主な特徴を以下に示す。<br>
* 疎結合性
*: シグナルを発行するオブジェクトは、どのオブジェクトがシグナルを受信するかを知る必要がない。
*: これにより、オブジェクト間の依存関係を低減して、コードの保守性が向上する。
* 型安全性
*: Qt 5以降のFunctorベース (関数ポインタ) 構文では、シグナルとスロットの引数の型がコンパイル時にチェックされる。
*: 型の不一致がある場合、コンパイルエラーとなるため、実行時エラーを未然に防ぐことができる。
* スレッド間通信
*: 異なるスレッド間でも安全に通信することができる。
*: <code>Qt::QueuedConnection</code> を使用することで、スロットはReceiverのスレッドで非同期に実行される。
* 自動メモリ管理
*: <code>QObject</code> が破棄される時、関連する全ての接続は自動的に切断される。
*: これにより、ダングリングポインタ (無効なポインタ) によるクラッシュを防止する。
<br>
シグナルおよびスロットの詳細は、[https://doc.qt.io/qt-6/signalsandslots.html Qtの公式Webサイト]を参照すること。<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" | style="background-color:#fefefe;"
{| class="wikitable"
|-
|-
! style="background-color:#66CCFF;" | 接続タイプ(定数)  
! 接続タイプ(定数) !! 動作
! style="background-color:#66CCFF;" | 動作
|-
|-
| Qt::AutoConnection || デフォルトの設定である。<br>SenderとReceiverが同じスレッドに存在する場合は、<code>Qt::DriectConnection</code>が使用される。<br>それ以外の場合は、<code>Qt::QueuedConnection</code>が使用される。<br><br>接続タイプはシグナルを<code>emit</code>する時に決定する。
| Qt::AutoConnection || デフォルトの設定である。<br>SenderとReceiverが同じスレッドに存在する場合は、<code>Qt::DirectConnection</code>が使用される。<br>それ以外の場合は、<code>Qt::QueuedConnection</code>が使用される。<br><br>接続タイプはシグナルを<code>emit</code>する時に決定する。
|-
|-
| Qt::DriectConnection || シグナルを呼び出したスレッドから対象のスロットを呼び出す。(同期呼び出し)
| Qt::DirectConnection || シグナルを呼び出したスレッドから対象のスロットを呼び出す。(同期呼び出し)
|-
|-
| Qt::QueuedConnection || Receiverのスレッド上でスロット関数が呼び出される。<br>シグナルはReceiver側のキューに入れられて、Receiverのイベントルーパーからスロット関数が呼び出される。(非同期呼び出し)
| Qt::QueuedConnection || Receiverのスレッド上でスロット関数が呼び出される。<br>シグナルはReceiver側のキューに入れられて、Receiverのイベントルーパーからスロット関数が呼び出される。(非同期呼び出し)
22行目: 63行目:
|-
|-
| 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関数 ====
==== connect関数のシンタックス ====
<code>connect</code>関数は大きく分けて、2種類、細かく分けて4種類の種類が存在する。<br>
<code>connect</code> 関数は大きく分けて、2種類、細かく分けて4種類の種類が存在する。<br>
<br>
<center>
<center>
{| class="wikitable" | style="background-color:#fefefe;"
{| class="wikitable"  
|-
|-
! style="background-color:#66CCFF;" | connect関数のシンタックス  
! connect関数のシンタックス !! スタイルの呼称
! style="background-color:#66CCFF;" | スタイルの呼称
|-
|-
| Stringベース || Qt 4スタイル
| Stringベース || Qt 4スタイル
38行目: 81行目:
| Qt 5スタイル
| Qt 5スタイル
|-
|-
| Lambdaスタイル 1
| ラムダスタイル 1
|-
|-
| Lambdaスタイル 2
| ラムダスタイル 2
|}
|}
</center>
</center>
46行目: 89行目:
  <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行目: 105行目:
  </syntaxhighlight>
  </syntaxhighlight>
<br>
<br>
<code>connect</code>関数の第2引数と第4引数において、Qt 4スタイルでは<code>SIGNAL</code><code>SLOT</code>というマクロを使用する。<br>
<code>connect</code> 関数の第2引数と第4引数において、Qt 4スタイルでは <code>SIGNAL</code> マクロ、<code>SLOT</code> マクロを使用する。<br>
このマクロは、Qt側で文字列(const char *signalのように)に変換される。<br>
このマクロは、Qt側で文字列 (const char *signalのように) に変換される。<br>
Qt 5スタイルと異なる点を、以下に示す。<br>
<br>
Qt 5スタイルと異なる事柄を以下に示す。<br>
* マクロの有無
* マクロの有無
* クラス名を記述するかどうか
* クラス名を記述するかどうか
67行目: 114行目:
<br>
<br>
  <syntaxhighlight lang="c++">
  <syntaxhighlight lang="c++">
  // Lambdaスタイル 1のconnect関数の形式
  // ラムダスタイル1のconnect関数の形式
  QMetaObject::Connection connect(Senderのポインタ, &Senderのクラス名::シグナルの関数名, [=]() { 処理1; 処理2; ...; });
  QMetaObject::Connection connect(Senderのポインタ, &Senderのクラス名::シグナルの関数名, [=]() { 処理1; 処理2; ...; });
   
   
73行目: 120行目:
  connect(sender, &Sender::valueChanged, [=]() { qDebug() << "Lambda Style1 signal received."; });
  connect(sender, &Sender::valueChanged, [=]() { qDebug() << "Lambda Style1 signal received."; });
   
   
  // ラムダスタイル2のconnect関数の形式
  // Lambdaスタイル 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>
Lambda形式は、Receiverのスロット関数を作成しなくてよいため、シグナルが発生しているかどうかログを出力する時に便利である。<br>
ラムダ形式は、Receiverのスロット関数を作成しなくてよいため、シグナルが発生しているかどうかログを出力する時に便利である。<br>
<br>
ラムダスタイル1は、引数に <code>Qt::ConnectionType</code> が無い。<br>
また、<code>Qt::ConnectionType</code> が <code>Qt::DirectConnection</code> 固定となる。<br>
<br>
<br>
Lambdaスタイル 1は、引数に<code>Qt::ConnectionType</code>が無い。<br>
また、<code>Qt::ConnectionType</code>が<code>Qt::DirectConnection</code>固定となる。<br>
つまり、SenderとReceiverが同一スレッドで動作することが前提となる。<br>
つまり、SenderとReceiverが同一スレッドで動作することが前提となる。<br>
<code>send</code>関数により、Senderを取得することもできない。<br>
<code>send</code> 関数により、Senderを取得することもできない。<br>
<br>
<br>
Lambdaスタイル 2は、Qt 5.2で追加されたシンタックスである。<br>
ラムダスタイル2は、Qt 5.2で追加されたシンタックスである。<br>
Lambdaスタイル 1と比較すると、第3引数にコンテキストが追加されて、第5引数に<code>Qt::ConnectionType</code>が追加されている。<br>
ラムダスタイル1と比較すると、第3引数にコンテキストが追加されて、第5引数に <code>Qt::ConnectionType</code> が追加されている。<br>
<code>Qt::ConnectionType</code>が指定できるため、<code>Qt::DirectConnection</code>以外の動作も可能である。<br>
<code>Qt::ConnectionType</code> が指定できるため、<code>Qt::DirectConnection</code> 以外の動作も可能である。<br>
また、<code>QObject::sender</code>関数も使用できる。<br>
<br>
<br>
上記のLambdaスタイル 1およびLambdaスタイル 2の例では、ラムダ式に<code>キャプチャ式 [=]</code>を使用しているが、<code>キャプチャ式 [=]</code>以外のキャプチャ記法も使用できる。<br>
また、<code>QObject::sender</code> 関数も使用できる。<br>
<br>
上記のラムダスタイル1 および ラムダスタイル2の例では、ラムダ式に <code>キャプチャ式 [=]</code> を使用しているが、<code>キャプチャ式 [=]</code> 以外のキャプチャ記法も使用できる。<br>
<br>
<br>
<center>
<center>
{| class="wikitable" | style="background-color:#fefefe;"
{| class="wikitable"  
|+  StringベースとFunctorベースの比較
|+  StringベースとFunctorベースの比較
|-
|-
! style="background-color:#66CCFF;width:50%;" |
! style="width:50%;" |
! style="background-color:#66CCFF;width:25%;" | Stringベース
! style="width:25%;" | Stringベース
! style="background-color:#66CCFF;width:25%;" | Functorベース
! 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関数 ==
connect関数を使用して、シグナルとスロットの設定を行う。<br>
シグナルとスロットの接続を切断する場合、<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++">
  connect(sender, SIGNAL(signal), receiver, SLOT(slot));
  // senderから発信される全ての接続を切断
disconnect(sender, nullptr, nullptr, nullptr);
// receiverが受信する全ての接続を切断
disconnect(nullptr, nullptr, receiver, nullptr);
  </syntaxhighlight>
  </syntaxhighlight>
<br>
<br>
以下に、connenct関数の引数の意味を記載する。<br>
==== オブジェクト破棄時の自動切断 ====
* sender
<code>QObject</code> が破棄される時、そのオブジェクトに関連する全ての接続は自動的に切断される。<br>
*: 信号が発生するコントロールIDまたはクラスのアドレスを渡す。
手動で <code>disconnect</code> 関数を呼び出す必要はない。<br>
* SIGNAL(signal)
*: signalに信号とする関数名を渡す。
*: 例: プッシュボタンの場合、<code>SIGNAL(clicked())</code>と記述する。
* receiver
*: 信号を受信するコントロールIDまたはクラスのアドレスを渡す。
* SLOT(slot)
*: 信号を受信した時に呼び出す関数名を渡す。
<br><br>
<br><br>


== connect関数の使用方法 ==
== sender()関数 ==
スロット実行中に、シグナルを発信したオブジェクトを取得する場合、<code>QObject::sender</code>関数を使用する。<br>
<syntaxhighlight lang="c++">
void MyClass::handleSignal()
{
    QObject *senderObj = sender();
    if (senderObj) {
      qDebug() << "Signal sent from:" << senderObj->objectName();
    }
}
</syntaxhighlight>
<br>
==== sender関数の制限事項 ====
* スロット実行中のみ有効
*: スロット関数の外部で呼び出すと、<code>nullptr</code> を返す。
* DirectConnectionでのみ安全
*: <code>Qt::DirectConnection</code> では、正しくSenderオブジェクトを取得できる。
* QueuedConnectionでの使用時の注意
*: <code>Qt::QueuedConnection</code> では、スロットが実行される時点でSenderオブジェクトが既に破棄されている可能性がある。
<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行目: 324行目:
  }
  }
  </syntaxhighlight>
  </syntaxhighlight>
<br><br>
<br>
 
==== 静的メンバ関数のconnect ====
== 静的メンバ関数のconnect ==
静的メンバ関数は、インスタンスを生成せずに実行することができる。<br>
静的メンバ関数は、インスタンスを生成せずに実行することができる。<br>
通常のメンバ関数の呼び出しと同様、thisからシグナルを発信して、thisでシグナルを受ければよい。<br>
通常のメンバ関数の呼び出しと同様、<code>this</code> ポインタからシグナルを発信して、<code>this</code> ポインタでシグナルを受ければよい。<br>
<br>
<br>
また、送信側と受信側の両方がthisの場合、受信側インスタンスの指定を省略できるconnectのオーバーロード関数も存在する。<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行目: 413行目:


== Qt Designerで設定したシグナルおよびスロット ==
== Qt Designerで設定したシグナルおよびスロット ==
Qt Designerで設定したシグナルおよびスロットは、<code>connect</code>関数を使用せずに呼び出すことができる。<br>
Qt Designerで設定したシグナルおよびスロットは、<code>connect</code> 関数を使用せずに呼び出すことができる。<br>
<br>
<br>
ソースコード上で<code>connect</code>関数を使用せずにスロット関数が呼び出される理由を以下に示す。<br>
ソースコード上で <code>connect</code> 関数を使用せずにスロット関数が呼び出される理由を以下に示す。<br>
まず、<code>QWidget</code>クラスや<code>QMainWindow</code>クラス等を継承した派生クラスにおいて、コンストラクタに自動生成されるソースコードで、以下の記述がある。<br>
まず、<code>QWidget</code> クラス あるいは <code>QMainWindow</code> クラス等を継承した派生クラスにおいて、コンストラクタに自動生成されるソースコードで、以下に示す記述がある。<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>
次に、<code>setupUi</code>メソッドの定義の最後に、以下の記述がある。<br>
以下に示す<code>QMetaObject::connectSlotsByName</code>メソッドは、オブジェクト(引数)が持つ全てのスロットに対して、<br>
<code>on_<子オブジェクト名>_<子オブジェクトのシグナル名></code>を満たすスロット名の存在を確認する。<br>
  <syntaxhighlight lang="c++">
  <syntaxhighlight lang="c++">
  void setupUi(QMainWindow *MainWindow)
  void setupUi(QMainWindow *MainWindow)
268行目: 435行目:
  </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:31時点における最新版

概要

Qtのシグナル・スロット機構は、オブジェクト間の通信を実現するQt独自のコールバックメカニズムである。
シグナルは、特定のイベントが発生した時に発行される通知であり、スロットはシグナルを受信して処理を実行する関数である。

シグナル・スロット機構の主な特徴を以下に示す。

  • 疎結合性
    シグナルを発行するオブジェクトは、どのオブジェクトがシグナルを受信するかを知る必要がない。
    これにより、オブジェクト間の依存関係を低減して、コードの保守性が向上する。
  • 型安全性
    Qt 5以降のFunctorベース (関数ポインタ) 構文では、シグナルとスロットの引数の型がコンパイル時にチェックされる。
    型の不一致がある場合、コンパイルエラーとなるため、実行時エラーを未然に防ぐことができる。
  • スレッド間通信
    異なるスレッド間でも安全に通信することができる。
    Qt::QueuedConnection を使用することで、スロットはReceiverのスレッドで非同期に実行される。
  • 自動メモリ管理
    QObject が破棄される時、関連する全ての接続は自動的に切断される。
    これにより、ダングリングポインタ (無効なポインタ) によるクラッシュを防止する。


シグナルおよびスロットの詳細は、Qtの公式Webサイトを参照すること。


シグナルとスロットの基本概念

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::ConnectionTypeQt::DirectConnection 固定となる。

つまり、SenderとReceiverが同一スレッドで動作することが前提となる。
send 関数により、Senderを取得することもできない。

ラムダスタイル2は、Qt 5.2で追加されたシンタックスである。
ラムダスタイル1と比較すると、第3引数にコンテキストが追加されて、第5引数に Qt::ConnectionType が追加されている。
Qt::ConnectionType が指定できるため、Qt::DirectConnection 以外の動作も可能である。

また、QObject::sender 関数も使用できる。

上記のラムダスタイル1 および ラムダスタイル2の例では、ラムダ式に キャプチャ式 [=] を使用しているが、キャプチャ式 [=] 以外のキャプチャ記法も使用できる。

StringベースとFunctorベースの比較
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のスレッドで非同期に実行される。
  • 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 では、イベントループが実行されていないと、スロットが呼ばれない。