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

Самая большое преступление разработчиков 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
Года три назад переносил приложение с 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, точнее получение ресурса в методе «выставить ширину скроллбара». Или начальное значение.
Only those users with full accounts are able to leave comments. Log in, please.