使用元对象编译器(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输出。
您可以使用CMake或qmake生成包含所有必要处理的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.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 $@
这保证了在编译foo.cpp
之前,make将运行moc。然后您可以这样放置:
#include "foo.moc"
在foo.cpp
的末尾,其中该文件声明的所有类都是完全已知的。
命令行选项
以下是moc支持的命令行选项
选项 | 描述 |
---|---|
-D<宏>[=<定义>] | 定义宏,可选定义。 |
-E | 仅预处理;不生成元对象代码。 |
-f[<文件>] | 强制在输出中生成一个#include 语句。这是以H 或h 开头扩展名的头文件的默认值。如果您有不符合标准命名约定的头文件,此选项很有用。<文件>部分是可选的。 |
-Fdir | macOS。将框架目录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.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(); }; };
信号/槽的返回类型不能是引用
信号和槽可以有返回类型,但返回引用的信号或槽将被视为返回空类型。
类中 signals
和 slots
部分只能出现信号和槽
如果尝试将信号和槽以外的结构放入类的 signals
或 slots
部分,moc
将会报错。
另请参阅
© 2024 Qt公司有限公司。本文件中包含的文档贡献属于其各自所有者的版权。本文件提供的内容根据GNU自由文档许可证版本1.3的条款提供,由自由软件基金会发布。Qt及其标志是芬兰和/或其他国家/地区的Qt公司的商标。所有其他商标均为其各自所有者的财产。