Чому я вважаю за краще використовувати вектор замість deque


86

Оскільки

  1. вони обидва є суміжними контейнерами пам'яті;
  2. особливо мудро, деке має майже все, що має вектор, але більше, оскільки його ефективніше вставляти спереду.

Чому хтось вважає std::vectorза краще std::deque?


2
Реалізація Visual C ++ std::dequeмає дуже малий максимальний розмір блоку (~ 16 байт, якщо я правильно пам'ятаю; можливо, 32), і як така не працює дуже ефективно для реалістичних додатків. deque<T>, Де sizeof(T) > 8(або 16? Це невелике число) має приблизно ту ж характеристику продуктивності в якості vector<T*>, де кожен елемент виділяється динамічно. Інші реалізації мають різні максимальні розміри блоків, тому писати код, який має відносно однакові характеристики продуктивності на різних платформах, важко deque.
James McNellis

12
Deque не є контейнером безперервної пам'яті.
Мохамед Ель-Накіб

@ravil Ні, це дублікат, який вказує на це питання.

1
важко повірити, що питання з невиправленою кричущою фактичною помилкою сиділо при рівновазі 34 голоси
underscore_d

2
@underscore_d, тому це питання. Інша історія, якщо це була відповідь;)
Асимілатер

Відповіді:


115

Елементи в dequeє НЕ суміжними в пам'яті; vectorелементи гарантовано будуть. Отже, якщо вам потрібно взаємодіяти з простою бібліотекою C, яка потребує суміжних масивів, або якщо ви (багато) дбаєте про просторову місцевість, то, можливо, ви віддасте перевагу vector. Крім того, оскільки існує додаткове ведення бухгалтерії, інші операційні послуги, ймовірно, (трохи) дорожчі, ніж їх еквівалентні vectorоперації. З іншого боку, використання багатьох / великих екземплярів vectorможе призвести до непотрібної фрагментації купи (уповільнення викликів до new).

Крім того, як зазначалося в інших місцях на StackOverflow , тут є більше обговорень: http://www.gotw.ca/gotw/054.htm .


3
Здається, що посилання на "деінде" вже мертве (через помірність?).
esilk

37

Щоб знати різницю, слід знати, як dequeце взагалі реалізується. Пам'ять виділяється в блоки однакових розмірів, і вони зв'язані між собою (як масив або, можливо, вектор).

Отже, щоб знайти n-й елемент, ви знайдете відповідний блок, а потім отримаєте доступ до елемента всередині нього. Це постійний час, оскільки це завжди рівно 2 пошуки, але це все одно більше, ніж вектор.

