| ホーム · All Namespaces · 全てのクラス · メインのクラス · グループ別 · モジュール一覧 · 関数一覧 |
シグナルとスロットはオブジェクト同士のやりとりに使われます。シグナル・スロット機構は Qt の中心的な機能であり、おそらく他のフレームワークによって提供される機能とは大きく異なっている部分です。
GUI プログラミングでは、あるウィジェットの変更を他のウィジェットにも通知したい場面があります。より一般的にいうと、あらゆるオブジェクト間でやりとりをしたいのです。例えば、ユーザーが Close ボタンをクリックしたら、ウインドウの close() 関数を呼び出したい、というようにです。
以前のツールキットではこれらのやりとりをコールバック関数を用いて実現していました。コールバックは関数のポインタであり、なんらかのイベントを通知してもらいたい場合はその処理をする関数にコールバック関数のポインタを渡します。処理関数は適切な時にその関数を呼び出すのです。しかし、コールバック関数には基本的な欠陥が2つあります:ひとつは、タイプ・セーフでは無いことです。コールバック関数が正しい引数で呼び出されるかどうか確かめる術はありません。もうひとつは、処理を行っている関数はコールバック関数について知らなければならず、コールバック関数と処理を行う関数とに強固な結合関係が発生してしまう、ということです。
Qt はコールバック技術に代わる方法を使用します: 私達はシグナルとスロットを使用します。シグナルはあるイベントが起こった際に発生します。Qt のウィジットは既にたくさんの定義済みのシグナルを持っていますが、ウィジットのサブクラスを作成することで自分でシグナルを追加することができます。スロットはある特定のシグナルの応答として実行される関数です。Qt のウィジットは既にたくさんの定義済みのスロットを持っていますが、あなたが感心のあるシグナルに応答するために、ウィジットのサブクラスを作成して自分でそのスロットを追加するのが一般的な習慣です。
シグナル・スロット機構はタイプ・セーフです。シグナルの引数の型は受信側のスロットの引数の型と一致しなければなりません。(実際には余分な引数は無視されるため、スロットの引数の数はシグナルの引数の数よりも少なくても良くなっています。) 引数の型の互換性をとるため、コンパイラは型の不整合を検出することができるのです。また、シグナルとスロットは緩く結合しています。シグナルを発行するクラスは、シグナルを受信するスロットについて知っている必要も、注意を払う必要もありません。Qt のシグナル・スロット機構は、シグナルをスロットに接続すれば、スロットはシグナルの引数で適切に呼ばれる、ということを保証します。シグナルとスロットはどんな型の引数を何個でもとることができ、それらは完全にタイプ・セーフです。
全てのクラスの基底である QObject やそのサブクラス (たとえば QWidget)はシグナルとスロットを含むことができます。シグナルはオブジェクトの状態が変更されたときにオブジェクトによって発行します。これがオブジェクト同士のやりとりの全てです。発行されたシグナルをなにが受信するかについては、知る必要も注意を払う必要もありません。これが真の情報カプセル化であり、オブジェクトがソフトウェアコンポーネントとして使えるという証にもなります。
スロットはシグナルを受信することにも使えますが、普通のメンバー関数でもあります。オブジェクトはシグナルを受信したかどうかはわからず、スロットもシグナルに接続されているかどうかはわかりません。これは Qt で独立したコンポーネントを作成できる、という保証になります。
ひとつのスロットに複数のシグナルを欲しい分だけ接続することも、逆にひとつのシグナルを必要な分だけのスロットに接続することもできます。シグナルを直接他のシグナルに接続することさえ可能です。(この場合、最初のシグナルが発行されるとすぐに次のシグナルを発行する、という動作になります。)
シグナルとスロットを組み合わせることで、強力なコンポーネントプログラミング機構を生成することができるのです。
最小限の C++ のクラス定義はこうなるでしょう:
class Counter
{
public:
Counter() { m_value = 0; }
int value() const { return m_value; }
void setValue(int value);
private:
int m_value;
};
これを QObjectを継承したクラス定義にするとこうなるでしょう:
#include <QObject>
class Counter : public QObject
{
Q_OBJECT
public:
Counter() { m_value = 0; }
int value() const { return m_value; }
public slots:
void setValue(int value);
signals:
void valueChanged(int newValue);
private:
int m_value;
};
ここで、 QObjectを基底としたバージョンは、前記したC++クラスと同じ内部状態を持ち、その状態にアクセスする公開メソッドを提供したうえで、シグナルとスロットを使ったコンポーネントプログラミングをサポートしています。オブジェクトの内部状態が変わったことを外部に通知するために、シグナル valueChanged()を提供し、また他のオブジェクトがシグナルを送信できるようにスロットも持っています。
シグナルとスロットを持つすべてのクラスは Q_OBJECT を他の宣言に先立ち記述しなければなりません。また、クラスは(直接、あるいは間接的に) QObjectを継承している必要があります。
スロットはアプリケーションプログラマによって実装されます。 Counter::setValue() スロットのとある実装としては以下のようなものがあげられます。
void Counter::setValue(int value)
{
if (value != m_value) {
m_value = value;
emit valueChanged(value);
}
}
ここで、 emit で記述された行では、新しく設定された値を引数としてシグナル valueChanged() を通知します。
以下にあげるコード断片では、まず2つの Counter オブジェクトを生成し、最初のオブジェクトのシグナル valueChanged() を2つ目のオブジェクトのスロット setValue() に QObject::connect() を使って接続しています。
Counter a, b; QObject::connect(&a, SIGNAL(valueChanged(int)), &b, SLOT(setValue(int))); a.setValue(12); // a.value() == 12, b.value() == 12 b.setValue(48); // a.value() == 12, b.value() == 48
関数として a.setValue(12) を呼び出すと、 a はシグナル valueChanged(12) を呼び出し、それを b はスロット setValue() slot, i.e. b.setValue(12) が呼び出されます。同様にして b もシグナル valueChanged() を通知しますが、 bの valueChanged() にはスロットが接続されていないため、無視されます。
この例で、 setValue() 関数は value != m_valueの場合にだけ、値を設定した後シグナルを発生させます。 これは(例えば, b.valueChanged() が a.setValue()に接続されているときに)無限ループに陥らないようにするためです。
シグナルはすべての接続先に通知されます。接続を複製した場合は、2つのシグナルが通知されるでしょう。接続はいつでも QObject::disconnect() を使って切断することができます。
この例ではオブジェクト同士が互いに中身を知り合う必要なく連携して動作できることを説明しました。これを実現するには、ただ互いを QObject::connect() 関数の呼び出し、または uicの 自動接続 機能を使って接続すれば良いだけです。
C++プリプロセッサは signals、 slots、 emit キーワードを置換もしくは削除して、Standard C++に適合させます。
まず moc をシグナルやスロットを含んだクラス定義にかけることで、コンパイル可能でアプリケーションのほかのオブジェクトとリンクできるC++ソースコードを生成します。ここで qmakeを使っていれば、プロジェクトの makefile に、自動的に moc を起動するルールを追加してくれます。
シグナルは、オブジェクトの内部状態が変更されたときに、それを知りたがっているクライアントまたは所有者に対して通知されます。そのシグナルを定義したクラス、およびそのサブクラスのみがシグナルを通知可能です。
シグナルが通知されると、接続されたスロットは通常の関数呼び出しのように直ちに実行されます。つまり、シグナル・スロット機構はGUIのイベントループとは独立している、ということです。 emit 文によってすべてのスロットで一回だけコードが実行されます。これは キューをはさんで接続された場合では少し事情が異なります。この場合は emit キーワードの後ろの処理が即実行され、スロットの実行は後回しになります。
複数のスロットがひとつのシグナルに接続されている場合、シグナルの通知があるとスロットは任意の順番で実行されます。
シグナルは自動的に moc によって生成されるので、 .cpp では実装してはいけません。また、決して返り値を持たない( void 型)ものとします。
引数について:経験上、シグナルとスロットは特殊な型を使わないようにすればより再利用がしやすくなります。 QScrollBar::valueChangedが QScrollBar::Range のような仮想の型を使っている場合、 QScrollBar. Connecting different input widgets together would be impossible.
スロットは接続されたシグナルが発火した際に実行されます。スロットはシグナルが接続できるという特徴を持つ以外は通常の C++ の関数で、普通に実行されます。
スロットは通常のメンバ関数なので、直接呼び出される際には通常の C++ のルールに従います。しかしながら、スロットとしてどんな相手からもアクセスレベルに関わらずシグナルスロット接続によって実行されます。これは何らかのクラスのインスタンスから発生したシグナルが、そのクラスとは関係ないクラスのインスタンスのプライベートなスロットを実行する原因となるということです。
スロットは仮想関数としても定義できます。これはいくつかの場面でとても有用です。
コールバックに比べると、シグナル・スロットはその柔軟性が増加する分若干遅いですが、実際のアプリケーションにとってはその違いは無視できるほど小さいです。一般的にいくつかのスロットに接続されているシグナルを発火させることは応答する virtual ではない関数を直接呼び出すのに比べると約10倍遅いです。 これは全ての接続に対して安全に繰り返し実行するための接続用のオブジェクトを作成するため(受信者が発火処理中に破棄されていないかどうかの確認など)と、全てのパラメータを一般的な形態でマーシャリングするためです。10個の virtual ではない関数コールは多いように感じられるのかもしれませんが、 new や delete といった処理に比べたら小さなものです。文字列や配列、リストといった裏で new や deleteが必要な型の場合、シグナルとスロットのオーバーヘッドは全体の関数呼び出しのコストのほんの一部しか占めないでしょう。
スロット内でシステムコールを呼び出したり、10個以上の関数を経由して呼び出しているとしても同様のことが言えます。i586-500MHzのシステム上では、ひとつのスロットが接続されたシグナルで毎秒2,000,000回の通知が可能です。2つのスロットが接続されている場合は毎秒1,200,000回です。シグナル・スロット機構が持つ単純さと柔軟さは、ユーザーが気づかないくらいですが、オーバーヘッドに払う価値の分はあるでしょう。
他のライブラリで変数に signals や slots と言った変数を宣言している場合、コンパイラはQtベースのアプリケーションと一緒にコンパイルすると警告やエラーを出力します。この問題を解決するには、 #undef でプリプロセッサの利用するシンボルを未定義にするなどの方法があります。
メタオブジェクトコンパイラ (moc)はC++ファイルにあるクラス定義を解析し、メタオブジェクトを初期化するC++コードを生成します。メタオブジェクトはメンバーのシグナルとスロット全部の名前と、その関数へのポインタを保持しています。
メタオブジェクトは class nameといったオブジェクトについての追加情報も保持しています。オブジェクトが特定のクラスを はシグナルとスレッドについてのほとんどの処理を定義している している場合、例えば、以下のようなコードが記述できます。
if (widget->inherits("QAbstractButton")) {
QAbstractButton *button = static_cast<QAbstractButton *>(widget);
button->toggle();
}
メタオブジェクトの情報は qobject_cast<T>() でも使用され、 QObject::inherits() に似ていますが間違いにくいです。
if (QAbstractButton *button = qobject_cast<QAbstractButton *>(widget))
button->toggle();
詳細は Meta-Object System を参照してください。
ウィジットの簡単なコメント付きの例です。
#ifndef LCDNUMBER_H
#define LCDNUMBER_H
#include <QFrame>
class LcdNumber : public QFrame
{
Q_OBJECT
LcdNumber はシグナルとスレッドについてのほとんどの処理を定義している QObjectを QFrame と QWidgetを通して継承しています。 これはビルトインウィジットの QLCDNumber となんとなく似ています。
ここで Q_OBJECT マクロはプリプロセッサによって mocで実装されるいくつかのメンバ関数の宣言に展開されます; もし "undefined reference to vtable for LcdNumber" というエラーが発生した場合は、 moc の実行 を忘れているか、moc の出力結果をリンク時のコマンドに含めるのを忘れているでしょう。
public:
LcdNumber(QWidget *parent = 0);
ここは一見 moc とは関係ないように見えますが、もし QWidget を継承したクラスを作成した場合、あなたはほぼ間違いなく parent をコンストラクタの引数に持ち、それを基底クラスのコンストラクタに渡そうとするでしょう。
デストラクタとメンバ関数はここでは省略します; moc はメンバ関数は無視します。
signals:
void overflow();
LcdNumber は表示不可能な値を設定された場合にシグナルを発生させます。
もしオーバーフローを気にしなかったり、オーバーフローが起きないことを知っている場合、この overflow() シグナルを無視することができます。例えばそれをスロットに全く接続しない場合など。
逆に、もしオーバーフローが起きた場合に2つの異なるエラー処理関数を呼びたい場合は単にこのシグナルを2つの異なるスロットに接続してください。 Qt は両方を(順不同で)呼び出します。
public slots:
void display(int num);
void display(double num);
void display(const QString &str);
void setHexMode();
void setDecMode();
void setOctMode();
void setBinMode();
void setSmallDecimalPoint(bool point);
};
#endif
それぞれのスロットは他のウィジットの状態の変化を受けとるために使われる関数です。 LcdNumber はこれらを関数名の通りの目的で表示する数字を設定するために使用します。 display() は他のプログラムの部分に対するこのクラスのインターフェースになるため、このスロットは public です。
サンプルプログラムのうちのいくつかは valueChanged() シグナル( QScrollBar の)を display() スロットに接続するので、この LCD number は絶えずそのスクロールバーの値を表示するでしょう。
ここで display() はオーバーロードされています; Qt はシグナルをスロットに接続する際に適切なものを選択するでしょう。コールバックでは5つの異なる名前を付けて、それぞれの型に応じた名前の関数を使用するようにあなた自身が注意しなければいけません。
いくつかのここでは関係の無いメンバ関数は省略されています。
シグナルを発生させたクラスの情報が必要になるかもしれないので、Qt は QObject::sender() 関数を提供します。これはシグナルを送ったオブジェクトのポインタを返します。
QSignalMapper クラスはたくさんのシグナルが同じスロットに接続されていて、そのスロットが各シグナルを別々の方法で処理する状況で使用するためのクラスです。
「税金ファイル」、「会計ファイル」、「報告書ファイル」の3つの、どのファイルを開くかを決めるボタンがある状況を考えましょう。
In order to open the correct file, you use QSignalMapper::setMapping() to map all the clicked() signals to a QSignalMapper object. Then you connect the file's QPushButton::clicked() signal to the QSignalMapper::map() slot.
signalMapper = new QSignalMapper(this);
signalMapper->setMapping(taxFileButton, QString("taxfile.txt"));
signalMapper->setMapping(accountFileButton, QString("accountsfile.txt"));
signalMapper->setMapping(reportFileButton, QString("reportfile.txt"));
connect(taxFileButton, SIGNAL(clicked()),
signalMapper, SLOT (map()));
connect(accountFileButton, SIGNAL(clicked()),
signalMapper, SLOT (map()));
connect(reportFileButton, SIGNAL(clicked()),
signalMapper, SLOT (map()));
それから mapped() シグナルを readFile() に接続し、どのボタンが押されたかによって異なるファイルをオープンする処理を行います。
connect(signalMapper, SIGNAL(mapped(const QString &)),
this, SLOT(readFile(const QString &)));
サードーパーティーのシグナル/スロットメカニズムを Qt で使用することが可能です。同じプロジェクトで両方のメカニズムを使用することさえも可能です。次の行を qmake のプロジェクトファイル(.pro)に追加するだけです。
CONFIG += no_keywords
これは Qt に moc のキーワード( signals、 slots、 emit)を、これらの名前が Boost の様なサードパーティーのライブラリで使用されるので、定義しないように知らせます。Qt のシグナルとスロットを no_keywords フラグの場合でも使い続けるには、ソース内の Qt の moc キーワードを対応する Qt のマクロ Q_SIGNALS (or Q_SIGNAL), Q_SLOTS (or Q_SLOT), and Q_EMITも参照してください。
Meta-Object System と Qt's Property Systemも参照してください。
| Copyright © 2009 Nokia Corporation and/or its subsidiary(-ies) | Trademarks | Qt 4.5.0 |