Контейнеры STL или Qt?

Каковы преимущества и недостатки использования контейнеров Qt (QMap, QVector и т.д.) по их эквиваленту STL?

Я вижу одну причину, чтобы предпочесть Qt:

  • Контейнеры Qt могут быть переданы другим частям Qt. Например, они могут быть использованы для заполнения QVariant, а затем a QSettings (с некоторым ограничением, однако принимаются только QList и QMap/QHash, ключи которых являются строками).

Есть ли другие?

Изменить. Предполагая, что приложение уже использует Qt.

Ответ 1

Я начал с использования std::( ​​w) string и контейнеров STL исключительно и преобразовывал в/из эквивалентов Qt, но я уже переключился на QString, и я обнаружил, что я все больше использую контейнеры Qt.

Когда дело доходит до строк, QString предлагает гораздо более полную функциональность по сравнению с std:: basic_string, и это полностью unicode. Он также предлагает эффективную реализацию COW, на которую я очень сильно полагаюсь.

Контейнеры Qt:

  • предлагает ту же реализацию COW, что и в QString, что чрезвычайно полезно, когда дело доходит до использования макроса Qt foreach (который делает копию) и при использовании метатипов, сигналов и слотов.
  • может использовать итераторы стиля STL или итераторы в стиле Java
  • являются потоковыми с помощью QDataStream
  • широко используются в Qt API
  • имеют стабильную реализацию по сравнению с операционными системами. Реализация STL должна подчиняться стандарту С++, но в противном случае можно делать все, что угодно (см. раздел std::string COW споры). Некоторые реализации STL особенно плохой.
  • предоставлять хеши, которые недоступны, если вы не используете TR1

QTL имеет другую философию из STL, которая хорошо обобщила Дж. Бланшетт: "В то время как контейнеры STL оптимизированы для сырой скорости, Классы контейнеров Qt были тщательно разработаны, чтобы обеспечить удобство, минимальное использование памяти и минимальное расширение кода".
В приведенной выше ссылке содержится более подробная информация о реализации QTL и о том, какие оптимизации используются.

Ответ 2

Трудно ответить на вопрос. Это действительно может свести к философскому/субъективному аргументу.

Это сказано...

Я рекомендую правило "Когда в Риме... сделайте, как делают римляне"

Это означает, что если вы находитесь на земле Qt, код, как это делают Qt'ians. Это касается не только соображений читаемости и согласованности. Рассмотрите, что произойдет, если вы храните все в контейнере stl, тогда вам нужно передать все эти данные функции Qt. Вы действительно хотите управлять кучей кода, который копирует вещи в/из контейнеров Qt. Ваш код уже сильно зависит от Qt, поэтому он не похож на то, что вы делаете его более стандартным с помощью stl-контейнеров. И какова точка контейнера, если каждый раз, когда вы хотите использовать его для чего-либо полезного, вы должны скопировать его в соответствующий контейнер Qt?

Ответ 3

Контейнеры Qt более ограничены, чем STL. Несколько примеров того, где STL превосходят (все это я использовал в прошлом):

  • STL стандартизирован, не изменяется с каждой версией Qt (Qt 2 имеет QList (на основе указателей) и QValueList (основанный на значении); Qt 3 имеет QPtrList и QValueList; теперь Qt 4 имеет QList, и это ничего не значит QPtrList или QValueList).
    Даже если вы закончите использование контейнеров Qt, используйте подмножество API, совместимое с STL (т.е. push_back(), а не append(); front(), а не first(),...), чтобы избежать портирования еще раз, введите Qt 5. В обоих переходах Qt2- > 3 и Qt3- > 4 изменения в контейнерах Qt были среди тех, которые требуют наибольшего отторжения кода.
  • Двунаправленные контейнеры STL имеют rbegin()/rend(), что делает обратную итерацию симметричной для последующей итерации. У контейнеров Qt нет такой вещи, поэтому обратная итерация бесполезно сложна.
  • Алгоритмы STL имеют диапазон insert() от разных, но совместимых типов итераторов, что делает std::copy() гораздо менее необходимым.
  • Контейнеры STL имеют аргумент шаблона Allocator, что делает управление пользовательской памятью тривиальным (требуется typedef) по сравнению с Qt (fork QLineEdit требуется для s/QString/secqstring/).
  • Нет Qt, эквивалентного std::deque.
  • std::list имеет splice(). Всякий раз, когда я нахожусь с помощью std::list, это потому, что мне нужно splice().
  • std::stack, std::queue правильно агрегируют свой основной контейнер и не наследуют его, как QStack, QQueue do.
  • QSet похож на std::unordered_set, а не как std::set.
  • QList является просто странным.

Многие из вышеперечисленных могут быть решены довольно легко в Qt, но в библиотеке контейнеров в Qt, похоже, не хватает фокуса разработки на момент.