vectorтакож добре працює з API, які хочуть суміжний буфер, оскільки вони є або API C, або є більш універсальними, оскільки можуть приймати покажчик і довжину. (Таким чином, ви можете мати вектор знизу або звичайний масив і викликати API з вашого блоку пам'яті).

Де dequeє його найбільші переваги:

  1. При вирощуванні або скороченні колекції з обох кінців
  2. Коли ви маєте справу з дуже великими розмірами колекції.
  3. Коли ви маєте справу з булами, вам дуже потрібні були, а не бітсет.

Другий з них менш відомий, але для дуже великих розмірів колекції:

  1. Вартість перерозподілу велика
  2. Накладні витрати на пошук суміжного блоку пам'яті є обмежувальними, тому ви можете швидше закінчити пам’ять.

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

Сказавши все це, ви можете зіткнутися з проблемами std::dequeв системах, які використовують "оптимістичний" розподіл пам'яті. Хоча спроби запитати великий розмір буфера для перерозподілу vectorзаповіту в певний момент отримають відмову з a bad_alloc, оптимістичний характер розподільника, швидше за все, завжди задовольняє запит на менший буфер, запитуваний a, dequeщо, ймовірно, може спричинити операційна система, щоб убити процес, щоб спробувати отримати трохи пам'яті. Який би з них він не вибрав, може бути не надто приємним.

Вирішення проблем у такому випадку - це встановлення прапорів системного рівня, щоб замінити оптимістичне розподіл (не завжди можливо), або управління пам’яттю дещо ручніше, наприклад, за допомогою власного розподільника, який перевіряє використання пам’яті тощо. Очевидно, не ідеально. (Що може відповісти на ваше запитання щодо переваги вектору ...)


29

Я реалізовував як вектор, так і deque кілька разів. deque набагато складніший з точки зору реалізації. Це ускладнення перетворюється на більше коду та більш складний код. Отже, ви зазвичай бачите розмір коду, коли ви вибираєте deque замість вектора. Ви також можете відчути невелику швидкість удару, якщо ваш код використовує лише те, у чому перевершує вектор (тобто push_back).

Якщо вам потрібна черга з подвійним кінцем, deque є безперечним переможцем. Але якщо ви робите більшість своїх вставок і стирань ззаду, вектор буде безперечним переможцем. Коли ви не впевнені, оголосіть свій контейнер typedef (щоб його було легко перемикати вперед-назад) і виміряйте.


3
Питання - чи розглядав комітет можливість додавання гібриду двох (скажімо, "колоди") до C ++? (тобто двосторонній vector.) У своїй відповіді я написав імплементацію, на яку посилається нижче . Це може бути настільки ж швидким, як vectorнабагато ширше застосовним (наприклад, під час швидкої черги).
user541686

5

std::dequeне має гарантованої безперервної пам’яті - і це часто дещо повільніше для індексованого доступу. Деке зазвичай реалізується як "список вектора".


14
Я не думаю, що "список векторів" є правильним: я зрозумів, що більшість реалізацій були "вектором покажчиків на масиви", хоча це залежить від вашого визначення "списку" (я читав "список" як "пов'язаний список" , "який не відповідав би вимогам складності.)
Джеймс МакНелліс,

2

Відповідно до http://www.cplusplus.com/reference/stl/deque/ , "на відміну від векторів, декам не гарантовано мати всі його елементи в сусідніх місцях зберігання, усуваючи таким чином можливість безпечного доступу через арифметику покажчиків".

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

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


5
std::dequeне менш стандартизований, ніж std::vector. Я не вірю, що вимоги до складності std::dequeможна виконати при суміжному зберіганні.
Джеррі Коффін

1
Можливо, моє формулювання було поганим: хоча це правда, що стандартизація не є ґрунтовною, наскільки я розумію, вектори стандартизовані як суцільну послідовність, а деки - ні. Здається, це є вирішальним фактором.
patrickvacek

1
@JerryCoffin: Які вимоги до складності dequeне можуть відповідати суміжним сховищам?
user541686

1
@Mehrdad: Чесно кажучи, я не пам’ятаю, що я мав на увазі. Я недавно не розглядав цю частину стандарту, щоб почувати себе комфортно, категорично заявляючи, що мій попередній коментар був неправильним, але, дивлячись на нього прямо зараз, я не можу подумати, як він був би правильним.
Джеррі Коффін

3
@JerryCoffin: Вимоги до складності насправді тривіальні: ви можете виділити великий масив і почати штовхати свою послідовність з середини назовні (я думаю, це те, що робить реалізація Mehrdad), а потім перерозподілити, коли дійдете до кінців. Проблема цього підходу полягає в тому, що він не задовольняє жодній з вимог deque, а саме, що вставка на кінцях не може призвести до недійсності посилань на існуючі елементи. Ця вимога передбачає розрив пам'яті.
Яків Галка

0

Deque - це контейнер послідовностей, який дозволяє довільний доступ до його елементів, але не гарантовано мати суміжне сховище.


0

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

Я віддав би перевагу, std::dequeніж std::vectorу більшості випадків.


1
Питання, якщо таке можна перегнати з числа фактичних помилок та пропущених слів, було, чому хтось вважає за краще vector. Ми можемо зробити висновок, що чому б ні - це наслідок. Сказати, що ви віддаєте перевагу dequeз невідомих причин невстановленим тестам, не є відповіддю.
underscore_d

0

Ви не віддаєте перевагу вектору, аніж декому відповідно до цих результатів тесту (із джерелом).

Звичайно, ви повинні протестувати у своєму додатку / середовищі, але коротко:

  • push_back в основному однаковий для всіх
  • вставка, стирання в deque набагато швидше, ніж список, і незначно швидше, ніж вектор

Ще трохи роздумів та примітка, яку слід розглянути circular_buffer.


0

З одного боку, вектор досить часто просто швидший, ніж deque. Якщо вам насправді не потрібні всі функції deque, використовуйте вектор.

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


Насправді, при одній push_backі тій же серії pop_backоперацій deque<int>завжди принаймні на 20% швидше, ніж vector<int>у моїх тестах (gcc з O3). Я думаю, саме тому dequeце стандартний вибір для таких речей, як std::stack...
igel

-1

Зверніть увагу, що векторна пам’ять перерозподіляється в міру зростання масиву. Якщо у вас є вказівники на векторні елементи, вони стануть недійсними.

Крім того, якщо стерти елемент, ітератори стають недійсними (але не "для (авто ...)").

Редагувати: змінено "deque" на "vector"


-1: Вставка та стирання на початку або в кінці НЕ робить недійсними посилання та вказівники на інші елементи. (Хоча вставка та стирання в середині робить)
Sjoerd

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