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


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

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

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

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

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

14   169  

14 ответов:

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

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

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

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

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

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

Как говорится...

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

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

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

  • STL стандартизирован, не меняется с каждой версией Qt (Qt 2 had QList (указатель) и QValueList (на основе значений); Qt 3 had 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()... Мы также недавно отклонили a QOptional шаблон класса, ждет std::optional вместо. Аналогично для std::unique_ptr. Я надеюсь, что эта тенденция сохранится.

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

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

легче

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

Java Style:

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 style

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 используют идиому копирования при записи.

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

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

Если данные, с которыми вы работаете, в основном используются для управления пользовательским интерфейсом на основе 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, когда результаты будут готовы к отображению вашими элементами управления и формами пользовательского интерфейса, если это необходимо.