はじめに
前回は、Qtの中核機能からシグナルとスロットというタイプセーフなコールバックについて説明しました。今回は、シグナルとスロットの実装の基礎となっているQtのオブジェクトモデルについて説明します。
オブジェクトモデル
Qtのオブジェクトモデルでは、C++オブジェクトにシグナルとスロットを含む表1の機能拡張をして、GUIプログラミングに必要とされる柔軟性を高めています。
表1 オブジェクトモデルで拡張された機能
シグナルとスロット | タイプセーフコールバック、オブジェクトの疎結合とカプセル化 |
オブジェクトプロパティ | 問い合わせ可能なプロパティ |
イベントとイベントフィルター | イベント処理とイベントフック |
国際化用テキスト文字列の翻訳 | クラスをコンテキストとするテキスト文字列翻訳 |
インターバルタイマー | イベント処理と統合されたタイマー |
オブジェクトツリー | オブジェクトの階層化と検索 |
ガーディドポインター | QPointer。Qt 4.5でQSharedPointerとQWeakPointerが追加 |
ダイナミックキャスト | ライブラリ境界制限のないダイナミックキャスト |
これらの機能は、オブジェクトクラスQObjectに対して実装されています。この中からオブジェクトプロパティ、オブジェクトツリー、ダイナミックキャストの3つ、それとこれらを支えているメタオブジェクトシステムについて説明します。
オブジェクトツリー
図1のように、既存のウィジェットをウィジェットの上に配置して作成するウィジェットをコンポジットウィジェットと呼び、以下の5つがその実装上のポイントです。
- ウィジエットがどのウィジェット上に置かれているか
- ウィジェットをどのように並べるか
- ウィジェットの表示/非表示の制御
- ウィジェットの有効化/無効化の制御
- ウィジェットのメモリ割り当てと解放の制御
Qtでは、これらの機能の実装にオブジェクトツリーが利用されています。2番目の「ウィジェットの並べ方」は、次回に詳しく説明します。
図1のコンポジットウィジェットのオブジェクトツリーは、レイアウトのためのオブジェクトを省略して、ウィジェットのみに着目すると、図2のようになります。
QFrameとQGroupBoxの親オブジェクトはQWidget、QLabelとQSliderの親オブジェクトはQGroupBoxです。QFrameの親クラスはQWidgetですが、QLabelやQSliderの親クラスはQGroupBoxでないことからわかるように、クラスの継承関係とは異なることに注意しましょう。これらのオブジェクトの親子関係はQObjectの機能で、QObjectを継承したQWidgetでは、この親子関係が、ウィジェットがどのウィジェットの上に乗っているかというウィジェットの上下関係に対応付けられています。
ウィジェットの親子関係に着目したコードはリスト1のようになります。
ウィジェットのインスタンス生成時に親ウィジェットを指定しているのがポイントです。QObject またはその継承クラスのインスタンス生成では、親オブジェクトを指定して、オブジェクトツリーを形成できるようにしています。親オブジェクトは子オブジェクトのリストを持っていて、子オブジェクトは親オブジェクトを指すポインタを持っています。ウィジェットに親子関係を付けることによって、以下のことが可能となっています。
1.子ウィジェットのメモリ解放の自動化
親オウィジェットのメモリ解放時にオブジェクトツリーを辿って、その配下(親子関係の繋がりのあるウィジェット)の全ウィジェットのメモリ解放を自動的に行なっています。
デストラクタでのウィジェットのメモリ解放が不要となり、メモリ解放の確実性が高まります。後から参照するウィジェットの他には、メンバー変数でオブジェクトを抱えておく必要がなくなり、コードがすっきりします。
QTimerクラスなどのQObjectを継承した非GUIクラスも親ウィジェットを指定してインスタンス生成して、メモリ解放を連動させられます。QObjectを継承していないクラスについては、通常のC++のインスタンスの扱いと同じようにしてメモリ解放をします。
2.オブジェクトツリー内のオブジェクト検索
以下のようにして、オブジェクトツリー内のオブジェクトを検索し、ウィジットを操作できます。
ここで、QLabelのインスタンスには、以下のようにてオブジェクト名が付けられているものとしています。
3 親ウィジェットの表示/非表示に応じて、配下のウィジェットを表示/非表示する。
show()を呼び出して親ウィジェットを表示状態にしたときに、その配下のウィジェットを自動的に表示します。hide()を呼び出すと非表示にします。
4 親ウィジェットの有効化/無効化に応じて、配下のウィジェットを有効化/無効化する。
setEnabled(true)を呼び出して親ウィジェットを有効化したときに、その配下のウィジェットを自動的に有効化します。setEnabled(false)を呼び出すと無効化します。
表示/非表示と有効化/無効化は、それぞれ三値状態で管理されているので、子ウィジェットが明示的にhide()やsetEnabled(false)を呼び出していると、親ウィジェットのshow()やsetEnabled(true)の呼び出しに、子ウィジェットは追従しなくなります。
子ウィジェットの自動メモリ解放に関連して、暗黙の共有クラスについても触れておきます。Qtでは、文字列、フォント、ピックスマップなどのリソースやコレクションクラスは、リファレンスカウント付きのクラスとして実装されていて、値ベースでデータを扱えるようにしています。関数の戻り値としてQStringやQPixmapのインスタンスを返しても文字列やイメージ領域の本体はコピーされません。コレクションクラスについても同様です。共有リソースに変更が行われようとするときに、データが実際にコピーされて変更が行われ、リファレンスカウントも変更されます。そして、リファレンスカウントが0になったときに、Qtライブラリーが自動的にメモリ領域を解放します。一般に、値ベースでデータを扱えるとコードが簡潔で明瞭になり、メモリ解放も確実にできて安定性が向上します。
オブジェクトプロパティ
QObjectの継承クラスに任意のプロパティを持たせることができ、オブジェクトにどのようなプロパティがあるかを検索して、プロパティの参照と変更ができます。
リスト2では、nameとageの2つのプロパティを宣言しています。アクセッサに対してQ_PROPERTYマクロでプロパティ宣言を記述すると、property()メソッドを使ってプロパティ名でプロパティにアクセスできるようになります。READアクセッサは必須で、プロパティ名とREADアクセッサ名は異なっても構いません。実際にプロパティにアクセスするコードは次のようになります。
weightプロパティは宣言されていないので、通常は未定義になりますが、実行中にsetProperty()で未定義のプロパティに値を設定するとその時点でプロパティが定義されます。これをダイナミックプロパティと呼びます。
QVariantは、コピー可能な任意のオブジェクトをひとつ抱える機能を持つクラスです。C++の組込み型とQtのクラスは扱えるようになっていて、Qtのタイプシステムによってユーザ定義型も扱えます。
ダイナミックキャスト
QObjectの継承クラスに対して、RTTI(Run Time Type Information)を使わずにダイナミックキャストを可能にしています。dynamic_castと異なり、共有ライブラリーを跨いで使用できるが利点です。以下のように扱い方は dynamic_castと同じです。
メタオブジェクトシステム
これまで説明した機能の実現を支えているのがメタオブジェクトシステムです。QObject::metaObject()メソッドはメタオブジェクトを返し、クラス名、シグナルとスロットのメソッド、プロパティ、列挙型と集合型、スーパークラスの情報などのオブジェクトのクラスのメタ情報にアクセスできるようにしています。
metaObject()が返すのは変数staticMetaObjectのアドレスで、metaObject()の実行コードとstaticMetaObjectの初期コードは、moc (Meta Object Compiler) がクラス宣言をパーズして生成されます。この他にmocは、ダイナミックキャストのためのqt_metacast()、シグナルとスロットのメソッド呼び出しのためのqt_metacall()、シグナルの実装コードなども生成します。
リスト5は、列挙型、シグナルとスロットのメソッド、プロパティに、メタオブジェクトを使ってアクセスをするコードの例です。
リスト6のクラス宣言に対しての実行結果はリスト7のようになります。
アクセス結果にシグナルとスロットのメソッドがあることに注目しましょう。シグナルとスロットのconnect()での接続時には、このメタ情報によってオブジェクト間を接続し、その情報をオブジェクトに設定しています。そして、シグナルを送信するというのは、シグナルメソッドが呼び出され(スレッド間の場合にはイベントループを介します)、接続先のオブジェクトのスロットを呼び出すことなのです。
QMetaObject::invokeMethod()は、シグナルとスロットを呼び出すために公開されているAPIです。
このようにするとQSliderのシグナルvalueChanged(int)が送信され、接続されたスロットでシグナルが受け取られます。強制的にシグナルを送信しているので、sliderの実際の値とは異なる値でシグナルが送信されることに注意しましょう。同様にしてスロットの呼び出しもできます。
まとめと次回の予告
今回はQtのアーキテクチャの基礎となっているオブジェクトモデルについて説明しました。次回は、Qtの基本プログラミングの最後として、ウィジェットを配置するレイアウトマネージメントを普通とは違う視点で説明します。