MochiuWiki : SUSE, EC, PCB
案内
メインページ
最近の更新
おまかせ表示
MediaWiki についてのヘルプ
ツール
リンク元
関連ページの更新状況
特別ページ
ページ情報
We ask for
Donations
検索
個人用ツール
ログイン
Toggle dark mode
名前空間
ページ
議論
表示
閲覧
ソースを閲覧
履歴を表示
Qtの基礎 - シグナルとスロットのソースを表示
提供: MochiuWiki : SUSE, EC, PCB
←
Qtの基礎 - シグナルとスロット
あなたには「このページの編集」を行う権限がありません。理由は以下の通りです:
この操作は、次のグループのいずれかに属する利用者のみが実行できます:
管理者
、new-group。
このページのソースの閲覧やコピーができます。
== 概要 == 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> == connect関数 == ==== Qt::ConnectionType ==== <code>connect</code> 関数の <code>Qt::ConnectionType</code> の設定により動作が異なる。<br> <br> <center> {| class="wikitable" |- ! 接続タイプ(定数) !! 動作 |- | Qt::AutoConnection || デフォルトの設定である。<br>SenderとReceiverが同じスレッドに存在する場合は、<code>Qt::DirectConnection</code>が使用される。<br>それ以外の場合は、<code>Qt::QueuedConnection</code>が使用される。<br><br>接続タイプはシグナルを<code>emit</code>する時に決定する。 |- | Qt::DirectConnection || シグナルを呼び出したスレッドから対象のスロットを呼び出す。(同期呼び出し) |- | Qt::QueuedConnection || Receiverのスレッド上でスロット関数が呼び出される。<br>シグナルはReceiver側のキューに入れられて、Receiverのイベントルーパーからスロット関数が呼び出される。(非同期呼び出し) |- | Qt::BlockingQueuedConnection || 基本的な動作は、Qt::QueuedConnectionと同様であるが、<br>シグナルの呼び出した側は、受信側のスロットの実行終了を待ち合わせる。<br><br><u>多用するとデッドロックを招きやすくなる。</u><br><u>例えば、Receiver側がSender側と同じスレッドに存在する場合には、必ず、デッドロックすることに注意する。</u> |- | 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> <br> ==== connect関数のシンタックス ==== <code>connect</code> 関数は大きく分けて、2種類、細かく分けて4種類の種類が存在する。<br> <br> <center> {| class="wikitable" |- ! connect関数のシンタックス !! スタイルの呼称 |- | Stringベース || Qt 4スタイル |- | rowspan=3 | Functorベース | Qt 5スタイル |- | ラムダスタイル 1 |- | ラムダスタイル 2 |} </center> <br> <syntaxhighlight lang="c++"> // 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); </syntaxhighlight> <br> <code>connect</code> 関数の第2引数と第4引数において、Qt 4スタイルでは <code>SIGNAL</code> マクロ、<code>SLOT</code> マクロを使用する。<br> このマクロは、Qt側で文字列 (const char *signalのように) に変換される。<br> <br> Qt 5スタイルと異なる事柄を以下に示す。<br> * マクロの有無 * クラス名を記述するかどうか * 仮引数を明示的に記述するかどうか <br> <syntaxhighlight lang="c++"> // ラムダスタイル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); </syntaxhighlight> <br> ラムダ形式は、Receiverのスロット関数を作成しなくてよいため、シグナルが発生しているかどうかログを出力する時に便利である。<br> <br> ラムダスタイル1は、引数に <code>Qt::ConnectionType</code> が無い。<br> また、<code>Qt::ConnectionType</code> が <code>Qt::DirectConnection</code> 固定となる。<br> <br> つまり、SenderとReceiverが同一スレッドで動作することが前提となる。<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> また、<code>QObject::sender</code> 関数も使用できる。<br> <br> 上記のラムダスタイル1 および ラムダスタイル2の例では、ラムダ式に <code>キャプチャ式 [=]</code> を使用しているが、<code>キャプチャ式 [=]</code> 以外のキャプチャ記法も使用できる。<br> <br> <center> {| class="wikitable" |+ StringベースとFunctorベースの比較 |- ! style="width:50%;" | ! style="width:25%;" | Stringベース ! 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;" | 可能 |- | シグナルより多くの引数を持つスロットに<br>シグナルを接続できる | style="text-align: center;" | 可能 | style="text-align: center;" | 不可 |- | QMLに接続できる | style="text-align: center;" | 可能 | style="text-align: 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> == 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++"> // 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> <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++"> #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(); } </syntaxhighlight> <br> ==== 静的メンバ関数のconnect ==== 静的メンバ関数は、インスタンスを生成せずに実行することができる。<br> 通常のメンバ関数の呼び出しと同様、<code>this</code> ポインタからシグナルを発信して、<code>this</code> ポインタでシグナルを受ければよい。<br> <br> また、送信側と受信側の両方がthisの場合、受信側インスタンスの指定を省略できる <code>connect</code> 関数のオーバーロード関数も存在する。<br> <br> 以下の例では、コンストラクタでシグナルとスロットを接続して、画面が表示された時、showEventメソッドでシグナルをemitしている。<br> また、送信側のクラスのインスタンスは生成していない。<br> <br> <syntaxhighlight lang="c++"> // 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); } </syntaxhighlight> <br> <syntaxhighlight lang="c++"> // 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; }; </syntaxhighlight> <br> <syntaxhighlight lang="c++"> // QObjectEx.h #pragma once #include <QObject> class QObjectEx : public QObject { Q_OBJECT public slots: static void staticSlot(int i) { qDebug() << i; } }; </syntaxhighlight> <br><br> == Qt Designerで設定したシグナルおよびスロット == Qt Designerで設定したシグナルおよびスロットは、<code>connect</code> 関数を使用せずに呼び出すことができる。<br> <br> ソースコード上で <code>connect</code> 関数を使用せずにスロット関数が呼び出される理由を以下に示す。<br> まず、<code>QWidget</code> クラス あるいは <code>QMainWindow</code> クラス等を継承した派生クラスにおいて、コンストラクタに自動生成されるソースコードで、以下に示す記述がある。<br> <br> <syntaxhighlight lang="c++"> ui->setupUi(this); </syntaxhighlight> <br> 次に、<code>setupUi</code> メソッドの定義の最後に、以下の記述がある。<br> 以下に示す <code>QMetaObject::connectSlotsByName</code> メソッドは、オブジェクト (引数) が持つ全てのスロットに対して、<br> <code>on_<子オブジェクト名>_<子オブジェクトのシグナル名></code> を満たすスロット名の存在を確認する。<br> <br> <syntaxhighlight lang="c++"> void setupUi(QMainWindow *MainWindow) { // ...略 QMetaObject::connectSlotsByName(MainWindow); } </syntaxhighlight> <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: |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 page is {{PAGENAME}} in our wiki about electronic circuits and SUSE Linux |image=/resources/assets/MochiuLogo_Single_Blue.png }} __FORCETOC__ [[カテゴリ:Qt]]
Qtの基礎 - シグナルとスロット
に戻る。
案内
メインページ
最近の更新
おまかせ表示
MediaWiki についてのヘルプ
ツール
リンク元
関連ページの更新状況
特別ページ
ページ情報
We ask for
Donations
Collapse