Как стать автором
Обновить

Комментарии 46

Спасибо за статью. У меня валяется дипломный проект на Qt4, вот думаю когда-нибудь его перетащить на Qt5. Мне, наверно, переходить будет проще, не такой большой разрыв
Полгода назад переводил огромный проект с Qt4 на Qt5, поменялись по большому счету только некоторые заголовки, и некоторые минорные вещи в API, в целом за пару дней работа была закончена.
Подтверждаю, тоже портировали с Qt 4.8 на тогда еще Qt 5.3 большой проект (~0.5 млн строк + библиотеки) — заняло пару дней. Исправления были минимальные, ничего не переписывали.
Qt3->Qt4 реально весьма болезненно. Поэтому мы до сих пор на qt3. В качестве подготовки перехода сейчас выкидываю Qt и переписываю всё что можно на std::c++ и boost (кроме графического интерфейса).

Самая большое преступление разработчиков Qt — игнорирование такой полезной возможности С++, как пространство имён. Если бы они его использовали, то можно было бы, по крайней мере на время переходного периода, линковать обе библиотеки…

Qt можно с пространством имен скомпилировать

Это удивительно, ведь сделали же, но всё равно по умолчанию это отключено и во всех дистрибутивах собрано без пространства имён.

Так это давно уже есть и нужно чтобы подобные ситуации разруливать

Это появилось начиная с Qt 4.4 и для использования требует перекомпиляции. Поэтому при переходе с qt5 на qt6 это можно будет использовать, просто пересобрав qt5 с этой опцией и завернув старый код в пространство имён. Но у qt3 нет такой возможности. Соответственно, для того чтобы это использовать, придётся заворачивать новый код в пространство имён и всегда пересобирать вручную qt5.
Не на всех платформах есть такое счастье, поэтому по умолчанию и отключено.

Насчет выкидывания Qt и переписывания на чистые плюсы и буст — если проект долгоиграющий, то это вам может аукнуться в будущем. Qt очень большой, слаженный, с отличным коммьюнити, огромным количеством примеров, понятным и логичным API. Неподготовленный пользователь может быстро стартануть с Qt.

А вот boost — уже для куда более подготовленных плюсовиков. И, к сожалению, не всегда такие есть под рукой.
В том и дело, что старый код написанный на старом добром C++ собирается без проблем, а код написанный с использованием Qt вызывает много проблем из-за несовместимости Qt с самим собой и просто непродуманности. В итоге «простота» обернулась непредвиденными проблемами.
Чего стоит только одна небезопасная реализация QString для потоков в qt3! Я просто вынужден выкидывать QString везде, где используются потоки и заменять на std::string.
Если бы в своё время мы ограничились бы использованием Qt только для графического интерфейса, то переход q3->q5 был бы значительно дешевле.

При этом, вместо того чтобы просто выкинуть QString в qt5, его продолжают тянуть в новые версии.

Вообще QString лучше с юникодом дружит и в целом удобнее для разработчика

UTF-16 — худший выбор. Для работы со строками достаточно удобен utf-8, для работы с отдельными символами лучше использовать utf-32. Поэтому не соглашусь.
Вопрос не только в собирается / не собирается, а в поддержке кода. Старый код на старом добром C++ (а чаще — на C с классами) может и собирается, но разобраться в нем зачастую боль и страдание.

только одна небезопасная реализация QString для потоков

Что значит эта фраза? QString реализует принцип CoW (Copy-On-Write), что, в общем-то, является одной из киллер-фич фреймворка, и позволяет практически безболезненно быстро гонять данные между потоками.

При этом, вместо того чтобы просто выкинуть QString в qt5, его продолжают тянуть в новые версии.

Кхм… у меня есть подозрения, что вы все-таки не очень разобрались с фреймворком. QString хранит внутри себя Unicode символы, а std::string — байты. В итоге QString знает, сколько хранит внутри себя символов, умеет гонять данные между разными кодировками, а также имеет удобное API, а std::string — не знает и не умеет. В итоге тот же lenght на UTF-8 данных вернет не количество символов, а размер в байтах.
Вообще, std::string корректнее сравнивать с QByteArray.
QString::size() возвращает не количество символов, а кол-во QChar в строке. Unicode-символы с кодом больше 65535 кодируются при помощи суррогатных пар с использованием двух QChar.
Вообще, UTF-16 это худшая из кодировок, если сравнивать с UTF-8 и UTF-32. Полагаю, что Qt её выбрали с оглядкой на Windows. В юниксах в то время, до всеобщего принятия UTF-8, был полный зоопарк. Теперь приходится тащить на себе бремя обратной совместимости.
Верное замечание, спасибо. Тем не менее, такой результат — это более логичное поведение для строки.

