- PVSM.RU - https://www.pvsm.ru -

Развенчание мифов о мета-объектном компиляторе Qt

Я часто встречаю критику фреймворка Qt, в которой ему пеняют использованием мета-объектного компилятора (утилиты moc). Как один из разработчиков moc, я решил написать данную статью с целью развенчать некоторые связанные с этим мифы.

Вступление

Moc — это один из инструментов разработчика и часть библиотеки Qt [1]. Его задача — поддерживать расширение языка С++, необходимое для интроспекции и рефлексии в Qt (сюда относятся сигналы, слоты и QML). Для более детального объяснение вы можете почитать о том, как работают сигналы и слоты в Qt [2].

Необходимость использования moc является одним из главных объектов критики Qt. Это даже привело к появлению форков Qt, принципиально отказавшихся от moc (например, CopperSpice). Но всё-же большинство приписываемых moc так называемых недостатков не обоснованы.

Мифы

Moc переписывает ваш код перед тем, как передать его компилятору

Это распространённое заблуждение. Moc не модифицирует и не переписывает ваш код. Он просто парсит часть кода для того, чтобы сгенерировать дополнительные С++ файлы, которые потом будут компилироваться независимо. Это не очень большое отличие, но всё-же важное техническое недопонимание.

Moc просто автоматически генерирует шаблонный код, который можно было бы долго и нудно писать вручную, если бы moc не существовало. Если вы мазохист, то и сейчас вполне возможно взять и написать самостоятельно все таблицы для интроспекции и реализации сигналов. Ну или положиться на надёжный инструмент, который сделает это за вас.

Используя Qt, вы не пишете на настоящем С++

Я слышал этот довод много раз, но он попросту неверен. Макросы, используемые moc для аннотации кода — это стандартные макросы С++. Они должны быть корректно распознаны любым инструментом, способным анализировать код на С++. Когда вы добавляете в код Q_OBJECT, то просто дописываете объявление нескольких функций [3]. Когда вы пишете «signals:», то просто добавляете макрос, который превратится [4] в «public:». Многие другие макросы Qt вообще ни во что не раскрываются. Moc просто находит их и генерирует код эмиттеров сигналов и таблиц интроспекции.

Тот факт, что ваш код теперь может быть прочитан ещё одним инструментом, не делает его «менее соответствующим стандарту С++». Вы же не считаете код, написанный с расчётом на использование gettext или doxygen каким-то «менее правильным С++»?

Moc усложняет процесс сборки кода

Если вы используете любую промышленную систему сборки кода, вроде CMake или qmake, то получаете нативную поддержку Qt. Даже с какой-то собственной системой сборки речь идёт о всего-лишь одном дополнительном запуске команды обработки заголовочных файлов. Все известные мне системы сборки позволяют добавлять шаги по запуску дополнительных команд перед запуском компилятора, поскольку многие проекты в том или ином виде используют генерацию кода при сборке проекта. Вспомните, например, инструменты вроде yacc/bison, gperf [5], llvm/TableGen [6].

Moc делает отладку сложнее

Поскольку moc генерирует код на чистом С++, то отладчики не должны иметь никаких проблем с ним. Мы стараемся поддерживать сгенерированный код в таком состоянии, чтобы он не вызывал предупреждений компиляторов или инструментов статического или динамического анализа кода. Да, иногда при отладке вы будете видеть в колстеке следы сгенерированного moc кода. В некоторых редких случаях вы можете получить ошибку, связанную с кодом, созданным moc, но обычно причины достаточно легко обнаружить. Код, сгенерированный moc, достаточно человекочитаем. Также его, вероятно, понимать и отлаживать даже проще, чем те печально известные ошибки некоторых библиотек, построенных на продвинутом использовании шаблонов.

Отказ от moc улучшает производительность на этапе выполнения кода

Это прямая цитата с главной страницы CopperSpice, и, вероятно, самая большая их ложь. Код, генерируемый moc, очень тщательно старается избегать динамических аллокаций и уменьшить использование реаллокаций памяти. Генерируемые moc таблицы являют собой константные массивы и хранятся в read-only сегменте данных. CopperSpice же регистрирует свои QMetaObject (информацию о сигналах, слотах и свойствах) на рантайме.

Milian Wolff [7] проделал некоторые сравнения производительности Qt и CopperSpice в его докладе [8] на CppCon2015. Вот скриншот одного из его слайдов (меньше — лучше).

image

Также нужно отметить, что код на Qt даже с учётом запуска moc компилируется быстрее, чем код на CopperSpice.

Устаревшие мифы

Некоторая критика когда-то была справедливой, но более таковой не является.

Макрос не может быть использован при объявлении сигнала, слота, базового класса объекта, а также ...

