在 Qt 编译环境下,自定义类继承 Qt 类时,程序报错 error: undefined reference to `vtable for XXXX'。这个错误看起来是虚函数表无法引用到,也就是继承 Qt 类失败了。
解决方案:删除编译文件夹,重新 rebuild 工程。
出于对 Q_OBJECT 宏的好奇,大致读了下 Qt 帮助文档对 Q_OBJECT 宏的解释,具体如下(英语不太好,谅解):
Using the Meta-Object Compiler(moc)
元对象编译器(moc)它是用来处理 Qt's C++ 扩展程序。
moc 工具读取一个 C++ 头文件,如果它一个或多个在类声明中包含 Q_OBJECT 宏,它会为这些类产生一个包含元对象代码 C++ 源文件。除了其他方面,对于信号槽机制、运行时类型信息、动态属性系统,元对象代码是必须的。
由 moc 生成的 C++ 源文件必须被编译并与实现类相链接。
如果你使用 qmake 去创建你的 makefiles,在需要的时候编译规则将包括调用 moc,因此你不需要直接使用 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_ENUMS() 宏声明一系列枚举类型在类里面,它对属性系统里面是有用的。
在下面的例子中,我们声明了一个枚举类型的属性 Priority ,另外调用 priority 有一个 get 函数 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() ,允许你附加额外的 name/value 对到类的元对象:
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 运行时,它会解析项目头文件并生成规则去调用 moc 为这些包含 Q_OBJECT 宏的文件。
如果在文件 myclass.h 有发现类声明,moc 的输出应该会放在一个叫 moc_myclass.cpp 里。然后通常这个文件会被编译,产生的结果在一个 object 文件内,譬如,在 Window 系统上是 moc_myclass.obj。然后这个 object 文件会被包括到 object 列表文件内,在程序最终的编译阶段一起链接。
Writing Make Rules for Invoking moc
除了最简单的测试程序,推荐你自动运行 moc 。通过添加一些规则到你程序的 makefile 里, make 可以在必要的时候处理运行中的 moc 并处理 moc 的输出。
针对在编译你的 makefiles 时,我们推荐你使用 qmake makefile 生成工具。这个工具生成了一个 makefile 以至于做了所有必要的 moc 处理。
如果你想你自己创建你的 makefiles,这里有些小窍门关于如何使用 moc 处理。
对于在头文件中类声明的 Q_OBJECT 宏,如果你仅仅只是使用 GNU 制作,这里有一个有用的 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) 文件,我们建议一个 makefile 规则应该像这样:
foo.o: foo.moc
foo.moc: foo.cpp
moc $(DEFINES) $(INCPATH) -i $< -o $@
这可以保证在编译 foo.cpp 之前运行 moc,然后你可以输入:
#include "foo.moc"
在 foo.cpp 的结尾,在文件中声明的所有类会全部知道。
Command-Line Options
这里的命令行选项被 moc 所支持:
-o<file>: 写输出至<file>,而不是标准输出。
-f[<file>]: 强制生成一个 #include 语句在输出里。对于头文件的扩展默认是 H 或者 h。这个选项是有用的,如果你有头文件没有随标准命名转换。<file> 部分是可选的。
-i: 不要在输出中生成一个 #include 语句,这可能用于在包含一个或多个类声明的 C++ 文件上运行 moc。 然后你应该 #include 元对象代码在你的 .cpp 文件里。
-nw: 不要生成任何的警告(不推荐)。
-p<path>: 在生成的 #include 语句中,使 moc 预先设置 <path>/ 到文件名。
-I<dir>: 为头文件添加文件夹到包含路径。
-D<macro>[=<def>]: 定义宏,带有可选选项。
-U<macro>: 不要定义宏。
-M<key=value>: 附件额外的 meta 数据到插件程序,如果一个类有指定 Q_PLUGIN_METADATA,键值对将会添加到它的 meta 数据。插件运行时会在 json 对象得到解析后将会结束(从 QPluginLoader 访问)。这个参数典型的用法是标记静态插件和信息解析被编译系统。
@<file>: 读取额外的命令行选项从<file>,文件的每一行都会作为单一选项对待。空行会被忽略。注意,在选项文件它自己里面这个选项不被支持(即,一个选项文件不能“包含”另外一个文件)。
-h: 显示用法和选项列表。
-v: 显示 moc 版本号。
-Fdir: OS X。添加框架目录 dir 到要搜索的头文件的头目录列表。这些与那些通过 -I 选项指定目录交错,并且按从左到右的顺序扫描(看 gcc )。正常情况下,使用 -F/ Library/ Frameworks/
你可以明确的告诉编译器不要解析一个头文件 .moc 定义的预处理符号 Q_MOC_RUN 部分。所有的被包围的代码
#ifndef Q_MOC_RUN
...
#endif
会被 moc 跳过。
Diagnostics
moc 将会警告你关于一些危险或非法的构造在 Q_OBJECT 类型声明。
如果你的程序在最终的编译阶段,你得到了一些链接错误,说 YouClass::className() 没有定义或 YouClass 缺少一个 vtable,肯定是有些事做错了。通常来说,你有忘记去编译或者 #include moc 生成的 C++ 代码,或者(在前一种情况)包含对象文件在链接命令。如果你使用 qmake ,试着运行它去更新你的 makefile。这一点应该要做到。
Limitations
moc 不会处理所有的 C++,主要问题是类模板不能有信号和槽。这里有一个例子:
class SomeTemplate<int> : public QFrame
{
Q_OBJECT
...
signals:
void mySignal(int);
};
其次,下面的构造是非法的。它们都有替代方案我们认为通常更好,因此,消除这些限制对我们来说没有很高的优先级。
Mutiple 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
如果你试着放置另外一个结构在类的 signals 和 slots 节点内,而不是信号和槽里, moc 将会报错。
See also Meta-Object System, Signals and Slots, and Qt's Property System.