Qt moc с реализациями внутри заголовочных файлов? - PullRequest
16 голосов
/ 09 июня 2010

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

Ответы [ 4 ]

17 голосов
/ 09 июня 2010

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

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

struct SubObject : QObject
{
    Q_OBJECT
};

//...

#include "main.moc"

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

11 голосов
/ 26 марта 2015

TL; DR

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

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

  1. MyObject.moc должно быть включено в конце из MyObject.cpp тогда и только тогда вы объявляете любые Q_OBJECT классы в пределах MyObject.cpp.

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

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

Чтобы перезапустить qmake / cmake в Qt Creator, просто щелкните правой кнопкой мыши проект верхнего уровня и выберите Запустить qmake или Запустить 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 объявляет некоторые биты реализации, которые были бы неопределенными, если бы не определения, сгенерированные moc.

Как Мок входит в картинку? Когда проект создается впервые, инструмент мета-сборки - 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 полны автономных определений членов класса, это нарушает правило одного определения . Правило требует, чтобы автономные определения могли появляться только в одной единице перевода в пределах цели. Компоновщик, являясь хранителем этого правила, справедливо жалуется.

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

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

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

  1. moc_foo.cpp из foo.h, в том случае, если foo.h содержит Q_OBJECT макрос.

  2. 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"
...
3 голосов
/ 13 июня 2010

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

#include <QObject>

class MyClass : public QObject
{
  Q_OBJECT

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

     methodExample()
     {
          // Method content
     }
};

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

0 голосов
/ 12 января 2011

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

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

Строить ... qmake Works.pro

сделать

...