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

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

はじめに

前回は、Qtの歴史そして現在と展望について説明しました。今回は、Qtの入手方法について簡単に触れた後、Qt WebKitの説明の前に、Qtの中核機能の中から3回に分けて最も重要なシグナルとスロット、オブジェトモデル、レイアウトマネージメントについて、少し普通とは違った視点で説明します。

入手方法

Qtには、商用版、評価版、オープンソース版のライセンスがあり、オープンソース版の入手方法は2通りあります。

  • UNIX/Linuxディストリビューションのパッケージを利用する
    Ubuntu/Kubuntu 8.04などのディストリビューションにはパッケージが用意されています。合わせてQt4ベースのKDE 4もパッケージで入手できます。
  • Trolltechのサイトからダウンロードする

サポートされるコンパイラ

UNIX/LinuxではGCCやSunCCなどのコンパイラがサポートされます。PhononやWebKitといったQt4.4の新しい機能については、比較的新しいバージョンのコンパイラが必要です。

WindowsではVisual StudioやMinGWなどのコンパイラがサポートされます。最近になってオープンソース版もVisual Studioのコンパイラが使えるようになっています。

Eclipse CDTで、Qtの開発をできるようにしたQt Eclipse IntegrationがTrolltechより配布されています。同様に、商用版のQtでは、Qt Visual Studio Integrationが使えます。

サポートされるコンパイラとプラットフォームの詳しい内容は以下より調べられます。

簡単なQtプログラム

ボタンを表示し、クリックすると終了するQtプログラムは次のようになります。

int main(int argc, char** argv)
{
    QApplication app(argc, argv);
    
    QPushButton hello("Hello!");
    QObject::connect(&hello, SIGNAL(clicked())
                     app, SLOT(quit()));

    return app.exec();
}

UNIX/Linuxはもちろんですが、WindowsでもQtプログラムのエントリーポイントはmain()関数です。QApplicationのインスタンスはプログラムに1つだけ作り、OSやウィンドウシステムとの初期化を行います。QPushButtonはQtのボタンです。QObject::connect()は、ユーザ操作を処理に結びつけるためにシグナルとスロットの接続をしています。ここでは、ボタンをクリックするとQApplicationクラスのquit()を呼出してイベントループを抜けるようにしています。app.exec()は、イベントループの開始です。

シグナルとスロット

シグナルとスロットの仕組みを少し詳しく追ってみましょう。スライダの値を表示させるコードは、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の中核機能からシグナルとスロットについて説明しました。次回は、シグナルとスロットの実装を支えるオブジェクトモデルについて説明します。

おすすめ記事

記事・ニュース一覧