Почему мне перестал нравиться Qt

Когда-то давным давно я полюбил Qt. Он был простой, с качественной документацией, наличием своей среды разработки. В нем были свои контейнеры QHash, QMap, QVector и после них, глядя на std, становилось сильно плохо.

Все изменилось в 2013-ом году, когда компиляторы gcc и clang начали полноценно поддерживать C++11. Этот факт прошел мимо меня, что и неудивительно. Все важные события нужно отслеживать самому.

Для понимания всей картины рекомендую посмотреть доклад от разработчика Qt: https://youtu.be/uZ68dX1-sVc?t=29m20s
Забавные факты (не все они упомянуты в докладе):

  • Все функции qSort, qAbs, qMin, Q_FOREACH и т.п. — deprecated. Настолько deprecated, что разработчики грозятся удалить все это из шестой версии Qt
  • Использование QList категорически не рекомендуется с версии Qt 5.8 (а сейчас актуален Qt 5.12). А до этого он рекомендовался к использованию вместо QVector. Внутри QList устроен очень хреново — это дек, но он может как хранить элементы, так и ссылки на элементы. В зависимости от размера объектов. Общение по внутреннему API идет строго через QList — догадайтесь насколько это удобно
  • Использование контейнеров Qt не рекомендуется во всех критичных к скорости местах. В конце девяностых контейнеры c implicid shared state были модными. Сейчас это уже не так. Каждый раз когда вы обращаетесь к Qt контейнеру он проверяет нужно ли ему делать detach, это не очень хорошее поведение
  • QTcpSocket имеет очень неприятную систему уведомлений о новых данных. Очень неприятную и очень незаметную. Это архитектурный просчет и он ну очень неудачный. Суть в том, что при обработке данных из сокета вам могут прийти новые данные, и вы об этом никогда не узнаете. Подробное описание проблемы
  • QVector поддерживает свой размер в переменной int. В том числе и под архитектурой x86_64. Ну не бред ли? Я на это уже нарывался, когда пытался прочитать файл размером ~1.5Гб в вектор за одну операцию. Почему так? Вероятно дело в обратной совместимости. std::vector держит размер в size_t. Оставим за кадром вопрос об отсутствии знака у size_t

Это всего лишь верхушка айсберга. C++ в последнее время начал резко быстрее развиваться. Официально, например, до C++11 не было возможности создавать потоки. Неофициально, как я понимаю, доступ к std::thread и std::mutex все-таки начиная с C++0x был. Потоки QThread были офигенным решением, но теперь оно просто не нужно.

std выигрывает за счет своей простоты. Qt проигрывает, потому что он держит совместимость.

Отдельно хочу пройтись по поводу Qml, который я в этой жизни надеюсь больше не увидеть. Речь идет большей частью о Qml 1.1 под десктопы, что может сильно влиять на мое мнение.

Во-первых, дебаггер qml бесполезен и не работает (система — CentOS6). Внутри Qml интерпретируется (привет всем проблемам не существующих переменных / опечаток / неверных типов). Все элементы Qml имеют очень странную систему определения своего места на экране. Есть якоря элемента, есть горизонтальные и вертикальные центры. Отдельно есть ширина и высота, прямые оффсеты (x, y), а также margin’ы. Каждое из значений может задаваться внутри элемента, а может снаружи. Привязки могут осуществляться к различным местам. Формально, можно подумать, что можно расположить любой объект наиболее удобным способом, но реально постоянно выплывают косяки вида огромный нормально выглядящий объект имеет нулевую ширину, потому что ее никто не указал. И ломается все, соответственно, через несколько дочерних объектов, когда кто-то привязывается именно к ширине. Особенно это заметно в ListView, который в приципе не умеет определять свои габариты. Надо прикидывать размер элементов в ListView и руками домножать на количество объектов. Максимальная костыльность, которая потом не раз аукается. Сюда же, в отношении Qml 1.1, идет полное отсутствие нормальных преднастроенных элементов. Я имею в виду PushButton, Slider, SpinBox, TextEdit и т.п. Все это в нашем проекте реализовывалось руками, и, с учетом того, что у нас на это не было нескольких лет, все это было реализовано очень криво. Например прокрутку окна, когда элементы не уместились, мы так и не смогли сделать нормально. Несколько тысяч элементов, и все начинало адски глючить.

Также, весь Qml просто напичкан своим «особенным» поведением. После виджетов хочется долго плеваться. Отсутствую хоткеи, нет нормальной системы диалоговых окон (все надо руками реализовывать). Непривычное поведение самописных кнопок — это вообще полный финиш.

Про то как Qml соединяется с C++ можно вообще говорить вечно. Вкратце — костылями и гвоздями. Ну и да, оно не работает. Как только начинает заходить речь о чем-то более сложном чем отдать в Qml модель или вызвать метод объекта из Qml, проблемы начинаются полным ходом. Так как Qml удаляет на свое усмотрение все те объекты, которые не видны, то доступ в Qml лучше не использовать. Конечно, тут можно подумать, мол, Qml — это идеальный View для модели MVC. Ведь если у него нет своего состояния, то и работать он должен максимально предсказуемо. По факту это не так, у движка есть состояние и это очень плохо. Очень плохо это, потому что это состояние из него получить нереально сложно.

Нормального API для доступа к объектам Qml нет, есть только уродское. Например, для получения доступа к объекту ChartView я действовал примерно здесь. Я передавал после инициализации из Qml ссылку на XYSeries в C++. После чего по цепочке родителей поднимался до объекта, которого получалось через qobject_cast (улучшенный аналог dynamic_cast) скастовать к QChart. И другого способа достучаться до QChart у меня нет. Костыли в чистом виде. Да, если все писать внутри Qml, то подобных проблем нет, но тогда все будет адски тормозить из-за дурацкого движка JavaScript (а разметка у нас была огромная).

Даже в Qml 2 хватает приколов по недописанному функционалу. на осях графиков есть возможность установить диапазон через setRange, но нет getRange — вместо них доступны min и max… Есть возможность поменять стиль ручки осей на графике, но эта же ручка используется для отрисовки промежуточных линий на графике, так что ее бесполезно делать пунктиром. Откровенно не достающего функционала настолько много, что о нем можно вечно говорить. Есть еще и «мигающий» функционал. Т.е. графики ChartView предоставляют ряд сигналов вида «левая кнопка мыши нажата / отжата», «произошло выделение» и т.п. В случае если доступно OpenGL ускорение, эти сигналы перестают работать. Я просто чудом нашел эту ремарку где-то глубоко в недрах документации читая все подряд. Сейчас почему-то не смог найти еще раз этот кусок документации, нагуглилось только «Mouse events for series are reported asynchronously», тем не менее — незаметное исчезновение функционала это непорядок. Должны хотя бы какие-нибудь warning’и лететь в консоль. Anyway, в старой версии эти события именно что не прилетали.

Архитектурные просчеты я считаю нормальным явлением, когда речь идет, скажем, о 20-летнем софте. Для молодого продукта под названием Qml это непростительно. Он недопилен, в нем не хватает функционала, он медленный и он неудобный. Наверное он неплохо работает на смартфонах. Но не более того. Если захочу писать десктопные приложения так же, как народ клепает сайты, то я для этого буду использовать что-то более адекватное. Писать чистом Qml, например, может оказаться вполне нормально.


Эта статья лежала в черновиках почти полгода, она была написана до получения оффера в Яндексе. В черновиках она лежала не просто так — я хотел дописать кусок про глюки графиков в Qml и ужасную работу Qml SurfaceView относительно QCustomPlot ColorMap. К сожалению, все детали уже забыты.

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *