使用Meta-Object Compiler (moc)

Meta-Object Compiler,moc, 是处理Qt 的 C++ 扩展的程序。

moc 工具读取 C++ 头文件。如果发现一个或多个类声明包含Q_OBJECT 宏,它就会生成一个 C++ 源文件,其中包含这些类的元对象代码。除其他外,信号和插槽机制、运行时类型信息和动态属性系统都需要元对象代码。

moc 生成的 C++ 源文件必须与类的实现进行编译和链接。

qmakeCMake都会生成带有编译规则的 makefile,这些规则会相应地调用moc ,所以你不需要直接使用mocqmake 默认会添加这些编译规则,而对于 CMake,你可以使用AUTOMOC属性来自动处理moc 。有关moc 的更多背景信息,请参阅为什么 Qt XML 使用 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 ,该属性也被称为priority ,它有一个 get 函数priority() 和一个 set 函数setPriority()

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() 宏声明的枚举将作为标志使用,即 OR 在一起。另一个宏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 时,它会解析项目的头文件,并生成 make 规则,以调用包含Q_OBJECT 宏的文件的moc 。同样,当把AUTOMOC设为ON 时,CMake 会在联编时扫描头文件和源文件,并相应地调用moc

如果在myclass.h 文件中找到类声明,则 moc 输出应放入名为moc_myclass.cpp 的文件中。然后,应像往常一样对该文件进行编译,生成一个对象文件,如 Windows 下的moc_myclass.obj 。然后,该对象文件应包含在对象文件列表中,这些文件将在程序的最终构建阶段链接在一起。

编写调用规则moc

除了最简单的测试程序外,建议您自动运行moc 。通过在程序的 makefile 中添加一些规则,make 可以在必要时运行 moc 并处理 moc 输出。

您可以使用CMakeqmake生成 makefile,其中包含所有必要的moc 处理。

如果你想自己创建 makefile,这里有一些关于如何包含 moc 处理的提示。

对于Q_OBJECT 头文件中的类声明,如果你只使用 GNU make,这里有一个有用的 makefile 规则:

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

如果你想可移植地编写,可以使用以下形式的单独规则:

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

您还必须记住将moc_foo.cpp 添加到您的SOURCES (用您喜欢的名字代替)变量中,并将moc_foo.omoc_foo.obj 添加到您的OBJECTS 变量中。

这两个示例都假定$(DEFINES)$(INCPATH) 扩展为定义和包含路径选项,并传递给 C++ 编译器。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 语句。对于扩展名以Hh 开头的头文件,这是默认设置。如果你的头文件不遵循标准命名约定,该选项将非常有用。<file> 部分是可选的。
-FdirmacOS。将框架目录dir 添加到要搜索头文件的目录列表的头部。这些目录与 -I 选项指定的目录交错排列,并按从左到右的顺序扫描(参见 gcc 的手册)。通常使用 -F /Library/Frameworks/
-h显示使用情况和选项列表。
-i在输出中不生成#include 语句。这可用于在包含一个或多个类声明的 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> 读取附加命令行选项。文件的每一行都被视为一个选项。空行将被忽略。请注意,选项文件本身不支持该选项(即选项文件不能 "包含 "另一个文件)。
-v显示moc 的版本号。

您可以明确告诉 moc 不要解析头文件的某些部分。moc 定义了预处理器符号Q_MOC_RUN 。被

#ifndef Q_MOC_RUN
    ...
#endif

包围的代码都会被moc 跳过。

诊断

moc 会对 类声明中的一些危险或非法构造提出警告。Q_OBJECT

如果在程序的最后编译阶段出现链接错误,提示YourClass::className() 未定义或YourClass 缺少 vtable,则说明程序中出现了错误。最常见的情况是,你忘记了编译或#include moc 生成的 C++ 代码,或者(在前一种情况下)在链接命令中包含了该对象文件。如果您使用qmake ,请尝试重新运行它来更新您的 makefile。这应该能起到作用。

构建系统

包含头文件 moc

qmake 和 CMake 在包含头文件 moc 时的表现不同。

举个例子来说明这一点,假设你有两个头文件和相应的源文件:a.h,a.cpp,b.h, 和b.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,因为这些文件中包含了 moc 生成的代码。

在 CMake 中,如果不包含这些文件,moc 会生成一个额外的文件(为方便起见,我们称其为cmake.cpp )。cmake.cpp 将包含moc_a.cppmoc_b.cpp 。CMake 仍允许包含 moc 生成的文件,但这并不是必须的。

有关 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 *);
};

有时,用继承和虚拟函数代替函数指针可能更好。

枚举和类型定义必须对信号和槽参数进行完全限定

在检查参数签名时,QObject::connect() 按字面意思比较数据类型。因此,AlignmentQt::Alignment 被视为两种不同的类型。要绕过这一限制,请确保在声明信号和槽以及建立连接时完全限定数据类型。例如

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 的属性系统

© 2025 The Qt Company Ltd. Documentation contributions included herein are the copyrights of their respective owners. The documentation provided herein is licensed under the terms of the GNU Free Documentation License version 1.3 as published by the Free Software Foundation. Qt and respective logos are trademarks of The Qt Company Ltd. in Finland and/or other countries worldwide. All other trademarks are property of their respective owners.