Насчет UTF-16 — да, думаю, из-за винды. UTF-32 в винде почти нигде не используется, поэтому неудивительно, что выбрали средний вариант.
Тем не менее, такой результат — это более логичное поведение для строки.

Логичное поведение для строки — возвращать количество символов, а не размер массива. Если же такое поведение устраивает, то с таким же успехом можно использовать std::wstring.
Еще раз: возвращается количество объектов QChar. В кодировке UTF-16 при значения больше 65535 используется 2 объекта QChar (ну потому что стандарт так говорит). Вполне логично, что на запрос размера мне возвращается количество QChar.

std::wstring

Ага, только wchar_t в винде занимает 2 байта, а в никсах — 4 байта. Со всеми вытекающими проблемами отсюда.
Ага, только wchar_t в винде занимает 2 байта, а в никсах — 4 байта. Со всеми вытекающими проблемами отсюда.

Есть std::u16string. Но главное — есть нормальные полноценные библиотеки типа ICU.
только одна небезопасная реализация QString для потоков в qt3


Что значит эта фраза? QString реализует принцип CoW (Copy-On-Write), что, в общем-то, является одной из киллер-фич фреймворка, и позволяет практически безболезненно быстро гонять данные между потоками.

Вы неправы теоретически и фактически.

Теоретически CoW наоборот усложняет и затрудняет обмен данными между потоками, потому что требует использование блокировки в момент изменения объекта. Без использования CoW, мы получаем независимую копию объекта, что делает блокировку излишней, а код реализации объекта — проще. С приходом же C+11 и его концепцией перемещений, CoW становится излишним в большинстве случаев.

Практически в qt5 реализация QString вынуждена использовать блокировки, чтобы позволить использовать строки в потоках. Но в qt3, о чём и шла речь, и что Вы аккуратно обрезали при цитировании (случайно, я надеюсь), всё очень и очень плохо — там используется cow и не используются блокировки. В результате, при попытке использование строки в потоке, будут происходить непредсказуемые обрушения программы в произвольном месте.
QString хранит внутри себя Unicode символы, а std::string — байты.

На самом деле обе реализации хранят внутри себя байты, и обе могут без проблем хранить, вводить и выводить строки юникода.

Если же есть необходимость работать с отдельными символами юникода, то для этого существуют полноценные библиотеки и прочие boost::locale. Почему в 2018 это до сих пор это не часть стандарта (или уже?) — вопрос к комитету.

Вообще, отступая от темы, Qt исторически был вынужден изобретать велосипеды для каких-то ограниченных платформ и не всегда у него это выходило идеально, хотя для своего времени это было неплохо. На сегодня ситуация сильно изменилась и многие из этих изобретений уже не нужны и надеюсь в следующей версии qt будут существенные изменения в эту сторону.
Практически в qt5 реализация QString вынуждена использовать блокировки

Все-таки внутри QString (да и вообще всех классов Qt, использующих implicit sharing) используются атомарные счетчики, а не стандартные блокирующие примитивы. Как только мы копируем объект, счетчик ссылок увеличивается на один, и мы получаем легкую копию (shallow copy). Если мы хотим изменить объект, вызывается detach и мы получаем глубокую копию (deep copy).

Так вот, если мы делаем копию строку в другом потоке, сначала получаем shallow копию, увеличиваем счетчик и работаем с shared данными. Как только мы начинаем изменять объект, мы получаем отвязанную локальную копию нашей строки, которая уже не ссылается на данные в других потоках. Ее изменение происходит локально, ничего нигде не сломается.

Совсем другое дело, если наша строка являются общей для разных поток, и они ее пытаются изменить. Да, тут нужны традиционные блокировки аля mutex, но это ровно также нужно и со всеми другими типами данных (то есть, аналогично нам нужно было бы сделать и с std::string). QString является реентерабельной функцией, не потокобезопасной.

Вообще, для решения такой проблемы (совместное изменение общих данных) разработчики рекомендуют использовать сигналы-слоты.

