В каком сценарии я использую определенный контейнер STL?

185

Я читал о контейнерах STL в моей книге по C ++, в частности, о STL и его контейнерах. Теперь я понимаю, что у каждого из них есть свои специфические свойства, и я близок к тому, чтобы запомнить их все ... Но я еще не понимаю, в каком сценарии используется каждый из них.

Какое объяснение? Пример кода гораздо предпочтительнее.

Дэниел Слооф
источник
Вы имеете в виду карту, vectot, набор и т. д.?
Томас Темпельманн
Даже глядя на эту диаграмму, я не могу сказать, что было бы лучше всего использовать в моей работе stackoverflow.com/questions/9329011/…
sergiol
2
@sbi: Удаление тега C ++ Faq из этого и добавление его к более новому и C ++ 11 включительно. Как эффективно выбрать контейнер стандартной библиотеки в C ++ 11?
Alok Save

Ответы:

338

Этот шпаргалка предоставляет довольно хорошее резюме различных контейнеров.

См. Блок-схему внизу в качестве руководства для использования в различных сценариях использования:

http://linuxsoftware.co.nz/containerchoice.png

Создано Дэвидом Муром и лицензировано CC BY-SA 3.0

Zdan
источник
14
Эта блок-схема золотая, хотелось бы, чтобы в c # было что-то подобное
Bruno
2
Обновленная ссылка: C ++ Containers Шпаргалка .
Билл Дверь
3
Начальная точка должна быть vectorскорее пустой, чем пустой. stackoverflow.com/questions/10699265/…
eonil
5
Теперь у вас есть unordered_mapи unordered_set(и их многовариантные варианты), которых нет в блок-схеме, но они хороши, когда вы не заботитесь о порядке, но должны найти элементы по ключу. Их поиск обычно O (1) вместо O (log n).
Aidiakapi
2
@ shuttle87, размер не только никогда не изменится, но, что более важно, размер определяется во время компиляции и никогда не изменится.
YoungJohn
188

Вот блок-схема, вдохновленная созданной мною версией Дэвида Мура (см. Выше), которая соответствует (главным образом) новому стандарту (C ++ 11). Это только мое личное взятие на него, это не бесспорный, но я полагал, что это могло бы быть полезным для этой дискуссии:

введите описание изображения здесь

Микаэль Перссон
источник
4
Можете ли вы сделать оригинал доступным? Это отличный график. Может быть, придерживаться блога или GitHub?
Кевинарпе
1
Это отличный график. Хотя может кто-нибудь объяснить мне, что подразумевается под «постоянными позициями»?
IDDQD
3
@STALKER Постоянные позиции означают, что если у вас есть указатель или итератор для элемента в контейнере, этот указатель или итератор будет оставаться действительным (и указывая на тот же элемент) независимо от того, что вы добавляете или удаляете из контейнера (до тех пор, пока он это не элемент, о котором идет речь).
Микаэль Перссон
1
Это действительно отличный график, однако я думаю, что vector (sorted)он немного не соответствует остальным. Это не другой тип контейнера, просто такой же, std::vectorно отсортированный. Что еще более важно, я не понимаю, почему нельзя использовать std::setупорядоченную итерацию, если это стандартное поведение итерации по множеству. Конечно, если в ответе речь идет о упорядоченном доступе к значениям контейнера [], тогда все в порядке, вы можете делать это только с запятой std::vector. Но в любом случае решение должно быть принято сразу после вопроса «необходим заказ»
РА
1
@ user2019840 Я хотел ограничить диаграмму стандартными контейнерами. Вместо «отсортированного вектора» должно отображаться «flat_set» (из Boost.Container ) или эквивалент (каждая основная библиотека или кодовая база имеет эквивалент flat_set, AFAIK). Но это нестандартные и довольно явные упущения в STL. И причина, по которой вы не хотите перебирать std :: set или std :: map (по крайней мере, не часто), заключается в том, что это очень неэффективно .
Микаэль Перссон,
41

Простой ответ: используйте std::vectorдля всего, если у вас нет реальной причины поступить иначе.

