Qt moc с реализациями внутри файлов заголовков?
Вопрос
Можно ли сообщить Qt MOC, что я хотел бы объявить класс и реализовать его в одном файле, а не разбивать их на файлы .h и .cpp?
Решение 4
Я считаю, что это лучше. Это на самом деле, как я строю все мои объекты сейчас.
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();
}
Окно
#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
сделать
Другие советы
Если вы хотите объявить и внедрить подкласс Qobject в файле CPP, вы должны вручную включить файл MOC.
Например: (файл main.cpp)
struct SubObject : QObject
{
Q_OBJECT
};
//...
#include "main.moc"
Вы должны Rerun Moc (make qmake
) после добавления #include
утверждение.
ТЛ;ДР
Да, если вы говорите только о файлах, которые пишете сами (а не о тех, которые генерируются 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.
Чтобы повторно запустить 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.
Какое место в этой картине занимает 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
для генерации до двух выходных данных:
moc_foo.cpp
отfoo.h
, если толькоfoo.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 и реализован и объявлен INT HE .h Файл.