使用元对象编译器(moc)

元对象编译器(moc),是处理 Qt 的 C++ 扩展的程序。

moc 工具读取 C++ 头文件。如果它找到一个或多个包含 Q_OBJECT 宏的类声明,它将生成一个包含那些类的元对象代码的 C++ 源文件。元对象代码对于信号和槽机制、运行时类型信息和动态属性系统等都是必需的。

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

无论是 qmake 还是 CMake,都会生成包含触发 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 的属性,也被称作 priority,它有一个获取函数 priority() 和一个设置函数 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 的规则。类似地,当将 AUTOMOC 设置为 ON 时,CMake 将在构建时扫描头文件和源文件并相应地调用 moc

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

为调用moc编写Make规则

对于任何非简单测试程序,建议您自动化运行moc。通过向您的程序makefile中添加一些规则,make可以在必要时运行moc并处理moc输出。

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

如果您想自己创建makefile,以下是一些包含moc处理的技巧。

对于在头文件中声明的Q_OBJECT类,以下是一个有用的makefile规则(如果您只使用GNU make的话)

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 $@

这保证了在编译foo.cpp之前,make将运行moc。然后您可以这样放置:

#include "foo.moc"

foo.cpp的末尾,其中该文件声明的所有类都是完全已知的。

命令行选项

以下是moc支持的命令行选项

选项描述
-D<宏>[=<定义>]定义宏,可选定义。
-E仅预处理;不生成元对象代码。
-f[<文件>]强制在输出中生成一个#include语句。这是以Hh开头扩展名的头文件的默认值。如果您有不符合标准命名约定的头文件,此选项很有用。<文件>部分是可选的。
-FdirmacOS。将框架目录dir添加到搜索头文件的目录列表的头部。这些目录与通过-I选项指定的目录交替,并按从左到右的顺序扫描(参阅gcc手册)。通常使用-F /Library/Frameworks/。
-h显示用法和选项列表。
-i不在输出中生成一个#include语句。这可能用于在包含一个或多个类声明的C++文件上运行moc。然后您应在.cpp文件中#include元对象代码。
-I<目录>将目录添加到头文件的包含路径中。
-M<键=值>向插件附加额外的元数据。如果一个类指定了Q_PLUGIN_METADATA,则会将其键值对添加到其元数据中。这将最终出现在在运行时解析的插件Json对象中(可通过QPluginLoader访问)。此参数通常用于用构建系统解析的信息标记静态插件。
-nw不生成任何警告。(不推荐。)
-o<file>将输出写入<file>而不是标准输出。
-p<path>在生成的#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缺少v表,那么一定有一个地方出错了。最常见的问题是您忘记编译或#include生成的moc C++代码,或者在第一种情况下,不要将此目标文件包含在链接命令中。如果您使用qmake,请尝试重新运行它以更新您的makefile。这应该可以解决问题。

构建系统

包含moc头文件

qmake和CMake在包含头文件moc文件方面表现不同。

为了说明这一点,假设您有两个头文件和相应的源文件:a.ha.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.cppb.cppmoc_a.cppmoc_b.cpp将被分别编译。这可能导致构建速度变慢。如果包括moc生成的文件,则只需要编译a.cppb.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();
    };
};

信号/槽的返回类型不能是引用

信号和槽可以有返回类型,但返回引用的信号或槽将被视为返回空类型。

类中 signalsslots 部分只能出现信号和槽

如果尝试将信号和槽以外的结构放入类的 signalsslots 部分,moc 将会报错。

另请参阅

© 2024 Qt公司有限公司。本文件中包含的文档贡献属于其各自所有者的版权。本文件提供的内容根据GNU自由文档许可证版本1.3的条款提供,由自由软件基金会发布。Qt及其标志是芬兰和/或其他国家/地区的Qt公司的商标。所有其他商标均为其各自所有者的财产。