EDIT 20150106. Проведя некоторое время, пытаясь привести С++ 11-поддержку в классы контейнеров Qt 5, я решил, что это не стоит того. Если вы посмотрите на работу, которая внедряется в стандартные реализации библиотек С++, совершенно ясно, что классы Qt никогда не догонят. Теперь мы выпустили Qt 5.4, а QVector по-прежнему не перемещает элементы при перераспределении, не имеет emplace_back() или rvalue- push_back()... Мы также недавно отклонили шаблон класса QOptional, ожидая std::optional. Аналогично для std::unique_ptr. Я надеюсь, что эта тенденция сохранится.

Ответ 4

Позвольте разложить эти утверждения на реальные измеримые явления:

  • Зажигалка: контейнеры Qt используют меньше памяти, чем контейнеры STL.
  • Более безопасный: у контейнеров Qt меньше возможностей для неправильного использования.
  • Легче: контейнеры Qt представляют меньшую интеллектуальную нагрузку.

Легче

В этом контексте утверждается, что итерация в стиле Java как-то "проще", чем стиль STL, поэтому Qt проще в использовании из-за этого дополнительного интерфейса.

Стиль Java:

QListIterator<QString> i(list);
while (i.hasNext())
    qDebug() << i.next();

Стиль STL:

QList<QString>::iterator i;
for (i = list.begin(); i != list.end(); ++i)
    qDebug << *i;

Стиль итератора Java имеет преимущество быть немного меньшим и более чистым. Проблема в том, что это больше не стиль STL.

С++ 11 Стиль STL

for( auto i = list.begin(); i != list.end(); ++i)
    qDebug << *i;

или

С++ 11 стиль foreach

for (QString i : list)
    qDebug << i;

Это настолько радикально просто, что нет оснований когда-либо использовать что-либо еще (если вы не поддерживаете С++ 11).

Моим любимым, однако, является:

BOOST_FOREACH(QString i, list)
{
    qDebug << i;
}

Итак, как мы видим, этот интерфейс не дает нам ничего, кроме дополнительного интерфейса, поверх уже гладкого, обтекаемого и современного интерфейса. Добавление ненужного уровня абстракции поверх уже стабильного и удобного интерфейса? Не моя идея "проще".

Кроме того, интерфейсы Qt foreach и java добавляют служебные данные; они копируют структуру и обеспечивают ненужный уровень косвенности. Это может показаться не таким уж большим, но зачем добавить слой служебных данных, чтобы обеспечить интерфейс, который не очень упрощен? Java имеет этот интерфейс, потому что java не имеет перегрузки оператора; С++ делает.

Безопаснее

Обоснование, которое дает Qt, - это проблема неявного обмена, которая не является ни подразумеваемой, ни проблемой. Однако это связано с совместным использованием.

QVector<int> a, b;
a.resize(100000); // make a big vector filled with 0.

QVector<int>::iterator i = a.begin();
// WRONG way of using the iterator i:
b = a;
/*
Now we should be careful with iterator i since it will point to shared data
If we do *i = 4 then we would change the shared instance (both vectors)
The behavior differs from STL containers. Avoid doing such things in Qt.
*/

Во-первых, это неявно; вы явно назначаете один вектор другому. Спецификация итератора STL четко указывает, что итераторы принадлежат контейнеру, поэтому мы четко представили общий контейнер между b и a. Во-вторых, это не проблема; до тех пор, пока соблюдаются все правила спецификации итератора, абсолютно ничего не пойдет не так. Единственный раз, когда что-то идет не так:

b.clear(); // Now the iterator i is completely invalid.

Qt указывает это, как будто это что-то значит, так как проблема возникает из этого сценария. Это не так. Итератор недействителен и точно так же, как и все, к чему можно получить доступ из нескольких непересекающихся областей, именно так оно и работает. Фактически, это будет происходить с помощью итераторов стиля Java в Qt, благодаря большой зависимости от неявного обмена, который является антипаттерном, как описано здесь здесь, и во многих других областях . Кажется особенно странным, что эта "оптимизация" должна быть введена в действие в рамках, все более и более приближающейся к многопоточности, но для маркетинга.

Зажигалка

Это немного сложнее. Использование Copy-On-Write и Implicit Sharing and Growth Strategies очень затрудняет фактическое предоставление гарантий того, сколько памяти ваш контейнер будет использовать в любой момент времени. Это не похоже на STL, что дает вам сильные алгоритмические гарантии.

Мы знаем минимальная граница потраченного впустую пространства для вектора - это квадратный корень длины вектора, но, похоже, нет способ реализовать это в Qt; различные "оптимизации", которые они поддерживают, будут исключать эту очень важную функцию экономии пространства. STL не требует этой функции (и большинство из них используют удвоение роста, что является более расточительным), но важно отметить, что вы могли бы хотя бы реализовать эту функцию, если это необходимо.

То же самое относится к двусвязным спискам, которые могут использовать привязку XOr для резкого сокращения используемого пространства. Опять же, это невозможно с Qt, из-за этого требования к росту и COW.

COW действительно может сделать что-то более легкое, но так же могут быть Intrusive Containers, например, поддерживаемые boost, а Qt часто используется в более ранние версии, но они больше не используются, потому что они сложны в использовании, небезопасны и налагают нагрузку на программиста. COW - гораздо менее назойливое решение, но непривлекательное по причинам, поставленным выше.

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