С чем я соглашусь:
1) Действительно, с приходом move-семантики implicit sharing уже не выглядит так вкусно. Единственным аргументом в использовании implicit sharing сейчас является разве что его хорошая производительность из коробки, тогда как про move-семантику нужно всегда помнить и писать код соответствующе (к сожалению, до сих пор многие этим пренебрегают).
2) Действительно, я проверил, в qt3 не было атомарных операций при implicit sharing, что ломало счетчик в разных потоках. Только с Qt4 все стало нормально.
Но я не очень понял, как так получилось, что раньше так код работал, а потом, когда стало все нормально — перестал.

На самом деле обе реализации хранят внутри себя байты

Естественно. Только QString имеет представление о том, что она хранит, а std::string — нет.

Если же есть необходимость работать с отдельными символами юникода

Перегнать что-то из одной кодировки в другую — на самом деле, не такая уж и редкая проблема. Да, в мире, где казалось бы, уже везде используется UTF-8, все равно встречаются системы с какой-нибудь KOI8-R, и никто ничего переделывать не будет. Вот Qt позволяет такие вопросы решать из коробки очевидными способами.
А вот почему этого всего нет в стандарте — вопрос действительно хороший.

Вообще, отступая от темы, Qt исторически был вынужден изобретать велосипеды для каких-то ограниченных платформ

В первую очередь — из-за ограничений языка. Сейчас-то и moc уже не нужен, есть реализация от авторов оригинального moc'a чисто на шаблонной магии и новых стандартах. Я тоже надеюсь, что велосипеды будут потихоньку заменяться на стандарт, а новые фичи будут упрощать код. Но все же даже сегодня писать на Qt очень приятно и производительно.

Все-таки внутри QString (да и вообще всех классов Qt, использующих implicit sharing) используются атомарные счетчики, а не стандартные блокирующие примитивы.

Атомарные операции не бесплатны, хотя и дешевле мьютексов. Конечно, то что в Qt это сделали — большой плюс qt5 по сравнению с qt3, где использование QString с потоками было связано со всяческими опасностями. Но, как по мне, лучше бы перешли на стандартную строку.

Естественно. Только QString имеет представление о том, что она хранит, а std::string — нет.

На сколько я знаю, QString не умеет нормально работать с комбинированными символами, состоящими из двух двухбайтовых слов. Точно также, как и std::u16string или std::string с многобайтовыми символами. Для этого нужны специализированные библиотеки. То есть никакого преимущества здесь я не вижу.
Сейчас-то и moc уже не нужен, есть реализация от авторов оригинального moc'a чисто на шаблонной магии и новых стандартах.

Да. И это и многое многое другое. Регулярки, потоки и т.д.
большой плюс qt5 по сравнению с qt3

Еще в Qt4 это сделали. И надо сказать, что Qt4 появился аж в 2005 году, 13 лет назад. А это времена, когда никаких умных указателей и потоков в GCC не было, не говоря уж про msvc.
Qt3 — это 2001 год. К слову, первый интелловский двухядерный процессор Pentium D и амдешный Opteron появились только в 2005. И я не уверен, что там была отличная аппаратная поддержка атомарных операций (во всяком случае, компиляторы точно не умели правильно генерировать код). В тоже время, Qt3 имеет поддержку потоков (треды, мьютексы, conditional variable и прочее). И мне кажется, вы слишком много просите для того времени.

Но, как по мне, лучше бы перешли на стандартную строку.

Конечно, нет. Строки в стандарте — это ужас. Вернее, как мы уже обсудили выше, строк как таковых в стандарте нет вообще.

На сколько я знаю, QString не умеет нормально работать с комбинированными символами, состоящими из двух двухбайтовых слов

QString::fromWCharArray

Регулярки, потоки и т.д.

Опять же, Qt давно имеет эти классы, на них уже написано много кода, они хорошо, логично и удобно реализованы. Зачем ломать удобные классы, только лишь потому, что в стандарте недавно появилось аналогичное?
Я за то, чтобы заменить заведомо костыльные вещи на что-то новое и удобное. moc — замечательный костыль своего времени, но сейчас его можно реализовать элегантнее, не сломав старый код. Слоты в Qt5, благодаря лямбдам, стали намного удобнее и красивее. Вот я за такие изменения.

Кстати, на Qt чаще ругаются не когда они вводят что-то новое (за это наоборот все радуются), а когда они что-то удаляют. Как было, например, с перехода QtWebKit на QtWebEngine. Вот тут у людей были болезненные переходы.

