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

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

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

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

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

редактировать: предполагая, что приложение уже полагается на Qt.

14 ответов


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

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

в Qt контейнеры:

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

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


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

Что было сказано...

Я рекомендую правило "когда в Риме... Делай, как римляне".--6-->

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


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

  • STL стандартизирован, не изменяется с каждой версией Qt (Qt 2 had 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 (форк QLineEdit требуются для s/QString/secqstring/). редактировать 20171220: это отсекает Qt от достижений в дизайне распределителя после C++11 и C++17, cf. например, разговор Джона Лакоса (часть 2).
  • нет 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, похоже, испытывает недостаток в фокусе разработки на данный момент.

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


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

  • зажигалка: контейнеры 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.

C++11 STL Style

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

или

C++11 стиль foreach

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

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

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

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

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

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

безопасное

обоснование, которое дает 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 указывает это, как будто это что-то значит, как будто проблема возникает de novo из этого сценария. Это не так. Итератор недействителен, и так же, как и все остальное это можно получить из нескольких непересекающихся областей, вот как это работает. На самом деле, это будет легко происходить с итераторами стиля Java в Qt, благодаря тому, что он сильно полагается на неявный общий доступ, который является антипаттерном, как документировано здесь и многие другие районах. Кажется особенно странным, что эта "оптимизация" используется в рамках, все больше и больше приближающихся к многопоточности, но это маркетинг для вы.

легче

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

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

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

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

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

В Заключение

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


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

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

контейнеры Qt используют идиому copy-on-write.


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

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


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

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

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


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

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

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


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

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


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


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


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


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

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