是否可以告诉 Qt MOC 我想声明该类并在单个文件中实现它,而不是将它们拆分为 .h 和 .cpp 文件?
4 回答
如果要在 cpp 文件中声明和实现 QObject 子类,则必须手动包含 moc 文件。
例如:(文件 main.cpp)
struct SubObject : QObject
{
Q_OBJECT
};
//...
#include "main.moc"
make qmake
添加#include
语句后,您必须重新运行 moc ( )。
TL;博士
是的,如果您只谈论您自己编写的文件(而不是那些由 moc 生成的文件)。你根本不需要做任何特别的事情。
如果您希望在编写的文件中明确包含 moc 输出,则在一种情况下您必须这样做,而在另一种情况下您可能希望这样做。假设在MyObject
中声明了类,MyObject.h
并且在中给出了对它的定义MyObject.cpp
:
MyObject.moc
如果您在. _ _ _ _MyObject.cpp
Q_OBJECT
MyObject.cpp
moc_MyObject.cpp
可以包含在任何地方,MyObject.cpp
以将项目中的翻译单元数量减半。这只是构建时优化。如果你不这样做,moc_MyObject.cpp
将被单独编译。
每次Q_OBJECT
从任何源文件或头文件中添加或删除宏,或者在此类文件中添加或删除显式包含的 moc 输出时,都必须重新运行 qmake/cmake。
要在 Qt Creator 中重新运行 qmake/cmake,只需右键单击顶层项目,然后从上下文菜单中选择运行 qmake或运行 cmake 。
简单的答案
一个基于 qmake 的示例 Qt 项目可能包含三个文件,如下所示:
# test.pro
QT += core
CONFIG += console
CONFIG -= app_bundle
TEMPLATE = app
SOURCES += main.cpp
HEADERS += myobject.h
// main.cpp
#include "myobject.h"
int main() {
MyObject obj;
obj.staticMetaObject; // refer to a value defined in moc output
return 0;
}
// myobject.h
#ifndef MYOBJECT_H
#define MYOBJECT_H
#include <QObject>
class MyObject : public QObject {
Q_OBJECT
public:
MyObject() {}
Q_SLOT void aSlot() {}
};
#endif // MYOBJECT_H
它没有多大作用,但它肯定是有效的。除了构建系统链接我们的项目的各种库之外,还有两个项目特定的翻译单元:main.cpp
和moc_myobject.cpp
.
尽管看起来整个实现MyObject
都在头文件中,但实际上并非如此。如果不是 moc 生成的Q_OBJECT
定义,宏声明了一些未定义的实现位。
moc是如何进入画面的?首次构建项目时,metabuild 工具(qmake 或 cmake)会扫描所有 C++ 输入文件中是否存在Q_OBJECT
宏。那些包含它的人会受到特殊待遇。在这个示例项目中,myobject.h
包含Q_OBJECT
并通过 moc 处理成moc_myobject.cpp
. 后者被添加到由 C++ 编译器编译的源列表中。仅在概念上,就好像您SOURCES += moc_myobject.cpp
在.pro
文件中一样。当然,您永远不应该在 .pro 文件中添加这样的一行。
现在请注意,整个实现存在MyObject
于两个文件中:myobject.h
和moc_myobject.cpp
. myobject.h
可以根据需要包含在任意数量的翻译单元中 - 因为没有违反单一定义规则的类外(独立)定义。构建系统将moc_myobject.cpp
其视为一个单独的、独立的翻译单元 - 这一切都由您负责。
因此,您的目标毫不费力地实现了:您无需做任何特别的事情来将整个实现MyObject
- 除了 moc 产生的位 - 放入头文件中。它可能会延长编译时间,但在其他方面是无害的。
违反规则的方法
确切地说,它违反了一个定义规则,因此产生了一个无效的 C++ 程序。
现在您可能会想变得“聪明”,并将 moc 输出强制包含到头文件中。qmake/cmake/qbs 将适应,并将检测到这一点,并且不会再通过编译器单独处理 moc 输出,因为您已经完成了。
因此,假设在上面的项目中,您更改myobject.h
为如下所示:
// myobject.h
#ifndef MYOBJECT_H
#define MYOBJECT_H
#include <QObject>
class MyObject : public QObject {
Q_OBJECT
public:
MyObject() {}
Q_SLOT void aSlot() {}
};
#include "moc_myobject.cpp"
#endif // MYOBJECT_H
就目前而言,该项目仍将编译,似乎实现了您的目标,即仅拥有一个定义全部的文件MyObject
- 您编写的位和 moc 生成的位,两者兼而有之。但这只是由于一个不太可能的幸福情况: 的内容moc_*.cpp
仍然只有一个翻译单元 -
假设,现在我们向项目中添加了第二个源文件:
# test.pro
QT += core
CONFIG += console
CONFIG -= app_bundle
TEMPLATE = app
SOURCES += main.cpp test.cpp
HEADERS += myobject.h
// test.cpp
#include "myobject.h"
没什么。它应该可以工作,即使它做的不多,对吧?
唉,它不会链接。现在的内容moc_myobject.cpp
是两个翻译单元的一部分。由于moc_myobject.cpp
的内部充满了独立的类成员定义,这违反了单一定义规则。该规则要求独立定义只能出现在目标中的一个翻译单元中。链接器,作为这条规则的守护者,正确地抱怨。
关于在 .cpp 文件中包含 moc 输出
正如 TL;DR 中提到的那样,在特定情况下,上述内容均不排除在源 (.cpp) 文件中明确包含 moc 输出。
给定“foo.h”和“foo.cpp”,以及由 qmake 或 cmake 管理的项目,构建系统将指导moc
生成最多两个输出:
moc_foo.cpp
从foo.h
, ifffoo.h
包含Q_OBJECT
宏。foo.moc
从foo.cpp
,当且仅当foo.cpp
包含#include "foo.moc"
。
让我们详细研究一下为什么要在 .cpp 文件中包含任何一个。
包括 xxx.moc
有时,特别是在 C++11 和 Qt 5 之前的日子里,只在一个翻译单元(源文件)中声明小型辅助 QObject 类以供本地使用会很方便。
这在编写单文件、独立的测试用例和使用 stackoverflow 的示例时也很方便。
假设您希望某人在一个文件中演示如何从事件循环中调用插槽:
// main.cpp
#include <QCoreApplication>
#include <QTextStream>
#include <cstdio>
QTextStream out(stdout);
class MyObject : public QObject {
Q_OBJECT
public:
MyObject() {}
Q_SLOT void mySlot() { out << "Hello from " << __FUNCTION__ << endl; }
};
int main(int argc, char ** argv) {
QCoreApplication app(argc, argv);
MyObject obj;
QMetaObject::invokeMethod(&obj, Qt::QueuedConnection, "mySlot");
QMetaObject::invokeMethod(&app, Qt::QueuedConnection, "quit");
return app.exec();
}
#include "main.moc"
由于MyObject
是一个仅在本地使用的小类main.moc
,因此将其定义放入单独的头文件中没有多大意义。该#include "main.moc"
行将被 qmake/cmake 注意到,并将main.cpp
通过 moc 输入,从而生成main.moc
. 由于main.moc
定义了 的成员MyObject
,因此它必须包含在MyObject
声明的某个地方。由于声明在 内main.cpp
,因此您不能main.moc
成为单独的翻译单元:由于未声明,它不会编译MyObject
。唯一声明它的地方是在 内main.cpp
,接近最后的某个地方。这就是为什么总是foo.moc
在末尾包含foo.cpp
.
一位精明的读者现在问:如何moc_foo.cpp
获得它定义的成员的类的声明?很简单:它显式地包含了它生成的头文件(这里:)foo.h
。当然foo.moc
不能这样做,因为它会通过在foo.cpp
.
包括 moc_xxx.cpp
在特别大的 Qt 项目中,每个类平均可能有两个文件和两个翻译单元:
MyObject.h
并且MyObject.cpp
是您编写的文件。MyObject.cpp
是翻译moc_MyObject.cpp
单位。
通过显式包含以下内容可以将翻译单元的数量moc_MyObject.cpp
减半MyObject.cpp
:
// MyObject.cpp
#include "MyObject.h"
#include "moc_MyObject.cpp"
...
我认为您通常可以在头文件中声明和实现该类,而无需使用任何特殊的东西,例如:
#include <QObject>
class MyClass : public QObject
{
Q_OBJECT
public:
MyClass(QObject * parent)
{
// Constructor Content
}
methodExample()
{
// Method content
}
};
在此之后,您将头文件添加到 pri 文件并再次执行 qmake,就是这样。您有一个继承自 qobject 并在 .h 文件中实现和声明的类。
我相信这是最好的方法。这实际上是我现在构建所有对象的方式。
Qt 4.8.7
Works.pro:
SOURCES += \
main.cpp
HEADERS += \
Window.h \
MyWidget.h
主文件
#include <QtGui>
#include "Window.h"
int main(int argc, char *argv[])
{
QApplication app(argc,argv);
Window window;
window.show();
return app.exec();
}
窗口.h
#ifndef WINDOW_H
#define WINDOW_H
#include <QtGui>
#include "MyWidget.h"
class Window : public QWidget
{
Q_OBJECT
private:
MyWidget *whatever;
public:
Window()
{
QHBoxLayout *layout = new QHBoxLayout;
setLayout(layout);
whatever = new MyWidget("Screw You");
layout->addWidget(whatever);
}
};
#include "moc_Window.cpp"
#endif // WINDOW_H
我的小部件.h
#ifndef MYWIDGET_H
#define MYWIDGET_H
#include <QtGui>
class MyWidget : public QLabel
{
Q_OBJECT
public:
MyWidget(QString text) : QLabel(text)
{
// Whatever
}
};
#include "moc_MyWidget.cpp"
#endif // MYWIDGET_H
构建... qmake Works.pro
制作