Использование метаобъектного компилятора (moc)
Метаобъектный компилятор, moc, — это программа, обрабатывающая расширения Qt на C++.
Инструмент moc считывает заголовочный файл C++. Если он находит одно или несколько объявлений классов, содержащих макрос Q_OBJECT, он генерирует файл исходного кода C++ с метаобъектным кодом для этих классов. Метаобъектный код, среди прочего, необходим для механизма сигналов и слотов, информации о типе во время выполнения и динамической системы свойств.
Файл исходного кода C++, сгенерированный moc, необходимо скомпилировать и связать с реализацией класса.
Если для создания файлов make вы используете qmake, будут включены правила построения, вызывающие moc при необходимости, поэтому вам не придётся использовать moc напрямую. Более подробную информацию о moc, см. в статье Почему Qt использует Moc для сигналов и слотов?
Использование
moc обычно используется с входным файлом, содержащим объявления классов, например:
class MyClass : public QObject
{
Q_OBJECT
public:
MyClass(QObject *parent = 0);
~MyClass();
signals:
void mySignal();
public slots:
void mySlot();
}; В дополнение к сигналы и слоты, показанным выше, moc также реализует свойства объектов, как в следующем примере. Макрос Q_PROPERTY() объявляет свойство объекта, а Q_ENUM() объявляет список типов перечислений внутри класса для использования в системе свойств.
В следующем примере мы объявляем свойство типа перечисления Priority, которое также называется priority и имеет функцию получения priority() и функцию установки setPriority().
class MyClass : public QObject
{
Q_OBJECT
Q_PROPERTY(Priority priority READ priority WRITE setPriority)
Q_ENUMS(Priority)
public:
enum Priority { High, Low, VeryHigh, VeryLow };
MyClass(QObject *parent = 0);
~MyClass();
void setPriority(Priority priority) { m_priority = priority; }
Priority priority() const { return m_priority; }
private:
Priority m_priority;
}; Макрос Q_FLAGS() объявляет перечисления, которые должны использоваться как флаги, т. е. комбинироваться с помощью оператора OR. Другой макрос, Q_CLASSINFO(), позволяет добавлять дополнительные пары имя/значение к метаобъекту класса:
class MyClass : public QObject
{
Q_OBJECT
Q_CLASSINFO("Author", "Oscar Peterson")
Q_CLASSINFO("Status", "Active")
public:
MyClass(QObject *parent = 0);
~MyClass();
}; Выходной код, сгенерированный moc, необходимо скомпилировать и связать, как и другой код C++ в вашей программе; в противном случае сборка завершится ошибкой на последнем этапе компоновки. Если вы используете qmake, это выполняется автоматически. Всякий раз, когда выполняется qmake, он анализирует заголовочные файлы проекта и генерирует правила make для вызова moc для файлов, содержащих макрос Q_OBJECT.
Если объявление класса находится в файле myclass.h, выходной код moc должен помещаться в файл с именем moc_myclass.cpp. Этот файл должен компилироваться как обычно, результатом будет файл объекта, например, moc_myclass.obj в Windows. Затем этот объект следует включить в список файлов объектов, которые объединяются на последнем этапе сборки программы.
Создание правил make для вызова moc
Для программ, отличных от самых простых тестовых программ, рекомендуется автоматизировать запуск moc. Добавив несколько правил в файл make вашей программы, make может позаботиться о запуске moc при необходимости и обработке выходных данных moc.
Мы рекомендуем использовать инструмент генерации файлов make qmake для создания файлов make. Этот инструмент генерирует файл make, который выполняет всю необходимую обработку moc.
Если вы хотите создать свои файлы make самостоятельно, вот несколько советов о том, как включить обработку moc.
Для объявлений класса Q_OBJECT в заголовочных файлах, вот полезное правило файла make, если вы используете только GNU make:
moc_%.cpp: %.h
moc $(DEFINES) $(INCPATH) $< -o $@ Если вы хотите написать переносимый код, вы можете использовать отдельные правила следующего формата:
moc_foo.cpp: foo.h
moc $(DEFINES) $(INCPATH) $< -o $@ Также необходимо добавить moc_foo.cpp в переменную SOURCES (подставьте своё любимое имя) и moc_foo.o или moc_foo.obj в переменную OBJECTS.
Оба примера предполагают, что $(DEFINES) и $(INCPATH) расширяются до параметров определения и включения пути, которые передаются компилятору C++. Они необходимы moc для предварительной обработки исходных файлов.
Хотя мы предпочитаем называть наши файлы исходного кода C++ .cpp, вы можете использовать любое другое расширение, например, .C, .cc, .CC, .cxx, и .c++, если хотите.
Для объявлений класса Q_OBJECT в файлах реализации (.cpp) мы рекомендуем правило файла make, подобное этому:
foo.o: foo.moc
foo.moc: foo.cpp
moc $(DEFINES) $(INCPATH) -i $< -o $@ Это гарантирует, что make запустит moc перед компиляцией foo.cpp. Затем вы можете добавить
#include "foo.moc"
в конец foo.cpp, где все классы, объявленные в этом файле, полностью известны.
Параметры командной строки
Вот поддерживаемые параметры командной строки для moc:
| Параметр | Описание |
|---|---|
-o<file> |
Записать вывод в <file> вместо стандартного вывода. |
-f[<file>] |
Вынудить генерацию инструкции #include в выходном коде. Это значение по умолчанию для заголовочных файлов, расширение которых начинается с H или h. Этот параметр полезен, если у вас есть заголовочные файлы, которые не соответствуют стандартным соглашениям об именовании. Часть <file> является необязательной. |
-i |
Не генерировать инструкцию #include в выходном коде. Это может быть полезно, если вы запускаете moc на файле C++, содержащем одно или несколько объявлений класса. Затем вам нужно будет вручную #include метаобъектный код в файле .cpp . |
-nw |
Не генерировать никаких предупреждений. (Не рекомендуется.) |
-p<path> |
Заставляет moc добавлять <path>/ к имени файла в сгенерированной инструкции #include. |
-I<dir> |
Добавить dir в путь включения для заголовочных файлов. |
-E |
Только предварительная обработка; не генерировать метаобъектный код. |
-D<macro>[=<def>] |
Определить макрос с необязательным определением. |
-U<macro> |
Отменить определение макроса. |
-M<key=value> |
Добавить дополнительные метаданные к плагинам. Если класс имеет Q_PLUGIN_METADATA, пара ключ-значение будет добавлена к его метаданным. Это приведет к объекту Json, который будет разрешен во время выполнения для плагина (доступного из QPluginLoader). Этот аргумент обычно используется для маркировки статических плагинов информацией, разрешенной системой сборки. |
@<file> |
Считать дополнительные параметры командной строки из <file>. Каждая строка файла обрабатывается как отдельный параметр. Пустые строки игнорируются. Обратите внимание, что этот параметр не поддерживается в файле параметров (т. е. файл параметров не может "включать" другой файл). |
-h |
Отобразить справку и список параметров. |
-v |
Отобразить номер версии moc. |
-Fdir |
macOS. Добавить каталог фреймворка dir в начало списка каталогов, которые нужно искать для заголовочных файлов. Эти каталоги чередуются с теми, которые указаны параметрами -I, и просматриваются слева направо (см. руководство по gcc). Обычно используйте -F /Library/Frameworks/ |
Вы можете явно указать moc не анализировать части заголовочного файла. moc определяет препроцессорную переменную Q_MOC_RUN. Любой код, заключённый в
#ifndef Q_MOC_RUN
...
#endif пропускается moc.
Диагностика
moc предупредит вас о ряде опасных или некорректных конструкций в объявлениях класса Q_OBJECT.
Если при окончательной сборке вашей программы вы получите ошибки компоновки, говорящие о том, что YourClass::className() не определено или что YourClass отсутствует vtable, что-то сделано неправильно. Чаще всего вы забыли скомпилировать или #include сгенерированный moc код C++, или (в первом случае) включить этот файл объекта в команду компоновки. Если вы используете qmake, попробуйте перезапустить его, чтобы обновить ваш файл make. Это должно помочь.
Системы сборки
Включение заголовочных файлов moc
qmake и CMake ведут себя по-разному в отношении включения заголовочных файлов moc.
Для иллюстрации этого на примере предположим, что у вас есть два заголовочных файла с соответствующими файлами исходного кода: a.h, a.cpp, b.h, и b.cpp. Каждый заголовочный файл имеет макрос Q_OBJECT:
// a.h
class A : public QObject
{
Q_OBJECT
public:
// ...
}; // a.cpp #include "a.h" // ... #include "moc_a.cpp"
// b.h
class B : public QObject
{
Q_OBJECT
public:
// ...
}; // b.cpp #include "b.h" // ... #include "moc_b.cpp"
С qmake, если вы не включаете сгенерированный файл moc (moc_a.cpp/moc_b.cpp), a.cpp, b.cpp, moc_a.cpp, и moc_b.cpp будут компилироваться отдельно. Это может привести к замедлению сборки. Если вы включаете сгенерированные файлы moc, компилироваться нужно будет только a.cpp и b.cpp, так как сгенерированный код moc включается в эти файлы.
С CMake, если вы не включаете файлы, moc сгенерирует один дополнительный файл (назовем его cmake.cpp для примера). cmake.cpp будет включать как moc_a.cpp, так и moc_b.cpp. Включение сгенерированного файла moc всё ещё разрешено с CMake, но оно не является обязательным.
Дополнительную информацию о поддержке moc в CMake по этой теме, см. в Включение заголовочных файлов moc в исходные файлы.
Ограничения
moc не обрабатывает весь C++. Основная проблема заключается в том, что классы шаблонов не могут иметь макрос Q_OBJECT. Вот пример:
class SomeTemplate<int> : public QFrame
{
Q_OBJECT
...
signals:
void mySignal(int);
}; Следующие конструкции являются недопустимыми. Все они имеют альтернативы, которые, по нашему мнению, обычно лучше, поэтому удаление этих ограничений не является для нас приоритетным заданием.
Для множественного наследования QObject должен быть первым
Если вы используете множественное наследование, moc предполагает, что первый унаследованный класс является подклассом QObject. Также убедитесь, что только первый унаследованный класс является QObject.
// correct
class SomeClass : public QObject, public OtherClass
{
...
}; Виртуальное наследование с QObject не поддерживается.
Функциональные указатели не могут быть параметрами сигналов или слотов
В большинстве случаев, когда вы рассматриваете использование функциональных указателей в качестве параметров сигналов или слотов, мы считаем, что наследование — это лучшая альтернатива. Вот пример незаконного синтаксиса:
class SomeClass : public QObject
{
Q_OBJECT
public slots:
void apply(void (*apply)(List *, void *), char *); // WRONG
}; Вы можете обойти это ограничение следующим образом:
typedef void (*ApplyFunction)(List *, void *);
class SomeClass : public QObject
{
Q_OBJECT
public slots:
void apply(ApplyFunction, char *);
}; Иногда даже лучше заменить функциональный указатель на наследование и виртуальные функции.
Перечисления и псевдонимы типов должны быть полностью квалифицированы для параметров сигналов и слотов
При проверке сигнатур своих аргументов, QObject::connect() сравнивает типы данных буквально. Таким образом, Alignment и Qt::Alignment обрабатываются как два различных типа. Чтобы обойти это ограничение, убедитесь, что типы данных полностью квалифицированы при объявлении сигналов и слотов и при установлении подключений. Например:
class MyClass : public QObject
{
Q_OBJECT
enum Error {
ConnectionRefused,
RemoteHostClosed,
UnknownError
};
signals:
void stateChanged(MyClass::Error error);
}; Вложенные классы не могут иметь сигналы или слоты
Вот пример проблемной конструкции:
class A
{
public:
class B
{
Q_OBJECT
public slots: // WRONG
void b();
};
}; Типы возвращаемых значений сигналов/слотов не могут быть ссылками
Сигналы и слоты могут иметь типы возвращаемых значений, но сигналы или слоты, возвращающие ссылки, будут обрабатываться как возвращающие void.
Только сигналы и слоты могут появляться в разделах signals и slots класса
moc будет жаловаться, если вы попытаетесь поместить другие конструкции в разделы signals или slots класса, кроме сигналов и слотов.
См. также Метаобъектная система, Сигналы и слоты и Системы свойств Qt.
© The Qt Company Ltd
Licensed under the GNU Free Documentation License, Version 1.3.
https://doc.qt.io/qt-6.2/moc.html