信号とスロットのメカニズム
記事ディレクトリ
1. 信号とスロットのメカニズムの概要
(注1:以下のスロットとスロット関数は同じ意味を表します)
(注2: この記事を読むのは少し退屈かもしれませんが、記事には開発でも無視されがちな信号とスロットに関する重要な知識が含まれています。続けて見てください)
シグナルとスロットは、複数のオブジェクト間の通信に使用されます。シグナルとスロットのメカニズムは Qt の核となる機能であり、他のフレームワークとの最大の違いでもあります。Qt のメタオブジェクト システムは、シグナルとスロットの実装の基礎です。
GUI プログラミングでは、通常、1 つのウィジェットが変更されると、別のウィジェットに通知が必要になります。どのタイプのオブジェクトも相互に通信できることが期待されます。たとえば、ユーザーが閉じるボタンをクリックした場合にウィンドウの関数を呼び出すことができますClose()
。
他のソフトウェア ツールキットまたはフレームワークは、コールバック メカニズムを使用してこの通信メカニズムを実装する場合があります。コールバック関数は関数へのポインタであるため、ハンドラ関数に何らかのイベントを通知したい場合は、別の関数 (コールバック関数) へのポインタをハンドラ関数に渡します。次に、ハンドラー関数は、必要に応じてコールバック関数を呼び出します。このアプローチを使用して成功したフレームワークは存在しますが、コールバックは直感的ではない可能性があり、正しい種類のコールバック引数を確保する際に問題が発生する可能性があります。
Qt には、コールバック手法に代わるメカニズムがあります。それが信号和槽
メカニズムです。特定のイベントが発生すると、信号が送信されます。Qt のウィジェットには事前定義されたシグナルが多数ありますが、ウィジェットをサブクラス化してカスタムシグナルを追加できます。スロットは、特定の信号に応答する関数です。Qt のウィジェットには多くの事前定義されたスロットがありますが、ウィジェットに関連付けられたシグナルを処理できるように、ウィジェットをサブクラス化し、独自のスロットを追加するのが一般的です。以下に示すように:
シグナルとスロットのメカニズムはタイプセーフです。シグナルのパラメーターは受信スロット関数のパラメーターと一致する必要があります。(実際には、スロットは余分なパラメーターを無視できるため、受信する信号よりも少ないパラメーターを持つことができます。) パラメーターには互換性があるため、関数ポインター構文に基づく信号とスロットの関連付けメカニズムを使用する場合、コンパイラーは次のことを行うことができます。タイプの一致を検出します。
信号とスロットは疎結合です。オブジェクトが信号を発するとき、オブジェクトはどのオブジェクトのスロットが信号を受信するかを知りませんし、気にしません。Qt のシグナルとスロットのメカニズムにより、シグナルをスロットに接続すると、そのスロットが正しいタイミングで呼び出されます。
シグナルとスロットは、任意の型の任意の数の引数を受け入れることができます。これらは完全にタイプセーフです。
継承するすべてのQObject
クラス、またはそのサブクラス (QWidget など) の 1 つには、シグナルとスロットを含めることができます。オブジェクトの状態が変更されると (開発者と親クラスによって決定されます)、シグナルが発行される場合があります。
スロット関数はシグナルの受信に使用できますが、通常のメンバー関数でもあります。オブジェクトが信号を受信したかどうかを知らないのと同じように、スロットも信号が接続されているかどうかを知らないため、Qt を使用して独立したコンポーネントを作成できます。独立したコンポーネントを使用する必要がある場合は、そのコンポーネント クラスで事前定義された信号関数とスロット関数を決定し、信号関数とスロット関数を接続します。
複数の信号を 1 つのスロット機能 (つまり [多対 1]) に接続することができ、1 つの信号を複数のスロット機能 [つまり 1 対多] に接続することもできます。
ある信号を別の信号に直接接続することも可能です。(最初の信号が発信されると、すぐに 2 番目の信号が発信されます。)
要約すると、信号とスロット関数は一緒になって強力なコンポーネント プログラミング メカニズムを形成します。
二、【信号】
(2-1) シグナルの発行
ある条件が到達するとオブジェクトに変化が生じる可能性があるため、オブジェクトの内部状態が変化し、このときオブジェクトは信号を送信します。シグナルはパブリック アクセス関数であり、どこからでも送信できますが、[シグナルを定義するクラスとそのサブクラスからのみシグナルを送信する]ことをお勧めします。
Qt フレームワークでは、次の 2 種類のシグナリングがあります。
1. [クラスごとに定義されたシグナル]: これらのシグナルが送信されると、公式ドキュメントを確認できます。
2. [カスタマイズされたシグナル]: これらのシグナルの送信は開発者によって定義されます。
(2-2) 信号処理
シグナルが発行されると、通常は、通常の関数呼び出しと同様に、それに接続されているスロット関数が即座に実行されます。この場合、シグナルとスロットのメカニズムは GUI イベント ループから完全に独立しており、GUI イベント ループに干渉しません。emit
ステートメントに続くコードは、すべてのスロット関数が返されるまで実行されません。キュー接続が使用されている場合、状況は少し異なります。この場合、emit キーワードに続くコードはすぐに続行され、スロット関数は後で実行されます。
複数のスロット関数が同じ信号に接続されている場合、信号が送信されると、これらのスロット関数は接続された順序で順番に実行されます。【この点は非常に重要です】
シグナルはmoc
ツールによって自動的に生成され、.cpp ファイルに実装することはできないため、シグナルに戻り値の型を設定することはできません (シグナルはvoid
キーワードを使用して定義する必要があります)。
信号とスロットのパラメーターに関する注意: 経験上、信号とスロットは特別な型を使用しない方が再利用可能であることがわかっています。
次の表は、connect()
信号作成およびスロット関数接続を使用するときに指定できる 5 つの異なる接続タイプを示しています。
シリアルナンバー | タイプ | 意味 |
---|---|---|
1 | Qt::自動接続 | 受信者がシグナルを発行したスレッド内に存在する場合にQt::DirectConnection 使用されます。それ以外の場合は、 を使用しますQt::QueuedConnection 。接続タイプは信号の発信時に決定されます。[これは、Qt がシグナル関数とスロット関数を作成するときのデフォルトの接続方法です] |
2 | Qt::DirectConnection | シグナルが送信されると、すぐにスロット関数が呼び出されます。スロット関数は、シグナルを送信するスレッドで実行されます。 |
3 | Qt::QueuedConnection | スロット関数は、制御が受信側スレッドのイベント ループに戻るときに呼び出されます。スロット関数は受信側のスレッドで実行されます。 |
4 | Qt::BlockingQueuedConnection | Qt::QueuedConnection スロットが戻るまでスレッドがブロックされることを除いて、 と同じです。シグナルを送信したスレッドに受信者が存在する場合、接続は使用できません。そうでない場合、アプリケーションはデッドロックになります。 |
5 | Qt::UniqueConnection | これは、ビットごとの OR を使用して上記の結合タイプと組み合わせることができるフラグです。設定するとQt::UniqueConnection 、QObject::connect() 接続がすでに存在する場合 (たとえば、同じ信号が同じオブジェクトの同じペアの同じスロットにすでに接続されている場合)、失敗します。注: このフラグは Qt 4.6 で導入されました。 |
3. 【スロット機能】
スロット関数は、スロット関数に接続された信号が発信されると呼び出されます。スロット関数は通常の C++ 関数であり、実際の開発でも普通に呼び出すことができますが、唯一の特徴は「信号を接続できる」という点です。
スロットは通常のメンバー関数であるため、直接呼び出す場合は通常の C++ ルールに従います。ただし、スロットとして、どのコンポーネントも信号接続を通じてそれらを呼び出すことができます。
スロット関数を仮想として定義することもできるため、開発に非常に役立ちます。
シグナルとスロットのメカニズムはコールバック メカニズムに比べてわずかに遅くなりますが、実際のアプリケーションではその差は大きくありません。一般に、あるスロットに接続された信号の送信は、非仮想関数を直接呼び出すよりも約 10 倍遅くなります。これは、接続オブジェクトの検索、すべての接続を安全に反復処理 (つまり、後続の受信者が送信中に破棄されないことの確認)、および呼び出しの受け渡しにかかるオーバーヘッドです。10 回の非仮想関数呼び出しは多そうに思えますが、新規作成や削除よりもはるかにオーバーヘッドが少なくなります。新規または削除を必要とする文字列、ベクトル、またはリストの操作がバックグラウンドで実行されると、信号とスロットのオーバーヘッドは、関数呼び出し全体のオーバーヘッドのほんの一部にすぎません。スロット関数でシステム コールを実行する (または 10 を超える関数を間接的に呼び出す) 場合も同様です。したがって、信号とスロットのメカニズムのシンプルさと柔軟性には価値があり、これらのオーバーヘッドは実際のアプリケーション シナリオでは認識されることさえありません。
変数をシグナルまたはスロットとして定義するサードパーティ ライブラリは、Qt ベースのアプリケーションでコンパイルするときにコンパイラの警告やエラーを引き起こす可能性があることに注意してください。これを修正するには、 を使用して、#undef
問題が発生したプリプロセッサ シンボルを定義します。
(3-1) デフォルトパラメータによるシグナルおよびスロット機能
シグナルとスロットにはパラメーターを含めることができ、パラメーターにはデフォルト値を含めることができます。例: QObject::destroyed()
。
void destroyed(QObject* = nullptr);
削除されるとQObject
、このQObject::destroyed()
信号が発せられます。削除された QObject へのダングリング参照がある場合は常に、このシグナルをキャッチしてクリーンアップできるようにしたいと考えています。適切なスロット パラメータは次のとおりです。
void objectDestroyed(QObject* obj = nullptr);
(3-2)QObject::connect()
信号をスロット機能に接続するには、次の 3 つの方法を使用します。
(1) 1 つ目の方法: 関数ポインタを使用する
connect(sender, &QObject::destroyed, this, &MyObject::objectDestroyed);
関数ポインターを使用することにはいくつかの利点がありますQObject::connect()
。これにより、コンパイラはシグナルの引数がスロットの引数と互換性があるかどうかをチェックできます。コンパイラは、必要に応じてパラメータを暗黙的に変換することもできます。
(2) 2 番目の方法: に接続できます。C++ 11 的 lambdas
connect(sender, &QObject::destroyed, this, [=](){
this->m_objects.remove(sender); });
どちらの場合も、connect() 呼び出しでこのコンテキストを提供します。コンテキスト オブジェクトは、レシーバーがどのスレッドで実行されるべきかに関する情報を提供します。
送信者またはコンテキストが破棄されると、lambda
接続は切断されます。注: 関数内で使用されるすべてのオブジェクトは、信号が発行されたときもアクティブのままです。
(3) 3 番目の方法:QObject::connect()
シグナルおよびスロット宣言マクロを使用します。SIGNAL()
およびマクロにパラメータを含める場合の規則SLOT()
(パラメータにデフォルト値がある場合) は、SIGNAL() マクロに渡されるパラメータの数が SLOT() マクロに渡されるパラメータよりも少なくならないことです。
たとえば、次のコードは合法です。
connect(sender, SIGNAL(destroyed(QObject*)), this, SLOT(objectDestroyed(Qbject*)));
connect(sender, SIGNAL(destroyed(QObject*)), this, SLOT(objectDestroyed()));
connect(sender, SIGNAL(destroyed()), this, SLOT(objectDestroyed()));
しかし、これは違法です:
connect(sender, SIGNAL(destroyed()), this, SLOT(objectDestroyed(QObject*)));
スロット関数はシグナルが送信されない QObject を期待しているためです。この接続では実行時エラーが報告されます。
QObject::connect()
オーバーロードが使用されている場合、コンパイラはシグナル関数とスロット関数のパラメーターを自動的にチェックしないことに注意してください。
要約: 最初の方法を使用して信号とスロットを作成することは、開発においてより一般的であり、適切です。
(3-3) シグナルおよびスロット機能の高度な使用法
シグナル送信者の情報を取得する必要がある場合は、QObject::sender()
シグナルを送信したオブジェクトへのポインターを返す Qt が提供する関数を使用できます。
Lambda
エクスプレッションは、カスタム パラメータをスロットに渡す便利な方法です。
connect(action, &QAction::triggered, engine,[=]() {
engine->processAction(action->text()); });
4 番目に、disconnect を使用して信号/スロット接続を切断します。
disconnect()
オブジェクト送信側のシグナルをオブジェクト受信側のメソッドから切断するために使用されます。接続が正常に切断された場合は true を返し、それ以外の場合は false を返します。
信号/スロットの関連付けの両側のいずれかのオブジェクトが破棄されると、信号/スロットの接続は削除されます。
disconnect()
次の例に示すように、3 つの使用方法があります。
1. オブジェクトに接続されているすべての信号/スロットを切断します。
disconnect(myObject, nullptr, nullptr, nullptr);
非静的オーバーロード関数と同等:
myObject->disconnect();
2. 特定の信号に接続されているすべてのオブジェクトを切断します。
disconnect(myObject, SIGNAL(mySignal()), nullptr, nullptr);
非静的オーバーロード関数と同等:
myObject->disconnect(SIGNAL(mySignal()));
3. 特定の受信オブジェクトの接続を切断します。
disconnect(myObject, nullptr, myReceiver, nullptr);
非静的オーバーロード関数と同等:
myObject->disconnect(myReceiver);
nullptr
それぞれ「任意の信号」、「任意の受信オブジェクト」、または「受信オブジェクトの任意のスロット」を意味するワイルドカードとして使用できます。
次の形式の使用例:
disconnect(发送对象,信号,接收对象,方法)
-
送信オブジェクトはnullptr にはなりません。
-
signalが nullptrの場合、受信オブジェクトとスロットはすべての信号から切断されます。それ以外の場合は、指定された信号のみが切断されます。
-
受信オブジェクトが nullptr の場合、その信号に関連付けられたすべての接続が切断されます。それ以外の場合は、受信オブジェクトからスロットを切断するだけです。
-
Method が nullptr の場合、受信オブジェクトへの接続をすべて切断します。そうでない場合は、メソッドと呼ばれるスロット接続のみが切断されます。受信オブジェクトがない場合、メソッドはnullptr である必要があります。今すぐ:
disconnect(发送对象,信号,nullptr,nullptr)
5. サードパーティのシグナルおよびスロット関数で Qt を使用する
サードパーティのライブラリにもシグナル/スロット関数の仕組みがある場合、この時点で Qt のシグナルとスロット関数の仕組みを使用する必要があります。この開発シナリオでは、Qt は同じプロジェクトで両方のメカニズムを使用できます。次の行を qmake プロジェクト (.pro) プロジェクト ファイルに追加する必要があります。
CONFIG += no_keywords
moc キーワードのシグナル、スロット、および を定義しないように Qt に指示します。emit
これらの名前はサードパーティのライブラリ (たとえばBoost
、 ) によって使用されるためです。no_keywords フラグを指定して Qt シグナルおよびスロット メカニズムを引き続き使用したい場合は、ソース ファイル内のすべての Qt moc キーワードを、対応する Qt マクロ (Q_SIGNALS (または Q_SIGNAL)、Q_SLOT (または Q_SLOT)、および Q_EMIT) に置き換える必要があります。
この記事では、開発の観点から Qt のシグナルとスロット関数のメカニズムについて説明します。フォローアップ計画はソース コードの観点から始まり、シグナル関数とスロット関数の背後にある実装メカニズムに関する情報を記録します。はは(ಡωಡ)ヒアヒアヒア
[Embedded Xiaosheng] wx パブリック アカウントを検索してフォローして、よりエキサイティングなコンテンツを入手してください >>>>