У якому сценарії я використовую конкретний контейнер STL?


185

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

Яке пояснення? Приклад коду набагато краще.


ти маєш на увазі карту, вектот, набір тощо?
Томас Темпельман

Навіть дивлячись на цю діаграму , я не можу сказати , що було б найкращим варіантом для використання в моєму quastion 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


14
Цей блок-схема є золотим, я б хотів, щоб у мене було щось подібне в c #
Бруно

2
Оновлене посилання: C ++ Cheat Sheet .
Білл Двері

3
Початкова точка повинна бути, vectorа не порожньою. stackoverflow.com/questions/10699265 / ...
eonil

5
Тепер у вас є , unordered_mapі unordered_set(і їх мульти варіанти) , які не в схемі , але є хорошими кирками , коли ви не дбаєте про порядок , але необхідно знайти елементи з допомогою ключа. Їх пошук зазвичай O (1) замість O (log n).
Айдіакапі

2
@ shuttle87 не тільки цей розмір ніколи не змінюватиметься, але ще важливіше, що розмір визначається під час компіляції і ніколи не змінюватиметься.
YoungJohn

188

Ось блок-схема, натхненна створеною мною версією Девіда Мура (див. Вище), яка є актуальною (здебільшого) новим стандартом (C ++ 11). Це лише мій особистий погляд на це, це не безперечно, але я вважав, що це може бути цінним для цієї дискусії:

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


4
Чи можете ви зробити оригінал доступним? Це відмінна діаграма. Можливо, дотримуйтесь блогу чи GitHub?
кевінарпе

1
Це відмінна діаграма. Хоча хтось може мені пояснити, що мається на увазі під "стійкими позиціями"?
IDDQD

3
@STALKER Постійні позиції означають, що якщо у вас є вказівник або ітератор на елемент у контейнері, цей покажчик чи ітератор залишатиметься дійсним (і вказує на той самий елемент) незалежно від того, що ви додаєте або вилучите з контейнера (доки не є предметом, про який йде мова).
Мікаел Перссон

1
Це справді чудова діаграма, однак я думаю vector (sorted), що це трохи не відповідає решті. Це не інший тип контейнера, просто такий же, std::vectorале відсортований. Ще важливіше, я не бачу, чому не можна було використовувати std::setвпорядковану ітерацію, якщо це стандартна поведінка ітерації через набір. Звичайно, якщо у відповіді йдеться про впорядкований доступ до значень контейнера через користь [], то добре, ви можете це зробити лише за допомогою соти std::vector. Але в будь-якому випадку рішення має бути прийнято відразу після того, як питання "потрібне замовлення"
RAs

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 - вектор зазвичай швидший навіть при операціях, які теоретично повинні працювати повільніше.
Bartek Banachewicz

1
@Vardhan std::remove_ifмайже завжди перевершує підхід "видалити під час ітерації".
fredoverflow

1
Деякі орієнтири дійсно допомогли б цій дискусії бути менш суб’єктивною.
Фелікс Д.

11

Подивіться на ефективну STL Скотта Майєрса. Добре пояснити, як користуватися STL.

Якщо ви хочете зберегти визначену / невизначену кількість об'єктів, і ви ніколи не збираєтесь видаляти жоден, то вектор - це те, що ви хочете. Це заміна за масивом C за замовчуванням, і він працює як один, але не переповнює. Ви можете заздалегідь встановити його розмір, а також за допомогою резерву ().

Якщо ви хочете зберегти невизначену кількість об'єктів, але ви будете додавати їх і видаляти, ви, ймовірно, хочете список ... тому що ви можете видалити елемент, не переміщуючи жодних наступних елементів - на відміну від векторних. Однак це займає більше пам'яті, ніж вектор, і ви не можете послідовно отримати доступ до елемента.

Якщо ви хочете взяти купу елементів і знайти лише унікальні значення цих елементів, це зробить читання їх у набір, і це буде сортувати їх і для вас.

Якщо у вас є багато пар ключових значень, і ви хочете сортувати їх за ключем, то карта корисна ... але вона містить лише одне значення на ключ. Якщо вам потрібно більше одного значення на ключ, ви можете мати вектор / список як своє значення на карті або використовувати мультимап.

Це не в STL, але воно є в оновленні TR1 до STL: якщо у вас є багато пар ключів-значень, які ви збираєтеся шукати за ключем, і вам не важливо їх порядок, ви можете хочете використовувати хеш - який є tr1 :: unordered_map. Я використовував його з Visual C ++ 7.1, де він називався stdext :: hash_map. Він має пошук O (1) замість пошуку O (log n) для карти.


Зараз я почув пару анекдотів, які свідчать про те, що Microsoft hash_mapне дуже вдала реалізація. Я сподіваюся, що вони зробили краще unordered_map.
Марк Рансом

3
З списків - "ви не можете послідовно отримати доступ до елемента". - Я думаю, ви маєте на увазі, що ви не можете отримати довільний доступ або індексувати безпосередньо елемент…
Тоні Делрой

^ Так, оскільки послідовний доступ - це саме те, що listробить. Там швидше очевидна помилка.
підкреслюйте_d

7

Я переробив блок-схему, щоб вона мала 3 властивості:

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

Діаграма: введіть тут опис зображення

Більше інформації надано за цим посиланням .


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 :: вектор містить 0 метаданих на об'єкт. Тож 2 покажчики не підлягають обговоренню порівняно з іншими контейнерами
Ханна Халіл

Все залежить від розміру ваших предметів. Я вже казав, що вектор має "компактну компоновку з невеликими або відсутніми накладними витратами на один об'єкт". Я б ще сказав, що у списку є невеликі накладні витрати на пам'ять у порівнянні з набором та картою, і трохи більший накладний об'єм пам'яті, ніж вектор. Я не дуже впевнений, яку точку ви намагаєтеся зробити TBH!
Ставки

Усі контейнери на основі режиму мають значні накладні витрати через динамічне розподілення, яке рідко буває безкоштовно. Якщо, звичайно, ви не використовуєте спеціальний розподільник.
MikeMB

2

Один урок, який я засвоїв, це: спробуйте перенести його в клас, оскільки зміна типу контейнера один прекрасний день може принести великі сюрпризи.

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

Це не коштує багато спереду, і економить час на налагодження, коли ви хочете зламати кожен раз, коли хтось робить операцію x на цій структурі.

Переходимо до вибору ідеальної структури даних для роботи:

Кожна структура даних забезпечує деякі операції, які можуть бути різними за часом складністю:

O (1), O (lg N), O (N) тощо.

По суті, ви повинні найкраще здогадатися, на яких операціях буде зроблено найбільше, і використовувати структуру даних, яка має цю операцію як O (1).

Просто, чи не так (-:


5
Чи не тому ми використовуємо ітератори?
Платина Лазурний

@PlatinumAzure Навіть ітератори повинні бути членами typedef .. Якщо ви зміните тип контейнера, вам також доведеться перейти та змінити всі визначення ітератора ... це все виправлено в c ++ 1x, хоча!
vrdhn

4
Для допитливих це виправлення в C ++ 11: auto myIterator = whateverCollection.begin(); // <-- immune to changes of container type
Чорний

1
Чи typedef Collection<Foo*> CollectionOfFoo;буде достатньо?
Крейг МакКвін

5
Навряд чи ви можете пізніше змінити свою думку і просто делегувати інший контейнер: Остерігайтеся ілюзії коду, незалежного від контейнера
fredoverflow


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 .

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.