Подтвердить что ты не робот

Qt moc с реализациями внутри файлов заголовков?

Можно ли сообщить Qt MOC, что я хотел бы объявить класс и реализовать его в одном файле, а не разбивать их на файлы .h и .cpp?

4b9b3361

Ответ 1

Я считаю, что это лучший способ. На самом деле, как я строю все мои объекты сейчас.

Qt 4.8.7

Works.pro:

SOURCES += \
    main.cpp

HEADERS += \
    Window.h \
    MyWidget.h

main.cpp

#include <QtGui>
#include "Window.h"

int main(int argc, char *argv[])
{
    QApplication app(argc,argv);

    Window window;
    window.show();
    return app.exec();
}

window.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

MyWidget.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

Build... qmake Works.pro

сделать

Ответ 2

Если вы хотите объявить и реализовать подкласс QObject в файле cpp, вам необходимо вручную включить файл moc.

Например: (файл main.cpp)

struct SubObject : QObject
{
    Q_OBJECT
};

//...

#include "main.moc"

Вам нужно перезапустить moc (make qmake) после добавления оператора #include.

Ответ 3

TL; DR

Да, если вы говорите только о файлах, которые вы пишете сами (в отличие от тех, которые генерируются moc). Вам не нужно ничего делать вообще.

Если вы хотите включить вывод moc явно в файлы, которые вы пишете, есть один случай, когда вы должны сделать это, и один случай, когда вы можете пожелать сделать это. Предположим, что класс MyObject объявлен в MyObject.h, и ваше определение его дано в MyObject.cpp:

  • MyObject.moc должен быть включен в конце MyObject.cpp iff. Вы объявляете какие-либо классы Q_OBJECT внутри MyObject.cpp.

  • moc_MyObject.cpp может быть включен в любом месте MyObject.cpp, чтобы сократить количество единиц перевода в вашем проекте вдвое. Это только оптимизация времени построения. Если вы этого не сделаете, moc_MyObject.cpp будет скомпилирован отдельно.

Каждый раз, когда вы добавляете или удаляете макрос Q_OBJECT из любого исходного или заголовочного файла или добавляете или удаляете явные включения вывода moc в такие файлы, вы должны повторно запустить qmake/cmake.

Чтобы повторно запустить qmake/cmake в Qt Creator, просто щелкните правой кнопкой мыши проект toplevel и выберите Run qmake или Run cmake из контекстного меню.

Простой ответ

Пример qt-проекта, основанного на qmake, может состоять из трех файлов:

# 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 находится в файле заголовка, это действительно так. Макрос Q_OBJECT объявляет некоторые биты реализации, которые были бы undefined, если бы это не для созданных moc определений.

Как moc входит в изображение? Когда проект сначала строится, инструмент метауправления - либо qmake, либо cmake - просматривает все входные файлы С++ для наличия макроса Q_OBJECT. Те, которые его содержат, получают особое лечение. В этом примере проект MyObject.h содержит Q_OBJECT и обрабатывается через moc в moc_MyObject.cpp. Последний добавляется в список источников, которые компилируются компилятором С++. Это только концептуально, как будто у вас SOURCES += moc_myobject.cpp в файле .pro. Конечно, вы никогда не должны добавлять такую ​​строку в файл .pro.

Теперь обратите внимание, что вся реализация MyObject лежит в двух файлах: MyObject.h и moc_MyObject.cpp. MyObject.h может быть включено в столько единиц перевода, сколько вы пожелаете, потому что не существует внеклассных (автономных) определений, которые нарушали бы одно правило определения. Система сборки рассматривает moc_MyObject.cpp как единую отдельную единицу перевода - это все позаботится о вас.

Таким образом, ваша цель достигается без каких-либо усилий: вам нечего делать, чтобы поставить всю реализацию MyObject - сохранить для битов, которые производит moc - в файл заголовка. Он может продлить время компиляции, но в остальном безвреден.

Подход, нарушающий правила

Он нарушает одно правило определения, а точнее, и дает недопустимую С++-программу.

Теперь вы можете подумать, чтобы получить "умный" и принудительно включить вывод 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 insides полны автономных определений членов класса, это нарушает одно правило определения. Правило предусматривает, что автономные определения могут отображаться только в одной единицы перевода внутри цели. Линкер, будучи хранителем этого правила, справедливо жалуется.

Вкл. Включение вывода moc в файле .cpp

Как указано в TL; DR, ни одно из вышеизложенных исключает явное включение вывода moc в файлы источника (.cpp) в определенных обстоятельствах.

Учитывая "foo.h" и "foo.cpp" и проект, управляемый qmake или cmake, система сборки направит moc на создание до двух выходов:

  • moc_foo.cpp из foo.h, iff foo.h содержит макрос Q_OBJECT.

  • foo.moc from foo.cpp, iff foo.cpp содержит #include "foo.moc".

Рассмотрим подробно, почему вы хотите включить либо в файл .cpp.

В том числе xxx.moc

Иногда, особенно в дни, предшествующие С++ 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"
...

Ответ 4

Я думаю, что вы можете обычно объявлять и реализовывать класс в файле заголовка, не используя ничего особенного, например:

#include <QObject>

class MyClass : public QObject
{
  Q_OBJECT

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

     methodExample()
     {
          // Method content
     }
};

После этого вы добавляете файл заголовка в файл pri и снова выполняете qmake и его. У вас есть класс, который наследует от qobject и реализуется и объявляется в файле .h.