Qt最新事情-QtでWebKitを使ってみよう

第2回 Qtの基本プログラミング~入手方法,シグナルとスロット

この記事を読むのに必要な時間:およそ 3 分

シグナルとスロット

シグナルとスロットの仕組みを少し詳しく追ってみましょう。スライダの値を表示させるコードは,Qtではこのようになります。

QSlider* slider = new QSLider;
QLDCNumber* lcdNumber = new lcdNumber;
QObject::connect(slider, SIGNAL(valueChanged(int)), 
                 lcdNumber, SLOT(display(int)));

valueChanged(int)がシグナルで,display(int)がスロットです。どちらも実際にはC++のメソッドして実装されます。シグナルはメッセージを送信するC++メソッド,スロットはシグナルの受取りもできるC++メソッドです。SIGNAL()とSLOT() はマクロで,引数を文字列にします。connect()は,その文字列をキーにしてオブジェクトのメソッド情報などが入っているメタ情報を検索し,オブジェクト間を接続することで,シグナルがスロットに渡るようにします。

図1 シグナルとスロットの接続

図1 シグナルとスロットの接続

スライダを操作して値が42になったときに,センダーsliderはemit valueChanged(42)でシグナルを送信します。emitは,Qtが拡張したキーワードでシグナルの送信をはっきりさせるために使われ,コンパイル時には空に置換えられ,単なる関数呼出しになります。シグナルはconnect()で結び付けられたlcdNumberのスロットdipslay(int)で受信されて表示されます。

シグナルとスロットのコードの実装方法は次のようになります。

class CustomClass : public QObject
{
    Q_OBJECT

signals:
    void valueChanged(int);

public slots:
    void display(int);
};

シグナルとスロットの仕組みを使うには,それを実装しているQObjectクラスを継承します。Q_OBJECTマクロにはメタ情報にアクセスするためのmetaObject()やメッセージ翻訳のためのtr()などの関数宣言がされています。signalsとslotsは,メソッドがシグナルやスロットとなることを示すためにQtが拡張したキーワードです。Qtのコマンドプログラムmoc (Meta Object Compiler) は,このクラス宣言を読んで,Q_OBJECTで宣言された関数とシグナルの実装コードを生成します。コンパイル時には,signalsはprotectedに,slotsは空にそれぞれ置換えられます。

タイプセーフコールバック

Qtのシグナルとスロットという仕組みは,オブジェクト間のタイプセープなコールバックで,型の安全性を確保し,即値受け渡しも可能なため扱い易く,早期に不整合が発見され修正も容易です。

コールバックは,キャストを必要としたり,型チェックが困難で,ポインタでデータを受け渡すために領域確保が必要であったりします。しかし,Qtでは前述のconnect()のようにセンダーとレシーバーのシグニチャを突き合せて,引数の型と順序が合う場合のみシグナルとスロットの接続をします。したがって,シグナル送信でスロットが呼び出されるときに引数の型の不一致でクラッシュはしません。整合性がなくて接続ができない場合にはスロットは呼び出されず,デバッグオプション付きでコンパイルされていれば,connectの実行時に警告メッセージが標準エラーに出力されます。

connect()による接続が成功するのは2通りの場合があります。

  • シグナルとスロットのシグニチャが一致する。
    今迄の通りです。
  • スロット側の引数が少ない。途中迄は引数の型と順序は一致している。
connect(redSlider, SIGNAL(valueChanged(int)),
        this, SLOT(sliderMoved()));
connect(greenSlider, SIGNAL(valueChanged(int)),
        this, SLOT(sliderMoved()));
connect(blueSlider, SIGNAL(valueChanged(int)),
        this, SLOT(sliderMoved()));

オブジェクトの疎結合とカプセル化

センダーとレシーバーは,お互いに相手がどのクラスのオブジェクトかを知らなくとも必要なデータを渡してメソッドを呼び出せます。センダーはシグナルを送信するだけ,レシーバーはシグナルを受信するだけです。従って,オブジェクトの相互依存性を低くし,カプセル化を促進させます。

シグナルとスロットの接続の組み合わせ

シグナルとスロットは次の3通りの組み合わせで接続できます。

  • シグナルを複数のスロットに接続する
  • connect(slider, SIGNAL(valueChanged(int)), 
            lcdNumber, SLOT(display(int)));
    connect(slider, SIGNAL(valueChanged(int)), 
            dial, SLOT(setValue(int)));
    
  • 複数のシグナルを同じスロットに接続する
  • connect(mainWindow, SIGNAL(message(const QString&)),
            logger, SLOT(logging(const QString&)));
    connect(findRecord, SIGNAL(erroMessage(const QString&)),
            logger, SLOT(logging(const QString&)));
    
  • シグナルをシグナルに接続する
  • connect(slider, SIGNAL(valueChanged(int)), 
            this, SIGNAL(valueChanged(int)));
    

シグナルはblockSignals()でブロックでき,接続はdisconnect()で解除できます。

スレッド間でも使用可能

イベントループへのアクセスがスレードセーフな関数を用いて,イベントループを介してスロットを呼出すことにより,異なるスレッド上にあるオブジェクト間でもシグナルとスロットを使えます。

Object::connect(sender, SIGNAL(signal),  receiver, SLOT(method), type)

connect()にの5番目の引数typeで,コネクションタイプを指定し,スレッド間の接続ではQt::QueuedConnectionを使います。typeを省略するとデフォルトのQt::AutoConnectionです。

表1 コネクションタイプ

コネクションタイプ説明
Qt::DirectConnectionシグナルが送信されるとすぐにスロットを呼び出し,スロットの処理を抜けるとシグナル送信から戻る。
Qt::QueuedConnectionシグナルが送信されるとシグナルをイベントキューに入れ,イベントループ介してスロットを非同期的に呼出す。
Qt::BlockingQueuedConnectionQueuedConnectionと同様だが,スロットの処理を抜けるとシグナル送信から戻る。
Qt::AutoConnectionレシーバーとセンダーが同一スレッドにあればDirectConnection,異なるスレッドにあればQueuedConnectionを用いる。

main()のあるメインスレッド (GUIスレッド) は,イベントハンドリングをするためmain()の最後の方でイベントループを走らせました。他のスレッドでシグナルとスロットを使う場合にもスレッドでイベントループを走らせます。Qt4.4からはデフォルトで,スレッドにイベントループが走るようになっています。

まとめと次回予告

今回は,Qtの中核機能からシグナルとスロットについて説明しました。次回は,シグナルとスロットの実装を支えるオブジェクトモデルについて説明します。

著者プロフィール

杉田研治(すぎたけんじ)

1955年生まれ。東京都出身。株式会社SRAに勤務。プログラマ。

仕事のほとんどをMac OS XとKubuntu KDE 4でQtと供に過ごす。