В заключение

Избегайте использования контейнеров Qt, когда это возможно, без начисления стоимости копирования и, если возможно, используйте итерацию типа STL (возможно, через оболочку или новый синтаксис).

Ответ 5

Контейнеры STL:

  • Гарантии производительности
  • Может использоваться в алгоритмах STL, которые также имеют гарантии производительности.
  • Можно использовать сторонние библиотеки С++, такие как Boost
  • Являются стандартными и могут пережить запатентованные решения.
  • Поощрять общее программирование алгоритмов и структур данных. Если вы пишете новые алгоритмы и структуры данных, которые соответствуют STL, вы можете использовать то, что STL уже предоставляет бесплатно.

Ответ 6

В контейнерах Qt используется идиома copy-on-write.

Ответ 7

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

Кроме того, если вы уже используете контейнеры Qt, может быть несколько более оптимальным использовать их исключительно, так как вам не нужно включать заголовочные файлы STL и потенциально ссылаться в библиотеках STL. Однако, в зависимости от вашей инструментальной цепочки, это может случиться в любом случае. Чисто с точки зрения дизайна, согласованность, как правило, хорошая вещь.

Ответ 8

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

Если данные в основном используются внутри приложения, и вы вряд ли удаляетесь из Qt, а затем запрещаете проблемы с производительностью, используйте контейнеры Qt, потому что он сделает биты данных, которые идут в пользовательский интерфейс, иметь дело.

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

Ответ 9

Помимо различия в COW, контейнеры STL гораздо более широко поддерживаются на различных платформах. Qt достаточно портативен, если вы ограничиваете свою работу "основными" платформами, но STL доступен и на многих других более неясных платформах (например, в DSP Texas Instruments).

Поскольку STL является стандартным, а не контролируется одной корпорацией, есть, вообще говоря, больше программистов, которые могут легко читать, понимать и модифицировать код STL и другие ресурсы (книги, онлайн-форумы, конференции и т.д.), чтобы поддержите их в этом, чем для Qt. Это не означает, что нужно уклоняться от Qt только по этой причине; просто, что, при прочих равных условиях, вы должны по умолчанию использовать STL, но, конечно, все вещи редко равны, поэтому вам придется решать в своем собственном контексте, который имеет наибольший смысл.

Что касается ответа AlexKR: производительность STL гарантируется в пределах, но данная реализация может использовать зависящие от платформы детали для ускорения их STL. Таким образом, в этом смысле вы можете получать разные результаты на разных платформах, но никогда не будет медленнее, чем явная гарантия (по модулю ошибок).

Ответ 10

Мои пять центов: Предполагается, что контейнеры Qt будут работать на разных платформах. Хотя контейнеры STL зависят от реализации STL. Вы можете получить разные результаты.

EDIT: Я не говорю, что STL "медленнее", но я указываю на эффекты различные детали реализации.
Пожалуйста, проверьте этот, а затем, возможно, этот.
И это не проблема STL. Obviosly, если у вас есть существенная разница в производительности, тогда есть проблема в коде, который использует STL.

Ответ 11

Я думаю, это зависит от того, как вы используете Qt. Если вы используете его во всем своем продукте, то, вероятно, имеет смысл использовать контейнеры Qt. Если вы используете его только для (например) части пользовательского интерфейса, лучше использовать стандартные контейнеры С++.

Ответ 12

Я придерживаюсь мнения, что STL - отличная часть программного обеспечения, однако, если я должен выполнить некоторое программирование, связанное с KDE или Qt, тогда Qt - это путь. Кроме того, это зависит от используемого вами компилятора, поскольку GCC STL работает очень хорошо, но если вам нужно использовать SUN Studio CC, то STL, скорее всего, принесет вам головные боли из-за того, что компилятор не является STL как таковой. В этом случае, поскольку компилятор заставит вашу голову болеть, просто используйте Qt, чтобы избавить вас от неприятностей. Только мои 2 цента...

Ответ 13

В QVector существует (иногда) большое ограничение. Он может выделять только int байты памяти (обратите внимание, что предел находится в байтах не в количестве элементов). Это означает, что попытка выделить смежные блоки памяти размером более ~ 2 ГБ с помощью QVector приведет к сбою. Это происходит с Qt 4 и 5. std::vector не имеет такого ограничения.

Ответ 14

Основная причина для использования контейнеров STL для меня - если вам нужен специальный распределитель, чтобы повторно использовать память в очень больших контейнерах. Предположим, например, что у вас есть QMap, который хранит 1000000 записей (пары ключ/значение). В Qt подразумевается ровно 1000000 миллионов выделений (new звонки) независимо от того, что. В STL вы всегда можете создать собственный распределитель, который внутренне распределяет всю эту память сразу и присваивает ее каждой записи при заполнении карты.

Мой совет состоит в том, чтобы использовать контейнеры STL при написании критически важных алгоритмов в бизнес-логике, а затем преобразовывать их обратно в контейнеры Qt, когда результаты готовы к отображению ваших элементов управления и форм интерфейса, если это необходимо.