Когда вы обнаружите случай, когда вы думаете: «Ну и дела, здесь std::vectorне очень хорошо из-за X», иди на основе X.

Дэвид Торнли
источник
1
Однако ... будьте осторожны, чтобы не удалять / вставлять элементы при итерации ... используйте const_iterator настолько, насколько это возможно, чтобы избежать этого ..
vrdhn
11
Хм ... Я думаю, что люди чрезмерно используют вектор. Причина в том, что случай «не работает» не будет легким, поэтому люди придерживаются наиболее часто используемого контейнера и злоупотребляют им для хранения списков, очередей, ... По моему мнению - что соответствует блок-схеме - следует выбирать контейнер, основываясь на предполагаемом использовании, вместо того, чтобы применять «кажется, что он подходит всем».
Черный,
13
@ Black Point, вектор обычно быстрее даже на операциях, которые теоретически должны работать медленнее.
Бартек Банахевич
1
@Vardhan std::remove_ifпочти всегда превосходит подход «удалить во время итерации».
fredoverflow
1
Некоторые критерии действительно помогут этому обсуждению быть менее субъективным.
Феликс Д.
11

Посмотрите на Эффективный STL Скотт Мейерс. Это хорошо объясняет, как использовать STL.

Если вы хотите сохранить определенное / неопределенное количество объектов и никогда не собираетесь их удалять, тогда вам нужен вектор. Это замена по умолчанию для массива C, и он работает как один, но не переполняется. Вы также можете заранее установить его размер с помощью Reserve ().

Если вы хотите хранить неопределенное количество объектов, но вы будете добавлять и удалять их, то вам, вероятно, нужен список ... потому что вы можете удалить элемент, не перемещая следующие элементы - в отличие от вектора. Однако он занимает больше памяти, чем вектор, и вы не можете последовательно получить доступ к элементу.

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

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

Это не в STL, но в обновлении TR1 для STL: если у вас есть много пар ключ-значение, которые вы собираетесь искать по ключу, и вам не важен их порядок, вы можете хочу использовать хеш - это tr1 :: unordered_map. Я использовал его с Visual C ++ 7.1, где он назывался stdext :: hash_map. Он имеет поиск O (1) вместо поиска O (log n) для map.

Марк Креницкий
источник
Я слышал пару анекдотов, которые говорят, что Microsoft hash_mapне очень хорошая реализация. Я надеюсь, что они сделали лучше unordered_map.
Марк Рэнсом
3
Из списков - «вы не можете последовательно получить доступ к элементу». - Я думаю, что вы имеете в виду, что вы не можете произвольного доступа или индексации непосредственно к элементу ....
Тони Делрой
^ Да, потому что последовательный доступ это именно то, что listделает. Скорее явная ошибка там.
underscore_d
7

Я переработал блок-схему, чтобы иметь 3 свойства:

  1. Я думаю, что контейнеры STL разделены на 2 основных класса. Основные контейнеры и те используют основные контейнеры для реализации политики.
  2. Сначала блок-схема должна разделить процесс принятия решения на основные ситуации, в которых мы должны принять решение, а затем подробно остановиться на каждом конкретном случае.
  3. Некоторые расширенные контейнеры имеют возможность выбора другого базового контейнера в качестве своего внутреннего контейнера. Блок-схема должна учитывать ситуации, в которых может использоваться каждый из основных контейнеров.

Блок-схема: введите описание изображения здесь

Более подробная информация предоставлена ​​по этой ссылке .

Ebrahim
источник
5

Важны лишь кратко упоминается до сих пор, является то , что если вам требуется непрерывная память (например , массив C дает), то вы можете использовать только vector, arrayили string.

Используйте, arrayесли размер известен во время компиляции.

Используйте, stringесли вам нужно работать только с символьными типами и вам нужна строка, а не просто контейнер общего назначения.

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

Со всеми этими тремя вы можете использовать data()функцию-член, чтобы получить указатель на первый элемент контейнера.

Джонатан Уэйкли
источник
3

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

