目录
只有信号和槽可以出现在类signals 和 slots 的部分中。
元对象编译器 是处理 Qt C++扩展的程序。
该工具读取C++头文件。如果找到一个或多个包含 Q_OBJECT 宏的类声明,它将生成一个包含这些类的元对象代码的C++源文件。除其他事项外,信号和槽机制、运行时类型信息和动态属性系统都需要元目标代码。
生成的C++源文件必须编译并与类的实现链接。
如果您使用 qmake 创建生成文件,则将包含在需要时调用 moc 的构建规则,因此您无需直接使用 moc。
用法
moc 通常与包含如下类声明的文件一起使用:
class MyClass : public QObject
{
Q_OBJECT
public:
MyClass(QObject *parent = 0);
~MyClass();
signals:
void mySignal();
public slots:
void mySlot();
};
除了上面显示的信号和槽之外,还实现了下一个示例中的对象属性。 Q_PROPERTY() 宏声明一个对象属性,而 Q_ENUM() 声明类中的枚举类型列表可在属性 system.moc 中使用
在下面的示例中,我们声明了一个枚举类型的属性,该属性也被调用并具有一个 get 函数和一个 set 函数 .Priorityprioritypriority()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();
};
产生的输出必须经过编译和链接,就像您程序中的其他 C++ 代码一样;否则,构建将在最后的链接阶段失败。如果您使用 ,这是自动完成的。无论何时运行,它都会解析项目的头文件并生成 make 规则以调用那些包含 Q_OBJECT 宏的文件。
mocqmakeqmakemoc 如果在文件中找到类声明,则应该将 moc 输出放在名为 .moc 的文件中。然后应该像往常一样编译该文件,从而生成一个目标文件,例如在 Windows 上。然后,该对象应包含在在 program.myclass.hmoc_myclass.cppmoc_myclass.obj 的最终构建阶段链接在一起的对象文件列表中。
为调用 moc 编写 Make 规则
对于最简单的测试程序以外的任何程序,建议您自动运行。通过向程序的makefile中添加一些规则,可以在必要时运行moc并处理moc输出。mocmake我们建议使用qmake生成文件工具来构建生成文件。此工具生成一个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) 扩展到了define 并包括了传递给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将在编译之前运行moc。然后可以放 #include "foo.moc" 在foo.cpp的末尾,该文件中声明的所有类都是完全已知的。
#include "foo.moc"
命令行选项
这是moc支持的命令行选项:
选项 | 描述 |
---|---|
-o<file> | 将输出写入<file>而不是标准输出。 |
-f[<file>] | 强制在输出中生成#include语句。这是扩展名以H或h开头的头文件的默认设置。如果头文件不遵循标准命名约定,则此选项很有用。 <file>部分是可选的。 |
-i | 不要在输出中生成#include语句。这可用于在包含一个或多个类声明的C ++文件上运行moc。然后,您应该在.cpp文件中#include元对象代码。 |
-nw | 不要产生任何警告。 (不建议。) |
-p<path> | 使moc在生成的#include语句中的文件名前加<path> /。 |
-I<dir> | 将dir添加到头文件的include路径。 |
-E | 仅预处理;不生成元对象代码。 |
-D<macro>[=<def>] | 定义宏,带有可选定义。 |
-U<macro> | 取消定义宏。 |
-M<key=value> | 将额外的元数据附加到插件。如果一个类指定了 Q_PLUGIN_METADATA,键值对将被添加到它的元数据中。这将在运行时为插件解析的 Json 对象中结束(可从 QPluginLoader 访问)。此参数通常用于使用构建系统解析的信息标记静态插件。 |
@<file> | 从<file>中阅读其他命令行选项。 文件的每一行都被视为一个选项。 空行将被忽略。 请注意,选项文件本身不支持此选项(即,选项文件不能“包含”另一个文件)。 |
-h | 显示用法和选项列表。 |
-v | 显示Moc的版本号。 |
-Fdir | 将框架目录添加到要搜索头文件的目录列表的头部。这些目录与 -I 选项指定的目录交错,并按从左到右的顺序扫描(参见 gcc 的手册页)。通常,使用 -F /Library/Frameworks/dir |
您可以明确告诉Moc不要解析头文件的各个部分。 moc定义了预处理程序符号Q_MOC_RUN。 被以下内容包围的任何代码被Moc跳过。
#ifndef Q_MOC_RUN
...
#endif
诊断程序
moc 会在 Q_OBJECT 类声明中警告您一些危险或非法的构造。
如果在程序的最终构建阶段遇到链接错误,说未定义YourClass :: className()或YourClass缺少vtable,则说明操作有误。 大多数情况下,您忘记编译或#include moc生成的C ++代码,或者(在前一种情况下)在link命令中包括该对象文件。 如果使用qmake,请尝试重新运行它以更新您的Makefile。 这应该可以解决问题。
构建系统
包含moc 头 文件
qmake 和 CMake 在包含 moc 头文件方面表现不同。
为了用一个示例说明这一点,假设您有两个头文件:a.h b.h ,分别带有相应的源文件:a.cpp 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 生成的file (/), 将单独编译。这可能会导致构建速度变慢。如果包含 moc 生成的文件,则只需要编译 a.cpp 和 b.cpp,因为 moc 生成的代码包含在这些文件中。moc_a.cpp moc_b.cpp a.cpp b.cpp moc_a.cpp moc_b.cpp。
使用CMake,如果不包含文件,则moc将生成一个附加文件(为了示例起见,我们称之为)。CMake仍然允许包含moc生成的文件,但不是必需的。CMake.cpp cmake.cpp moc_a.cpp mac_b.cpp。
有关 CMake 对此主题的 moc 支持的更多信息,请参阅在源文件中包含头 moc 文件。
使用限制
moc 不能处理所有的 C++。主要问题是类模板不能有 Q_OBJECT 宏。这是一个例子:
class SomeTemplate<int> : public QFrame
{
Q_OBJECT
...
signals:
void mySignal(int);
};
以上构造是非法的。他们都有我们认为通常更好的替代方案,因此消除这些限制对我们来说不是一个高度优先事项。
多重继承要求 QObject 优先
如果使用多重继承,假设第一个继承的类是 QObject 的子类。另外,请确保只有第一个继承的类是 QObject.moc
// 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。