19

是否可以告诉 Qt MOC 我想声明该类并在单个文件中实现它,而不是将它们拆分为 .h 和 .cpp 文件?

4

4 回答 4

21

如果要在 cpp 文件中声明和实现 QObject 子类,则必须手动包含 moc 文件。

例如:(文件 main.cpp)

struct SubObject : QObject
{
    Q_OBJECT
};

//...

#include "main.moc"

make qmake添加#include语句后,您必须重新运行 moc ( )。

于 2010-06-09T11:36:36.327 回答
16

TL;博士

是的,如果您只谈论您自己编写的文件(而不是那些由 moc 生成的文件)。你根本不需要做任何特别的事情。

如果您希望在编写的文件中明确包含 moc 输出,则在一种情况下您必须这样做,而在另一种情况下您可能希望这样做。假设在MyObject中声明了类,MyObject.h并且在中给出了对它的定义MyObject.cpp

  1. MyObject.moc 如果您在. _ _ _ _MyObject.cpp Q_OBJECTMyObject.cpp

  2. 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.cppmoc_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.hmoc_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生成最多两个输出:

  1. moc_foo.cppfoo.h, iff foo.h包含Q_OBJECT宏。

  2. foo.mocfoo.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"
...
于 2015-03-26T20:01:47.493 回答
3

我认为您通常可以在头文件中声明和实现该类,而无需使用任何特殊的东西,例如:

#include <QObject>

class MyClass : public QObject
{
  Q_OBJECT

  public:
     MyClass(QObject * parent)
     {
        // Constructor Content
     }

     methodExample()
     {
          // Method content
     }
};

在此之后,您将头文件添加到 pri 文件并再次执行 qmake,就是这样。您有一个继承自 qobject 并在 .h 文件中实现和声明的类。

于 2010-06-13T09:37:56.677 回答
-3

我相信这是最好的方法。这实际上是我现在构建所有对象的方式。

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

制作

于 2011-01-11T23:18:00.647 回答