vector: Компактная компоновка с небольшим объемом памяти или без нее на каждый содержащийся объект. Эффективно перебирать. Добавить, вставить и стереть может быть дорого, особенно для сложных объектов. Дешево найти содержащийся объект по индексу, например, myVector [10]. Используйте там, где вы бы использовали массив в C. Хорошо, когда у вас много простых объектов (например, int). Не забудьте использовать reserve()перед добавлением большого количества объектов в контейнер.

listНебольшие накладные расходы памяти на каждый содержащийся объект. Эффективно перебирать. Добавить, вставить и стереть дешево. Используйте, где вы бы использовали связанный список в C.

setmultiset): Значительные накладные расходы памяти на каждый содержащийся объект. Используйте там, где вам нужно быстро выяснить, содержит ли этот контейнер данный объект, или эффективно объединить контейнеры.

mapmultimap ): Значительные накладные расходы памяти на каждый содержащийся объект. Используйте, где вы хотите хранить пары ключ-значение и быстро искать значения по ключу.

Блок-схема на шпаргалке, предложенная zdan, дает более исчерпывающее руководство.

Инфо
источник
«Небольшие накладные расходы памяти на содержащийся объект» не относится к списку. std :: list реализован в виде двусвязного списка, поэтому он поддерживает 2 указателя на каждый сохраненный объект, которым нельзя пренебрегать.
Ханна Халил
Я бы все равно считал два указателя на хранимый объект как «маленький».
Ставки
по сравнению с чем? std :: forward_list - это контейнер, для которого в основном предлагалось хранить меньше метаданных на объект (только один указатель). В то время как std :: vector содержит 0 метаданных на объект. Таким образом, 2 указателя не подлежат обсуждению по сравнению с другими контейнерами
Ханна Халил
Все зависит от размера ваших объектов. Я уже говорил, что у вектора есть «Компактная компоновка с небольшим объемом памяти или без нее на каждый содержащийся объект». Я бы все еще сказал, что список имеет небольшие накладные расходы памяти по сравнению с set и map, и немного больше накладных расходов памяти, чем vector. Я не совсем уверен, какой момент вы пытаетесь сделать TBH!
Ставки
Все контейнеры, основанные на режиме, обычно имеют значительные накладные расходы из-за динамического распределения, которое редко предоставляется бесплатно. Если, конечно, вы не используете пользовательский распределитель.
MikeMB
2

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

class CollectionOfFoo {
    Collection<Foo*> foos;
    .. delegate methods specifically 
}

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

Приходя к выбору идеальной структуры данных для работы:

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

O (1), O (LG N), O (N) и т. Д.

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

Просто, не правда ли (-:

vrdhn
источник
5
Разве не поэтому мы используем итераторы?
Платиновая Лазурь
@PlatinumAzure Даже итераторы должны быть членами typedef. Если вы изменяете тип контейнера, вам также нужно перейти и изменить все определения итераторов ... это было исправлено в c ++ 1x!
vrdhn
4
Для любопытных это исправление в C ++ 11: auto myIterator = whateverCollection.begin(); // <-- immune to changes of container type
Black
1
Было typedef Collection<Foo*> CollectionOfFoo;бы достаточно?
Крейг МакКуин,
5
Маловероятно, что вы можете позже передумать и просто делегировать другой контейнер: остерегайтесь иллюзии независимого от контейнера кода
fredoverflow
1

Я расширил фантастическую схему Микаэля Перссона . Я добавил несколько категорий контейнеров, контейнер массивов и некоторые заметки. Если вы хотите свою собственную копию, вот чертеж Google. Спасибо, Микаэль, за проделанную работу! Сборщик контейнеров C ++

Джон ДиФини
источник
1

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

Как ответил @David Thornley, std :: vector - это путь, если нет других особых потребностей. Это совет, данный создателем C ++ Бьярном Страуструпом в блоге 2014 года.

Вот ссылка на статью https://isocpp.org/blog/2014/06/stroustrup-lists

и цитата из этого,

И да, я рекомендую использовать std :: vector по умолчанию.

В комментариях пользователь @NathanOliver также предоставляет еще один хороший блог, в котором есть более конкретные измерения. https://baptiste-wicht.com/posts/2012/12/cpp-benchmark-vector-list-deque.html .

CS Pei
источник