И еще есть момент. Qt очень часто голым используется в embedded устройствах, прям минуя STL. Если памяти довольно мало, то практически всегда выгоднее тащить с собой QtCore с огромным функционалом из коробки, вместо довольно голого STL.
мне кажется, вы слишком много просите для того времени.

Простите, где?

Конечно, нет. Строки в стандарте — это ужас. Вернее, как мы уже обсудили выше, строк как таковых в стандарте нет вообще.

Они не особо хуже QString, как мы уже вроде бы обсудили выше. Точно так же нет поддержки комбинированных символов из коробки.

QString::fromWCharArray

?

Зачем ломать удобные классы, только лишь потому, что в стандарте недавно появилось аналогичное?

Ну почему же сразу ломать? Конечно, какой-то уровень совместимости для работы старого кода следует оставить, но вечно таскать за собой ставшие ненужными костыли неправильно. Qt это хорошая библиотека для создания графических интерфейсов и пусть она останется таковой.
Они не особо хуже QString, как мы уже вроде бы обсудили выше. Точно так же нет поддержки комбинированных символов из коробки.


Вообще-то умеет

Другое дело, что Unicode итераторы нифига не документированы, но вот, скажем, пример
О! Не знал. Интересно сравнение уровня поддержки юникода в Qt и ICU.
Qt может быть скомпилена с поддержкой ICU (вкл по дефолту), тогда какие-то вещи QString/QLocale и друзья делают через ICU. Какие конкретно — сказать не могу, не копал так глубоко.
В общем случае, Qt не переизобретает велосипед, а умеет только какие-то базовыве вещи, т.е. ICU шире. Ну а фигли, там либа весит больше чем QtCore)
В контексте нашей беседы c oYASo выше, если бы Qt нужно было заново создавать сегодня, то ICU + std::basic_string из C++11 было бы вполне достаточно и QString был бы не нужен.
Но в реальности разработчикам Qt придётся тащить QString ещё много лет.
Нет, вы не правы. Во-первых, врядли кто-либо разрешит засунуть ICU в стандартную библиотеку (как зависимость, а-ля pthreads). То есть можно сразу забыть о std::to_lower(«АБВ») и друзьях (привет, QString::number, QString::localeAwareCompare) — скажут дергайте ICU руками. Но это не оч удобно.
Во-вторых, никто не торопится в стандарт добавить функции работы непосредственно со строками — нормальный replace, split/join. Да, возможно, в виде генерик алгоритмов они смотрелись бы лучше, но… Пока никто не написал proposal на это, а значит приходиться кушать что дают. Сюда же попадает localeAwareCompare, он сейчас юзает платформенные функции для (т.е. ICU не особо нужен), но о5 же, нет пропозала в стд для аналога.
В третьих, кодеки для разных кодировок — std::u16string тупо массив short'ов без знания о том, Big-Endian, Little-Endian он или ещё какой.

Вот было огромное обсуждение касательно QString/QStringView (utf16 vs utf8 там тоже затронули).
В общем, сейчас политика партии такова — обоже никогда не юзайте QList, вместо QVector используйте std::vector, вместо QScopedPointer — std::unique_ptr, если это не касается публичного API. До сих пор нет решения касательно холивара «юзать ли std:: в публичном API». Из-за обещаний BC между версиями, нельзя юзать стандартные типы (вдруг stdlib поменяет BC), но слом BC в стандартной библиотеке всё равно ведёт к пересборке всего мира, так что, надеюсь, этот холивар разрешится в пользу std:: к 6й версии). Например, зарезали мою реализацию QOptional (aka std::experimental::optional) — юзай std, говорят. Аналогично не хотят добавлять move-семантику в QScopedPointer (зачем, если есть unique). А вот QString/QStringView/QByteArray — скорее исключения из правила юзать std.
А так, QThread уже давно внутри юзает std::thread (только наворачивает эвентлуп поверх), атомики юзают std:: и только QString живёт и процветает потому что аналога в стандартной библиотеке всё ещё нет.
> Вот было огромное обсуждение касательно QString/QStringView (utf16 vs utf8 там тоже затронули).

Спасибо, почитаю на досуге.

> и только QString живёт и процветает потому что аналога в стандартной библиотеке всё ещё нет.

Когда же что-то хотя бы уровня ICU появится в стандарте? (хотя ICU тоже заточен под utf-16, как я понял). Ведь уже 2018 год! Доживу ли?
А так, QThread уже давно внутри юзает std::thread


