本页内容

使用Meta-Object Compiler (moc)

Meta-Object Compiler (moc )是一个处理Qt C++ 扩展的程序。

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

由moc 生成的 C++ 源文件必须经过编译,并与该类的实现代码进行链接。

qmake和CMake都会生成包含构建规则的 makefile,这些规则会相应地调用moc ,因此您无需直接使用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 的属性,该属性同样命名为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() 用于声明将用作标志(即通过“或”运算组合)的枚举。另一个宏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 $@

此外,请务必将moc_foo.cpp 添加到SOURCES (替换为你喜欢的名称)变量中,并将moc_foo.o 或moc_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 语句。对于扩展名以H 或h 开头的头文件,这是默认行为。如果您的头文件未遵循标准命名约定,此选项非常有用。<file> 部分是可选的。
-FdirmacOS。将框架目录dir 添加到用于搜索头文件的目录列表开头。这些目录与 -I 选项指定的目录交错排列,并按从左到右的顺序进行扫描(参见 gcc 的手册页)。通常,请使用 -F /Library/Frameworks/
-h显示用法及选项列表。
-i不在输出中生成#include 语句。这可用于对包含一个或多个类声明的 C++ 文件运行 moc。随后,应将元对象代码通过#include 转换为.cpp 文件。
-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 缺少虚函数表,则说明某处操作有误。 最常见的情况是,你忘记编译或使用#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.cpp 和moc_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() 会进行字面上的数据类型比较。因此,Alignment 和Qt::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。

只有信号和槽可以出现在类的 `signals ` 和 `slots ` 部分

moc 若尝试在类的signals 或slots 部分中放置信号和槽以外的其他结构,编译器会报错。

另请参阅 “元对象系统”、“信号与槽”以及“Qt 的属性系统”。

© 2026 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.