メタオブジェクトコンパイラ(moc)を使う

メタオブジェクトコンパイラ(moc )はQt の C++ 拡張を扱うプログラムです。

moc ツールは C++ のヘッダーファイルを読み込みます。Q_OBJECT マクロを含むクラス宣言が1つ以上見つかると、それらのクラスのメタオブジェクトコードを含むC++ソースファイルを生成します。特に、メタ・オブジェクト・コードは、シグナルとスロットのメカニズム、ランタイム型情報、およびダイナミック・プロパティ・システムに必要です。

moc 、生成されたC++ソース・ファイルをコンパイルし、クラスの実装とリンクする必要があります。

qmakeCMake の両方が、moc を適宜呼び出すビルド・ルールを持つ makefile を生成するので、moc を直接使用する必要はありません。qmake はデフォルトでこれらのビルド・ルールを追加しますが、CMake ではAUTOMOCプロパティを使用してmoc を自動的に処理できます。moc の背景については、「QtはなぜシグナルとスロットにMocを使うのか」を参照してください

使用方法

moc は通常、このようなクラス宣言を含む入力ファイルで使用されます:

class MyClass : public QObject
{
    Q_OBJECT

public:
    MyClass(QObject *parent = 0);
    ~MyClass();

signals:
    void mySignal();

public slots:
    void mySlot();
};

上に示したシグナルとスロットに加えて、moc は次の例のようにオブジェクト・プロパティも実装しています。Q_PROPERTY() マクロはオブジェクト・プロパティを宣言し、Q_ENUM() はプロパティ・システム内で使用可能なクラス内の列挙型のリストを宣言します。

次の例では、priority とも呼ばれ、get 関数priority() と set 関数setPriority() を持つ列挙型Priority のプロパティを宣言します。

class MyClass : public QObject
{
    Q_OBJECT
    Q_PROPERTY(Priority priority READ priority WRITE setPriority)

public:
    enum Priority { High, Low, VeryHigh, VeryLow };
    Q_ENUM(Priority)

    MyClass(QObject *parent = 0);
    ~MyClass();

    void setPriority(Priority priority) { m_priority = priority; }
    Priority priority() const { return m_priority; }

private:
    Priority m_priority;
};

Q_FLAG() マクロは、フラグとして使用される列挙型を宣言します。もう1つのマクロQ_CLASSINFO() では、クラスのメタオブジェクトに名前と値のペアを追加することができます:

class MyClass : public QObject
{
    Q_OBJECT
    Q_CLASSINFO("Author", "Oscar Peterson")
    Q_CLASSINFO("Status", "Active")

public:
    MyClass(QObject *parent = 0);
    ~MyClass();
};

moc によって生成された出力は、プログラム内の他の C++ コードと同様にコンパイルおよびリンクする必要があります。そうしないと、最終的なリンク段階でビルドが失敗します。そうしないと、最終的なリンク段階でビルドに失敗する。qmake を使用すると、これは自動的に行われる。qmake が実行されるたびに、プロジェクトのヘッダー・ファイルが解析され、Q_OBJECT マクロを含むファイルに対してmoc を呼び出す make ルールが生成されます。同様に、AUTOMOC を ON に設定すると、CMake はビルド時にヘッダー・ファイルとソース・ファイルをスキャンし、それに応じてmoc を呼び出します。

クラス宣言がファイルmyclass.h で見つかった場合、moc 出力はmoc_myclass.cpp というファイルに格納されます。このファイルは通常通りコンパイルされ、オブジェクト・ファイル(例 えばWindowsのmoc_myclass.obj )が生成される。このオブジェクトは、プログラムの最終的なビルド段階で一緒にリンクされるオブジェクト・ファイルのリストに含める必要があります。

呼び出しの Make ルールの記述moc

最も単純なテストプログラム以外では、moc の実行を自動化することをお勧めします。プログラムのmakefileにいくつかのルールを追加することで、make は必要なときにmocを実行し、mocの出力を処理することができます。

CMakeや qmakeを使えば、必要なmoc の処理をすべて行うmakefileを生成することができる。

もし自分でmakefileを作りたいのであれば、moc処理を含めるためのヒントをいくつか紹介しよう。

ヘッダファイル中のQ_OBJECT クラス宣言については、GNU makeしか使わない場合に便利なmakefileのルールがあります:

moc_%.cpp: %.h
        moc $(DEFINES) $(INCPATH) $< -o $@