Что-то я прям загорелся от этой фразы, это вы про что рассказываете? про какую-то будущую 6 ветку? т.к. в текущем релизе я ничего такого не наблюдаю:
github.com/qt/qtbase/blob/5.11/src/corelib/thread/qthread_win.cpp
github.com/qt/qtbase/blob/5.11/src/corelib/thread/qthread.cpp
Ваша правда, давно не глядел в код Qt, попутал. Думал, треды переписали вместе с атомиками, но нет.
Года три назад переносил приложение с Qt3 на Qt5. Наибольшую сложность составили .ui файлы — их пришлось просто заново нарисовать в дизайнере, потом вручную код перенести. Благо, на CentOS 5 можно было установить из репозитория сразу Qt3 Designer и новый QtCreator.

Ещё немало изменений было в конструкторах, так же, как и у автора.

Одно из самых проблемных мест — цветовые схемы, как они применялись в дизайнере, серьёзно изменились.
Получается, в распоряжении было 7 символов, т.к. первый почему-то обязательно должен быть «q» ))
Помню свою боль перехода с qt 4 и webkit на ранний qt 5 и вебенжину… Половины функций, что требовалось от вебкита в вебенжине не было, жуть.
Про совместимость бинарников в минорных версиях уже не актуально. Qt 5.4 последняя версия, которую можно собрать под XP, Qt 5.5 — последняя которую можно деплоить под XP. Может некстати подвести — обновился с 5.3 на 5.10, вроде ничего не предвещало несовместимости — получите, распишитесь.
Ну, странно было бы ожидать, что Qt будет вечно продолжать поддерживать ОС, которую уже и производитель этой ОС не поддерживает.
Вот я с одной стороны с вами согласен, что пора уже выкидывать везде поддержку ХР, но сам производитель ОС до сих пор сохранил поддержку деплоя на ХР для msvc2017.
Что-то ваша информация неточная, 5.6 еще поддерживает XP, мы с ним проекты на XP деплоим вполне успешно. Вот с 5.7 и выше, да, облом (там еще и с макосью тоже беда с поддержкой).
Qt гарантирует совместимость на уровне кода и бинарников при обновлении между минорными версиями фреймворка

Минорные — это 4.Х или, скажем, 4.3.Х
Потому как если первый вариант — ой, врут.
(пруфы)
1. сейчас держим две версии библиотек: 4.5 и 4.7, т.к. новые требует third-party либа, используемая нами, но при попытке стартовать только с 4.7 — софт улетает в неведомые области памяти. Дело тут скорее всего в особенностях компиляции разных версий.
2. С каждым изменением graphics pipeline начинается игра «что поменяли в работе с OGL на поверхностях Qt». Артефакты бывают весьма забавными и сложноловимыми.
С third-party конечно беда, как правило надо держать зоопарк версий Qt. Однако, справедливости ради, нужно отметить, что во многих случаях это не проблема Qt — многие разработчики в проектах вставляют целые куски кода (может модифицированного под себя) из т.н. «private implementations», благо код Qt открыт и свободен. Естественно там ломается любая совместимость. Но тут как повезет, например достаточно годная библиотека Qxt, канувшая в лету, не соберется уже никогда после Qt 5.3, но бинарники исправно работают на всех минорных версиях 5й ветки.

Недавно переносил приложение с qt3 на qt4. На qt5 перейти не получилось, там насколько я понял api изменилось сильно. Так что статья не оторвана от жизни :)

api как раз с 4->5 не сильно изменилось, я проект на 200к строк кода за несколько дней портировал (никакого приватного API правда не использовалось).
«Использование индивидуальных сеттеров — значительно лучшая практика, чем конструктор, принимающий 7 аргументов.» чуть более чем спорно. Про RAII вы скорее всего не слышали.
Проблема конструкторов, принимающих по 7 аргументов — в конструкторах вида
FooBar(int* a, int b=3, int c=2, int d = 1, int e =0, int f = 1, double g = 1)
RAII надо использовать там, где это приносит выгоду — во всех же остальных местах Named Parameter Idiom будет приводить к гораздо меньшему числу багов и более быстрому их поиску.
Не очень понял какой там RAII, точнее получение ресурса в методе «выставить ширину скроллбара». Или начальное значение.
Зарегистрируйтесь на Хабре, чтобы оставить комментарий