元对象编辑器moc时处理Qt的C++扩展的程序
该moc工具读取C++头文件。如果找到包含Q_OBJECT
宏的一个或者多个类声明,它将生成一个C++源文件,其中包含这些类的元对象代码。其中,信号和槽机制、运行时类型信息和动态属性系统需要元对象代码。
由MOC生成的C++源文件必须编译并链接到类的实现。
如果使用qmake创建makefile,则将包含在需要时调用moc的构建规则,因此您无需直接使用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)
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必须将产生的输出进行编译和链接,就像程序中的其他代码一样;否则,构建将在最后的链接阶段失败。如果您使用qmake,这是自动完成的。无论何时qmake运行,它都会解析项目的头文件,并生成make规则以调用moc包含Q_OBJECT
宏的那些文件
如果在文件中找到类声明myclass.h,则应将moc输出放入名为的文件中moc_myclass.cpp。然后应像往常一样编译该文件,从而生成一个目标文件,例如moc_myclass.obj在Windows上。然后,应将该对象包含在程序的最终构建阶段中链接在一起的对象文件列表中。
编写调用规则moc
对于除最简单的测试程序以外的任何程序,建议您自动运行moc。通过向程序的makefile中添加一些规则,make可以在必要时照顾运行moc并处理moc输出。
我们建议使用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)
扩展到define并包括传递给C ++编译器的路径选项。这些是moc预处理源文件所必需的。
虽然我们喜欢我们的命名C ++源文件.cpp,你可以使用任何其他扩展名,如.C,.cc,.CC,.cxx,和.c++。
对于实现()文件中的Q_OBJECT类声明.cpp,我们建议使用以下makefile规则:
foo.o: foo.moc
foo.moc: foo.cpp
moc $(DEFINES) $(INCPATH) -i $< -o $@
这保证了make将在moc编译之前运行foo.cpp。然后你可以把
#include "foo.moc"
在的末尾foo.cpp,该文件中声明的所有类都是完全已知的。
命令行选项
这是moc支持的命令行选项:
您可以明确地告诉Moc不要解析头文件的各个部分。moc定义预处理器符号Q_MOC_RUN。被以下内容包围的任何代码
#ifndef Q_MOC_RUN
...
#endif
被跳过moc。
诊断程序
moc会在Q_OBJECT类声明中警告您一些危险或非法的构造。
如果在程序的最终构建阶段遇到链接错误,例如YourClass::className()未定义或YourClass缺少vtable,则说明操作有误。大多数情况下,您忘记编译或#include由moc生成的C ++代码,或者(在前一种情况下)忘记将该对象文件包含在link命令中。如果您使用qmake,请尝试重新运行它以更新您的Makefile。这应该可以解决问题。
局限性
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 *);
};
有时用继承和虚函数替换函数指针可能会更好。
枚举和Typedef必须完全符合信号和插槽参数的要求
检查其参数的签名时,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和slot部分中只能出现Signals和slot
如果您试图在类的signals或slot部分中放置signals和slot以外的其他构造,moc会抱怨。