До выхода Qt5 утилита moc действительно не раскрывала макросы. Но начиная с Qt 5.0 moc полностью поддерживает макросы во всех вышеперечисленных местах, так что это больше совершенно не является проблемой.

Перечисления (enums) и переопределения типов (typedefs) должны строго соответствовать при использовании их в качестве параметров сигналов и слотов

Это является проблемой только если вы всё ещё хотите использовать синтаксис соединений, основанный на строках (поскольку там действительно используется прямое сравнение названий типов). С выходом Qt5 и новым синтаксисом [9] это больше не препятствие.

Q_PROPERTY не позволяет использовать запятые в типах

Q_PROPERTY это макрос с одним аргументом, который ни во что не раскрывается и служит лишь для помощи moc. Но, поскольку это всё ещё макрос, запятая в, например, QMap<Foo, Bar> разделяет аргументы макроса и вызывает ошибку компиляции. Когда я увидел [10], как CopperSpice использует этот аргумент против Qt, то потратил 5 минут на то, чтобы исправить это [11] с использованием variadic-макросов из стандарта С++11.

Другая критика

Шаблоны, вложенные классы или классы, используемые множественное наследование, не могут быть QObject-ами

Хотя это и является правдой, но эти возможности просто пока не поддерживаются QObject, хотя и вполне могут быть реализованы в moc, если мы этого захотим. Проект Qt в данный момент не считает данные фичи приоритетными.

Я однажды добавил [12] поддержку шаблонных QObjects в moc, но это изменение не вошло в основную ветку разработки, поскольку никто больше не выразил интереса к данной функциональности.

Ещё нужно отметить поддержку шаблонных и вложенных классов в moc-ng [13].

Множественное наследование уже само по себе является очень неоднозначным. Чаще всего оно указывает на проблемы с архитектурой приложения и многие современные языки напрямую его запрещают. Вы всё ещё можете использовать множественное наследование с Qt, если если QObject является первым базовым классом в цепочке наследования. Это небольшое ограничение позволяет нам применять полезные оптимизации. Когда-нибудь задумывались почему qobject_cast настолько быстрее, чем dynamic_cast?

Выводы

Я не думаю, что moc — это проблема. Макросы Qt действительно помогают реализовать необходимую Qt функциональность. Сравнивая их с подходом CopperSpice мы можем заметить в последнем значительную избыточность служебного кода, а также недружелюбный синтаксис макросов (не говоря уже о потерях производительности на рантайме). Синтаксис сигналов и слотов, который существует в Qt с 90-ых годов — одна из фундаментальных вещей, обеспечивших успех фреймворка.

Вам может быть также интересно изучить некоторые эксперименты, связанные с moc, вроде moc-ng [13] (это moc, переписанный с использованием библиотек clang). Также есть вот это исследование [14] замены moc с помощью инструментов рефлексии С++. Ну и библиотека Verdigris [15], с макросами, создающими QMetaObject без moc.

Автор: Инфопульс Украина

Источник [16]


Сайт-источник PVSM.RU: https://www.pvsm.ru

Путь до страницы источника: https://www.pvsm.ru/qt-2/253540

Ссылки в тексте:

[1] библиотеки Qt: https://qt.io/

[2] сигналы и слоты в Qt: https://woboq.com/blog/how-qt-signals-slots-work.html

[3] нескольких функций: https://code.woboq.org/qt5/qtbase/src/corelib/kernel/qobjectdefs.h.html#_M/Q_OBJECT

[4] превратится: https://code.woboq.org/qt5/qtbase/src/corelib/kernel/qobjectdefs.h.html#_M/signals

[5] gperf: https://www.gnu.org/software/gperf/

[6] llvm/TableGen: http://llvm.org/docs/TableGen/

[7] Milian Wolff: http://milianw.de/

[8] докладе: https://youtu.be/RXTfC-35Nx8?t=42m26s

[9] новым синтаксисом: https://woboq.com/blog/new-signals-slots-syntax-in-qt5.html

[10] увидел: https://www.reddit.com/r/programming/comments/3969n6/copperspice_a_modern_c_fork_of_qt/cs0xn3s

[11] исправить это: http://code.qt.io/cgit/qt/qtbase.git/commit/?id=7f85fb4654f0d6f9058336af53148d81fc700497

[12] добавил: https://codereview.qt-project.org/49864/

[13] moc-ng: https://github.com/woboq/moc-ng

[14] исследование: https://woboq.com/blog/reflection-in-cpp-and-qt-moc.html

[15] Verdigris: https://woboq.com/blog/verdigris-qt-without-moc.html

[16] Источник: https://habrahabr.ru/post/327176/?utm_source=habrahabr&utm_medium=rss&utm_campaign=best