移植性のある書き方をしたい場合は、次のような形で個々のルールを使うことができます:

moc_foo.cpp: foo.h
        moc $(DEFINES) $(INCPATH) $< -o $@

また、SOURCES (お好きな名前を代入してください)変数にmoc_foo.cppOBJECTS 変数にmoc_foo.o またはmoc_foo.obj を追加することも忘れてはなりません。

どちらの例も、$(DEFINES)$(INCPATH) がC++コンパイラに渡されるdefineとincludeパス・オプションに展開されると仮定しています。これらは、moc がソース・ファイルを前処理するために必要です。

C++ソース・ファイル名は.cpp とするのが好ましいが、.C.cc.CC.cxx.c++ など、他の拡張子を使用することもできる。

実装(.cpp )ファイル内のQ_OBJECT クラス宣言については、次のような makefile のルールを推奨します:

foo.o: foo.moc

foo.moc: foo.cpp
        moc $(DEFINES) $(INCPATH) -i $< -o $@

これにより、make がfoo.cpp をコンパイルする前に moc を実行することが保証されます。そうすれば

#include "foo.moc"

foo.cpp の末尾に置くことができます。ここで、そのファイルで宣言されたすべてのクラスが完全に既知になります。

コマンドラインオプション

以下はmocがサポートしているコマンドラインオプションです:

オプション説明
-D<macro>[=<def>]マクロを定義する。
-Eメタ・オブジェクト・コードを生成しない。
-f[<file>]出力に#include ステートメントを強制的に生成する。これは、拡張子がH またはh で始まるヘッダーファイルのデフォルトです。 このオプションは、標準の命名規則に従わないヘッダーファイルがある場合に便利です。<file> の部分はオプションです。
-FdirmacOS。ヘッダー・ファイルを検索するディレクトリ・リストの先頭に、フレームワーク・ディレクトリdir を追加します。これらのディレクトリーは、-I オプションで指定されたディレクトリーとインターリーブされ、左から右の順序でスキャンされます(gcc の man ページを参照)。通常は、-F /Library/Frameworks/ を使用します。
-h使用法とオプションのリストを表示します。
-i出力に#include ステートメントを生成しない。これは、1つ以上のクラス宣言を含むC++ファイルに対してmocを実行するために使用できます。その後、.cpp ファイルでメタ・オブジェクト・コードを#include
-I<dir>ヘッダーファイルのインクルードパスにdirを追加する。
-M<key=value>プラグインに追加のメタ・データを追加する。クラスがQ_PLUGIN_METADATA 、キーと値のペアがメタデータに追加されます。これは、実行時にプラグインのために解決されるJsonオブジェクトの中で終わります(QPluginLoader からアクセス可能)。この引数は通常、ビルドシステムによって解決された情報で静的プラグインにタグ付けするために使用されます。
-nw警告は生成しません。(推奨しません)。
-o<file>出力を標準出力ではなく<file> に書き出す。
-p<path>mocは生成された#include ステートメントのファイル名の前に<path>/ を付加します。
-U<macro>マクロの定義を解除する。
@<file><file> から追加のコマンドラインオプションを読み込む。 ファイルの各行は1つのオプションとして扱われる。空行は無視される。このオプションはオプション・ファイル自体ではサポートされていないことに注意してください(つまり、オプション・ファイルは他のファイルを「インクルード」できません)。
-vmoc のバージョン番号を表示する。

moc はプリプロセッサシンボルQ_MOC_RUN を定義する。で囲まれたコードは

#ifndef Q_MOC_RUN
    ...
#endif

で囲まれたコードは、moc によってスキップされます。

診断

moc は、 のクラス宣言に含まれる、多くの危険な、あるいは不正な構文について警告します。Q_OBJECT

プログラムの最終ビルド段階で、YourClass::className() が未定義であるとか、YourClass に vtable がないとかいうリンケージ・エラーが発生する場合は、何かが間違っています。たいていの場合、moc が生成した C++ コードのコンパイルや#include を忘れているか、(前者の場合は)リンク・コマンドにそのオブジェクト・ファイルをインクルードするのを忘れています。もしqmake 、makefileを更新するために再実行してみてください。これでうまくいくはずだ。

ビルド・システム

ヘッダーmocファイルのインクルード

qmake と CMake は、ヘッダー moc ファイルをインクルードする際の動作が異なります。

例として、2つのヘッダーとそれに対応するソースファイルがあるとします:a.h a.cppb.hb.cpp 。各ヘッダーにはQ_OBJECT マクロがあります:

