2015/08/27

メタオブジェクトシステム――C++/Qtの深いところ#2

Qtのメタオブジェクトシステムについて解説します
※注意 Qt5.5時点の情報です。

目次

  • 目的
  • メタオブジェクトシステム
    • 前置き
    • 概要
    • 使い方
    • 型変換
    • メタオブジェクトシステムの機能
    • (補足)QObjectの機能
  • まとめ
  • 次回予告

目的

C++/Qtを利用する際にハマりがちな、メタオブジェクトシステムについて調査します。わかりやすく言えば、筆者がハマってしまったところを、後々の方々が見返しやすいようにまとめています。英語ドキュメントが多く、誤解を生みがちなので、筆者と同じ轍を踏まないようにできればと考えています。このシリーズでは、あまり詳しいコードを書くつもりはありません。C++の経験はあるけれど、Qtはこれからだという方を対象に、噛み砕いて説明します。

メタオブジェクトシステム

前置き

C++を使っていてよく頭が沸騰してくる原因の一つが、#1の冒頭でも少し触れた、メタプログラミングによる安全でない強い静的型付けです。しかしC++の標準化委員会はこういう弱小な問題には一切触れず、クロージャやコンパイル時定数型などをあれよあれよと導入してしまいます。ですので、このマニアックな静的型付けはプログラミング初心者をはじめ、筆者を含めた全国のゆるふわC++使いを置き去りにし、悩ませます。そんな私達は、例えば簡易的なゲームスクリプトや拡張機能を書く場合などに、C++に組み込みLuaやECMAScript等を導入するのですが、そこに助け舟として登場するのが、Qtのメタオブジェクトシステムです。名前に「メタ」が付いてるって?気にしたら負けだ!

概要

Qtと聞いたら、シグナル/スロットを連想する方も多いのではないでしょうか。実は、これはメタオブジェクトシステムが提供する機能の一つです。メタオブジェクトシステムは、概念的には、オブジェクト間通信や、実行時型情報、動的プロパティシステムにより、様々な機能をQtで書かれたオブジェクトに提供します。オブジェクト間通信というのは、QMLやECMAScriptのオブジェクト、Qtデザイナーと連携していたりもします。

使い方

メタオブジェクトシステムを利用するためには、まず、ユーザーはメタオブジェクトであることを宣言する必要がありますが、これは簡単です。QObjectを継承し、Q_OBJECTマクロを書いたクラスは、moc(メタオブジェクトコンパイラ)により全て、メタオブジェクトであると認識されます。話が逸れますが、QObjectを継承しない自作データクラスや、QObjectへのポインタはQ_DECLARE_METATYPEによりメタタイプにすることができます。しかし、これはメタオブジェクトではありません。メタタイプとしてQVariantからキャストしたり、Qtの内部に保持しておける、という意味です。なお、QObjectはNONCOPYABLEなので、メタタイプにはできません。必要な場合は、QObjectのポインタをメタタイプとして宣言しましょう。

型変換

メタオブジェクトの特性の一つとして、全てのQObject間で、型変換が可能であるという点があります。例えば、MyClassAを、全く関係ないMyClassBにキャストしてしまうことが可能です。もちろん、未定義の変換をするとメモリ領域が見つからなくなり、セグメント違反になります。実際には、親クラスを引数として受け取ったものを、元のサブクラスにキャストしたり、そのままメタオブジェクトとして取り扱ったりします。

メタオブジェクトシステムの機能

※一部、メタではない版も存在します。
  • シグナル/スロット(異なるオブジェクト間で、引数とともにシグナルを送受信し、動的にスロットを呼び出す。Qt5.5時点では、オーバーロード不可) 
  • プロパティシステム(どこからでもアクセスできる、アクセサ要らずで安全なオブジェクトプロパティ)
  • 型変換(前述)
  •  メタ列挙型(サブクラスの列挙型をスーパークラスから取得したり、スクリプトからアクセスしたりする) 
  • メタ関数(サブクラスの関数呼び出しが可能。引数や返り値にも対応。スクリプトからも呼び出せる) 
  • 実行時型情報(これで比較とか) 
  • 翻訳(Qtアプリケーションをクロスプラットフォームの多言語対応。QtLinguistから扱える) 
  • メタコンストラクタ(新しいインスタンスを作るために使われる。staticMetaObjectという、メタオブジェクトに生成される静的オブジェクトを経由して使われることが多い)
  • Qフラグ(ビット演算によるフラグ管理)
  • 継承関係の取得(どのクラスを継承しているかを実行時判定)

(補足)QObjectの機能

QObjectとQMetaObjectは形式的に分離されたクラスなのですが、tr関数や型変換等、いちいちQMetaObjectを経由せずに済むように、一部混ぜ込ぜになっている関数もあります。ですので、それらを整理するために、QObjectとしての機能も書いておきます。
  • イベント(Qtの呼び出すフックのハンドラを書ける)
  • マルチスレッド(オブジェクトが動いているスレッドを返したり、スレッドに参加したりできる)
  • タイマーイベント(設定した時間ごとに呼ばれる。複数指定可能)
  • 親子関係(オブジェクトの親や子を関連付ける)
  • 子を探す(親子関係にある子を返す)
  • 等々・・・

まとめ

ザックリとした記事でしたが、ここまで読んでくださいまして、ありがとうございました。テストコードが少なかったでしょうか。Qtドキュメントに多くのサンプルが載せられているので、なるべく紙面を簡略化するためにも、この記事では省略してみました。
Qtをあまり知らなかったという方は、Qtに少しでも興味を持っていただければ幸いです。また、Qtを今利用しているという方は、こういうこともできる、とか、これは知らなかった、ここはこうだ、などの感想を頂けると幸いです。ご意見、ご質問等も可能な範囲で受け付けております。
普通のC++erの方々は、メタオブジェクトシステムを外道だと思われるかもしれません。その場合、言語レベルでのより良い解決策がありましたら、ご教授賜りたいと思います。
なお、翻訳機能など、メタオブジェクトシステムとの関係がわかりにくい機能もあるので、その周辺は今後、個別に掲載する予定です。
mocって響きがキュートだよね。ネザーランドドワーフみたい。

※この記事はQt公式ドキュメント(doc.qt.io)を参考に書かれました。

次回予告

Qtを本格的に学ぶための布石が整ってきましたので、次回はQtGUIを支えるQtのグラフィックスを解説する予定です。

0 件のコメント:

コメントを投稿