Qt のオブジェクトシステム( QObject と関連クラス)にはプロパティのシステムが用意されています。C++ だけを使用してプログラムするときには意識する必要はほとんどありませんが、Qt Script や Qt WebKit、QML などが C++ と連携する場合には非常に重要な仕組みです。この記事では Qt のプロパティシステムと、Qt 5.1 で追加される MEMBER 指定子について簡単に説明します。
プロパティとは
まず、プロパティとは何でしょうか。プロパティはオブジェクトの属性のことです。例えば、QWidget であればその幅( width )や高さ( height )、QLabel であればその文字列( text )などがプロパティになります。
C++ ではそれらのプロパティの値は getter と呼ばれる関数を通じて取得し、setter と呼ばれる関数で設定します。先ほどの QLabel の場合、QLabel::text() が getter で、QLabel::setText() が setter になります。このように C++ ではメンバー関数を通じて利用するために getter で取得する値がプロパティかどうかは意識することはまずありませんが、たとえば QML でアクセスする場合には getter / setter はエンジン側で自動的に使用され、ユーザーからはプロパティは変数のように見えます。
Q_PROPERTY マクロ
プロパティを作成するには Q_PROPERTY マクロを使用します。最低限必要なのはプロパティの型とその getter です。読み書きの双方を許可する場合には setter も必要となります。QML でバインディングするのであれば値が変わったことを通知する SIGNAL も作成してください。
書式は以下のようになります。この書式は Qt4 も Qt5 も共通です。
Q_PROPERTY(型 名前 READ getter) Q_PROPERTY(型 名前 READ getter WRITE setter) Q_PROPERTY(型 名前 READ getter WRITE setter NOTIFY signal)
例えば、int 型の count というプロパティを持つ Counter クラスを作るとします。getter として count() メソッドを作成し、setter として setCount()、SIGNAL に countChanged() を使用する場合には以下のように書きます。
class Counter : public QObject { Q_OBJECT Q_PROPERTY(int count READ count WRITE setCount NOTIFY countChanged) public: Counter(QObject *parent = 0); int count() const { return m_count; } void setCount(int value) { if (m_count != value) { m_count = value; emit countChanged(); } } singals: void countChanged(); private: int m_count; };
プロパティ名と getter の名前は同じにすることも多いですが、別のものでもかまいません。また、NOTIFY に指定する SIGNAL は他にも同時に変化するプロパティがあるならば同じものを共用してもかまいません。また、型は QVariant で扱える型であれば何でもかまいませんが、カンマ(“,”)が含まれているとマクロで扱う性質上エラーになるため、そういう場合はあらかじめ typedef で定義しておく必要があります。
このように Q_PROPERTY マクロを書き、関連するメソッドを宣言・定義して初めてプロパティとして扱えるようになります。このプロパティのシステム、QML と C++ の双方を用いるアプリケーションで連携を行うにはほぼ必須の便利な機能です。ただ、プロパティの数が少なければ問題ないのですが、数が増えてくると getter や setter、SIGNAL の生成がとたんに面倒になってきます。多くの場合は中身も定型のため、書いていて面白くもありません。最近の Qt Creator では Q_PROPERTY マクロにカーソルを合わせてリファクタリング機能で不足するメソッドやメンバ変数を生成することも出来ますが、書式的にそのまま使いたくない場合も少なくありません。そこで登場したのが MEMBER 指定子です。
MEMBER
Qt 5.1 で導入される MEMBER では Q_PROPERTY マクロの記述時に READ や WRITE で getter / setter を指定する代わりに、対応するメンバ変数を指定します。先ほどの Counter クラスを MEMBER を用いて書き直してみましょう。
class Counter : public QObject { Q_OBJECT Q_PROPERTY(int count MEMBER m_count NOTIFY countChanged) public: Counter(QObject *parent = 0); singals: void countChanged(); private: int m_count; };
MEMBER を使用すると、getter / setter が不要になり、それらのメソッドを作成する必要がなくなります。相当する処理は moc が生成し、メタオブジェクトシステムに組み込まれます。値の変更時に通知を行いたい場合には SIGNAL の作成と NOTIFY での指定は必要ですが、SIGNAL の emit 自体はやはり moc が担当します。上記の例では引数を持たない値の通知は行わない SIGNAL を用いていますが、
void countChanged(int value);
のような値の通知を伴う SIGNAL でも問題ありません。
MEMBER は READ または WRITE 指定子と一緒に使用することも出来ます。READ と MEMBER を指定する場合には getter は作成する必要がありますが、setter は moc が作成します。逆に WRITE と MEMBER を指定する場合は setter は作成する必要がありますが、getter は moc が作成します。MEMBER と READ、WRITE のすべてを指定することは意味が無いため、サポートの対象外です。setter で範囲チェックを行いたい場合などに setter だけ作成して、getter は作成せずに MEMBER を用いるのが主な使い方でしょうか。
以上のように MEMBER を使うとクラス作成時の記述量を大きく減らすことが出来ますが、副作用として getter / setter となるメソッドが無くなるため、C++ 側からは QObject::property() や QObject::setProperty() メソッドを使用してアクセスすることとなります。property() や setProperty() はパフォーマンス的には適切に生成された getter / setter よりも劣るため、C++ 側からも大量にアクセスしたいプロパティでの利用には向きません。
参考文献
非常に簡単に説明したので不明点もあると思いますが、その場合はコメントなどで質問してください。プロパティシステムにはここで使用した以外にもたくさんの指定子があります。詳しいことは各バージョンの Qt に “The Property System” というドキュメントがありますのでそれを参考にするといいでしょう。
- The Property System (Qt5 stable)
- [QTBUG-16852] moc: Q_PROPERTY attribute for auto-generated getters/setters/emitter