Using the Meta-Object Compiler (moc)
元对象编译器 (moc
) 是处理 Qt 的 C++ 扩展程序.
moc
工具读取 C++ 头文件. 如果它找到一个或多个包含 Q_OBJECT 宏的类声明, 它会生成一个包含这些类的元对象代码的 C++ 源文件. 除此之外, 信号和槽机制, 运行时类型信息和动态属性系统都需要元对象代码.
moc
生成的C++源文件必须与类的实现进行编译和链接.
如果你使用 qmake 创建 makefiles, 则会包含在需要时调用 moc 的构建规则, 因此你不需要直接使用 moc. 详见 Why Does Qt Use Moc for Signals and Slots?
Usage
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() 声明类中可在 property system 中使用的枚举类型列表.
在下面的示例中, 我们声明了一个枚举类型 Priority
的属性, 也称为 priority
, 并具有一个获取函数 priority()
和一个设置函数 setPriority()
.
class MyClass : public QObject { Q_OBJECT Q_PROPERTY(Priority priority READ priority WRITE setPriority) Q_ENUMS(Priority) public: enum Priority { High, Low, VeryHigh, VeryLow }; MyClass(QObject *parent = 0); ~MyClass(); void setPriority(Priority priority) { m_priority = priority; } Priority priority() const { return m_priority; } private: Priority m_priority; };
Q_FLAGS() 宏声明用作标志的枚举, 即. 或运算. 另一个宏 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
.
如果在文件 myclass.h
中找到类声明, 则应将 moc 输出放入名为 moc_myclass.cpp
的文件中. 然后应像平常一样编译该文件, 生成一个目标文件, 如., Windows 上的 moc_myclass.obj
. 然后, 该对象应包含在程序最终构建阶段链接在一起的对象文件列表中.
Writing Make Rules for Invoking moc
对于除了最简单的测试程序之外的任何程序, 建议你自动运行 moc
. 通过向程序的 makefile 添加一些规则, make
可以在必要时运行 moc 并处理 moc 输出.
我们建议使用 qmake 生成工具来构建 makefile 文件. 该工具生成一个 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.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
的末尾, 这个文件中声明的所有类都是完全已知的.
Command-Line Options
以下是 moc 支持的命令行选项:
Option | Description |
---|---|
-o<file> | Write output to <file> rather than to standard output. |
-f[<file>] | Force the generation of an #include statement in the output. This is the default for header files whose extension starts with H or h . This option is useful if you have header files that do not follow the standard naming conventions. The <file> part is optional. |
-i | Do not generate an #include statement in the output. This may be used to run the moc on on a C++ file containing one or more class declarations. You should then #include the meta-object code in the .cpp file. |
-nw | Do not generate any warnings. (Not recommended.) |
-p<path> | Makes the moc prepend <path>/ to the file name in the generated #include statement. |
-I<dir> | Add dir to the include path for header files. |
-E | Preprocess only; do not generate meta-object code. |
-D<macro>[=<def>] | Define macro, with optional definition. |
-U<macro> | Undefine macro. |
-M<key=value> | Append additional meta data to plugins. If a class has Q_PLUGIN_METADATA specified, the key-value pair will be added to its meta data. This will end up in the Json object that gets resolved for the plugin at run time (accessible from QPluginLoader). This argument is typically used for tagging static plugins with information resolved by the build system. |
@<file> | Read additional command-line options from <file> . Each line of the file is treated as a single option. Empty lines are ignored. Note that this option is not supported within the options file itself (i.e. an options file can't "include" another file). |
-h | Display the usage and the list of options. |
-v | Display moc 's version number. |
-Fdir | macOS. Add the framework directory dir to the head of the list of directories to be searched for header files. These directories are interleaved with those specified by -I options and are scanned in a left-to-right order (see the manpage for gcc). Normally, use -F /Library/Frameworks/ |
你可以明确告诉 moc 不要解析头文件的部分内容. moc
定义预处理器符号 Q_MOC_RUN
. 任何被包围的代码
#ifndef Q_MOC_RUN ... #endif
被 moc
跳过.
Diagnostics
moc
将警告你 Q_OBJECT 类声明中的许多危险或非法构造.
如果你在程序的最后构建阶段遇到链接错误, 指出 YourClass::className()
未定义或 YourClass
缺少 vtable, 则表明某些操作出错了. 大多数情况下, 你忘记编译或 #include
moc 生成的 C++ 代码, 或者 (在前一种情况下) 在链接命令中包含该目标文件. 如果你使用 qmake
, 请尝试重新运行它更新你的 makefile. 这应该可以解决问题.
Limitations
moc
不处理所有的 C++ 语法. 主要问题是类模板不能有 Q_OBJECT 宏. 这是一个例子:
class SomeTemplate<int> : public QFrame { Q_OBJECT ... signals: void mySignal(int); };
以下构造是非法的. 他们都有我们认为通常更好的替代方案, 因此消除这些限制对我们来说并不是首要任务.
Multiple Inheritance Requires QObject to Be First
如果你使用多重继承, moc
假定第一个继承的类是 QObject 的子类. 另外, 请确保只有第一个继承类是 QObject.
// correct class SomeClass : public QObject, public OtherClass { ... };
不支持 QObject 的虚拟继承.
Function Pointers Cannot Be Signal or Slot Parameters
在大多数情况下, 你会考虑使用函数指针作为信号或槽参数, 我们认为继承是更好的选择. 这是非法语法的示例:
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 *); };
有时用继承和虚函数代替函数指针可能会更好.
Enums and Typedefs Must Be Fully Qualified for Signal and Slot Parameters
检查参数的签名时, QObject::connect() 按照字面比较数据类型. 因此, Alignment 和 Qt::Alignment 被视为两种不同的类型. 要解决此限制, 请确保在声明信号和槽以及建立连接时完全限定数据类型. 例如:
class MyClass : public QObject { Q_OBJECT enum Error { ConnectionRefused, RemoteHostClosed, UnknownError }; signals: void stateChanged(MyClass::Error error); };
Nested Classes Cannot Have Signals or Slots
这是一个有问题的结构示例:
class A { public: class B { Q_OBJECT public slots: // WRONG void b(); }; };
Signal/Slot return types cannot be references
信号和槽可以有返回类型, 但返回引用的信号或槽将被视为返回 void.
Only Signals and Slots May Appear in the signals
and slots
Sections of a Class
如果你尝试将除信号和槽之外的其他构造放入类的信号或槽部分中, moc
输出警告.
参见 Meta-Object System, Signals and Slots, Qt's Property System.