元对象编译器(Meta-Object Compiler), moc, 是处理 Qt的C++扩展的程序.
Moc工具读取一个C++ 头文件.如果它找到了一个或者多个包含了Q_OBJECT宏的类声明,它会产生一个包含这些类的元对象代码的一个C++源文件. 在其他东西中, 元对象代码被要求用于信号槽机制,运行时间(run-time)类型信息,以及动态特性系统.
由moc产生的C++源文件必须被编译并与这个类的完成相连接.
如果你使用 qmake来生成你的makefile文件,编译规则会在需要的时候包含调用moc, 所以你不需要直接使用moc. 关于moc的更多背景知识,请参看 为什么 Qt 不使用模板来实现信号和槽?
用法
Moc被典型的和一个包含像下面这种情况的类声明输入文件一起使用:
class MyClass : public QObject { Q_OBJECT public: MyClass(QObject *parent = 0); ~MyClass(); signals: void mySignal(); public slots: void mySlot(); };除了上述的信号和槽外, moc也会像下面的这个例子一样实现对象属性.Q_PROPERTY()声明了一个对象属性, 而 Q_ENUMS() 声明了一个在这个属性系统中可用的枚举类型的列表.
在下面的例子中,我们声明了一个枚举类型 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); Priority priority() const; };Q_FLAGS() 宏声明了被当做标记的枚举类型, 即 OR'd together. 另一个宏, 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的make规则.
如果类的声明被发现在myclass.h文件中,moc的输出文件将会被放在一个叫做moc_myclass.cpp的文件中. 这个文件会像通常情况一样被编译,产生一个目标文件,例如,在Windows中是moc_myclass.obj .这个对象将会被包含在一个将在程序连编的最后阶段被连接在一起的对象的列表中.
书写调用moc的make规则
除了最简单的测试程序外,建议自动的运行moc. 向你程序的makefile中添加一些规则,make会注意在需要的时候运行moc并处理moc的输出.
我们推荐使用qmake这个makefile生成工具来连编你的makefile文件.这个工具会生成包含所有需要的moc处理的makefile.
如果你想自己创建你的makefile文件,这里有一些如何包含moc处理的诀窍.
对于头文件中有 Q_OBJECT类声明的, 如果你只使用GNUmake,这里有一个很有用的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++,只要你喜欢.
对于 Q_OBJECT 类声明在实现(.cpp)文件中的,我们建议一条如下的makfile规则:
foo.o: foo.moc
foo.moc: foo.cpp
moc $(DEFINES) $(INCPATH) -i $< -o $@
这保证了make会在它编译foo.cpp之前运行moc.然后你可以把
#include "foo.moc"
放在 foo.cpp的末尾, 这样所有在那个文件中声明的类都被知道了.
命令行选项
如下是被moc支持的命令行选项:
选项
描述
-o<file>
把输出写入<file> 而不是标准输出文件中.
-f[<file>]
强制#include 声明在输出中产生. 这是扩展名以H或h开始的头文件的默认情况. 如果你有不遵从标准命名约定的头文件,这是非常有用的. <file>部分是可选的.
-i
在输出文件中不产生 #include 声明.这个选项可能被用来在包含一个或者多个类声明的C++文件中运行moc. 然后你应该在.cpp文件中#include 元对象代码.
-nw
不要产生任何警告. (不推荐.)
-p<path>
让moc预先考虑<path>/作为产生的#include声明的文件名.
-I<dir>
向头文件中添加目录到include路径.( Add dir to the include path for header files.)
-E
仅预处理;不要产生元对象代码.
-D<macro>[=<def>]
定义宏, 附带可选的定义.
-U<macro>
非定义宏Undefine macro.
-h
显示用法和选项的列表.
-v
显示moc的版本号.
-Fdir
Mac OS X. 添加框架目录表到目录列表的开头用于头文件的搜索.( Add the framework directory dir to the head of the list of directories to be searched for header files.)这些目录与指定了-I选项的目录交叉存取并且以从左到右的顺序被扫描(请参考gcc的man帮助页).一般地, 使用 -F /Library/Frameworks/
你可以明确的告知moc不要解析一个头文件的某些部分.moc定义了预处理器标记Q_MOC_RUN. 任意被
#ifndef Q_MOC_RUN
...
#endif
包围的代码将会被跳过.
诊断
moc 将警告你关于一些在Q_OBJECT类声明中危险的或是非法的构造函.
如果你在你的程序连编的最后阶段得到了连接错误,说YourClass::className() 未定义或者YourClass 缺乏vtable, 某些事情已经被做错了.最常见的,是你忘记了编译或者#include moc产生的C++代码, 或者 (在前面情况下) include连接命令中的对象文件. 如果你使用 qmake, 试着再运行它来更新你的makefile.这应该采用这个技巧.
限制
moc 并不能处理所有的C++ .主要问题是类模板不能包含信号和槽. 这有个例子:
class SomeTemplate<int> : public QFrame
{
Q_OBJECT
...
signals:
void mySignal(int);
};
另一个限制是moc不扩充宏, 所以你不能做如使用一个宏来声明一个信号/槽或者使用一个宏来为一个QObject定义一个基类.
其次, 后面的构造是不合法的. 所有的这些都可以替换为我们通常认为更好的方案, 所以去除这些限制对我们来说并不是高优先级的.
多重继承需要把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);
};
类型宏不能用作信号和槽的参数
由于moc不扩展#define,带参数的类型宏在信号和槽中将无效.这里有一个非法的例子:
#ifdef ultrix
#define SIGNEDNESS(a) unsigned a
#else
#define SIGNEDNESS(a) a
#endif
class Whatever : public QObject
{
Q_OBJECT
signals:
void someSignal(SIGNEDNESS(int));
};
不带参数的宏将是有效的.
嵌套类不能含有信号或槽
这里是一个违规的例子:
class A
{
public:
class B
{
Q_OBJECT
public slots: // WRONG
void b();
};
};
信号/槽返回类型不能为引用
信号和槽可以有返回类型,但是信号或者槽返回引用将被当做返回空.
只有信号和槽可以出现在类中的signals和 slots 片段中
如果你尝试将其他的构造放到一个类的signals或slots片段中, moc将会报错(complain).
请参考 元对象系统, 信号和槽,以及 Qt的性质系统.
本文来自CSDN博客,转载请标明出处:http://blog.csdn.net/chen2qiao/archive/2009/09/12/4545939.aspx