// a.h
class A : public QObject
{
    Q_OBJECT

    public:
        // ...
};
// a.cpp
#include "a.h"

// ...

#include "moc_a.cpp"
// b.h
class B : public QObject
{
    Q_OBJECT

    public:
        // ...
};
// b.cpp
#include "b.h"

// ...

#include "moc_b.cpp"

qmakeでは、moc生成ファイル(moc_a.cpp/moc_b.cpp )をインクルードしないと、a.cpp,b.cpp,moc_a.cpp,moc_b.cpp が別々にコンパイルされます。その結果、ビルドが遅くなることがあります。mocが生成したファイルをインクルードすると、a.cppとb.cppだけがコンパイルされる必要があります。

CMakeでは、ファイルをインクルードしない場合、mocによって1つの追加ファイルが生成されます(例では、これをcmake.cpp と呼ぶことにします)。cmake.cpp は、moc_a.cppmoc_b.cpp の両方をインクルードすることになります。mocが生成したファイルをインクルードすることは、CMakeでも可能ですが、必要ではありません。

このトピックに関するCMakeのmocサポートの詳細については、ヘッダーのmocファイルをソースに含めるを参照してください。

制限事項

moc はC++のすべてを扱えるわけではありません。主な問題は、クラステンプレートが マクロを持てないことです。以下に例を示します:Q_OBJECT

class SomeTemplate<int> : public QFrame
{
    Q_OBJECT
    ...

signals:
    void mySignal(int);
};

以下の構文は不正です。これらの制限を取り除くことは、私たちにとって優先順位の高いことではありません。

多重継承では QObject が最初に必要です。

多重継承を使用する場合、moc は、最初に継承されるクラスがQObject のサブクラスであると仮定します。また、最初に継承されるクラスのみがQObject であることを確認してください。

// correct
class SomeClass : public QObject, public OtherClass
{
    ...
};

QObject による仮想継承はサポートされていません

関数ポインターはシグナル・パラメーターにもスロット・パラメーターにもなり得ません。

関数ポインタをシグナルやスロットのパラメータとして使用するほとんどの場合、継承を使用する方がよいでしょう。以下は不正な構文の例です:

class SomeClass : public QObject
{
    Q_OBJECT

public slots:
    void apply(void (*apply)(List *, void *), char *); // WRONG
};

この制限を回避するには、次のようにします:

typedef void (*ApplyFunction)(List *, void *);

class SomeClass : public QObject
{
    Q_OBJECT

public slots:
    void apply(ApplyFunction, char *);
};

関数ポインタを継承や仮想関数に置き換えた方が良い場合もあります。

EnumとTypedefはシグナルとスロットの引数を完全に修飾しなければならない

引数のシグネチャをチェックするとき、QObject::connect ()はデータ型を文字通りに比較します。そのため、AlignmentQt::Alignment は2つの異なる型として扱われます。この制限を回避するには、シグナルとスロットを宣言するとき、および接続を確立するときに、データ型を完全に修飾するようにしてください。例えば

class MyClass : public QObject
{
    Q_OBJECT

    enum Error {
        ConnectionRefused,
        RemoteHostClosed,
        UnknownError
    };

signals:
    void stateChanged(MyClass::Error error);
};

入れ子クラスはシグナルやスロットを持つことができない

以下はその例です:

class A
{
public:
    class B
    {
        Q_OBJECT

    public slots:   // WRONG
        void b();
    };
};

シグナル/スロットのリターン・タイプは参照にできない

シグナルとスロットはリターン・タイプを持つことができますが、参照を返すシグナルやスロットはvoidを返すものとして扱われます。

クラスのsignalsslots セクションには、シグナルとスロットしか記述できません。

moc クラスの セクションや セクションにシグナルやスロット以外のコンストラクトを記述しようとすると、 や のエラーメッセージが表示されます。signals slots

メタオブジェクト・システムシグナルとスロットQt のプロパティ・システムも参照してください

©2024 The Qt Company Ltd. 本書に含まれる文書の著作権は、それぞれの所有者に帰属します。 本書で提供されるドキュメントは、Free Software Foundation が発行したGNU Free Documentation License version 1.3に基づいてライセンスされています。 Qtおよびそれぞれのロゴは、フィンランドおよびその他の国におけるThe Qt Company Ltd.の 商標です。その他すべての商標は、それぞれの所有者に帰属します。