Подтвердить что ты не робот

Как реализован С++ std::vector?

Я много использовал std::vector, и недавно я задал себе этот вопрос: "Как реализовано std::vector?"

У меня было две альтернативы:

1) Связанный список, а затем делает API похожим на произвольный доступ (т.е. перегрузка operator[]).

2) Используя new, например. Foo* temp = new Foo[20]: Я считаю, что они делают что-то подобное, но затем возникает еще один вопрос. Всегда ли они выделяют максимальное хранилище (uint32_t) для предоставления произвольного доступа? (Это неэффективно с точки зрения памяти.)

Или есть что-то еще, о чем я должен знать?

4b9b3361

Ответ 1

Он реализован с использованием базового массива.

Невозможно реализовать std::vector<T> со связанным списком, поскольку стандарт гарантирует, что элементы в списке будут удерживаться в непрерывной памяти,

Ответ 2

Я считаю, что это третий вариант. Он не может просто использовать new T[n], потому что тогда ему действительно нужно будет построить столько объектов, сколько они выделяют. Например

std::vector<Foo> v;
v.reserve(10);

Если ваша реализация просто закончилась тем, что сделала new Foo[10], вы просто создали 10 экземпляров Foo.

Вместо этого он использует свой распределитель для выделения и освобождения необработанной памяти (без создания объектов) и по мере необходимости (например, когда вы на самом деле push_back объекты) помещает экземпляры, созданные копированием, в правильные ячейки памяти в своем резерве, используя размещение new и удаляет их с явным вызовом деструктора (что вы делаете только в сочетании с размещением нового). Класс allocator предоставляет следующие методы для того, что я предполагаю, что векторные реализации используют

 void construct(pointer p, const_reference val);

  Returns:
    new((void *)p) T(val)

  void destroy(pointer p);

  Returns:
    ((T*)p)->~T()

( "Возврат", вероятно, должен читать "эффект" или аналогичный.)

Подробнее о размещение нового

Ответ 3

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

Кстати, одним из распространенных способов регрессии массива является удвоение размера по мере необходимости. Это так, что если вы вставляете n элементы не более, чем O(log n) выполняются повторные зарождения, и самое большее O(n) пробел теряется.

Вы можете прочитать одну реализацию для себя в SGI (где первоначально была задумана STL).

Ответ 4

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

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

Вы можете прочитать реализацию для себя, прямо там, в файле заголовка.

Я могу сказать, что никакие реализации не используют связанные списки. Они не соответствуют требованиям стандарта.

Ответ 5

Раздел 23.2.4, ¶1 стандарта требует, чтобы арифметика на указателях в векторе работала так же, как с указателями в массив.

Элементы вектора сохраняются смежно, что означает, что если v является вектор, где T - некоторый тип, отличный от bool, тогда он подчиняется идентификатор & v [n] == & v [0] + n для все 0 <= n < V.SIZE().

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

Ответ 6

В главе 11 замечательной (вводной) книги "Ускоренный С++" обсуждается педагогическая (и, следовательно, упрощенная) версия контейнера под названием "Vec". То, что они описывают, является урезанной версией std::vector, но я думаю, что все равно стоит отметить, что:

1) они реализуют свой шаблонный класс в терминах массива,

2) они обсуждают push_back в терминах трюка (упомянутого выше) выделения большего объема памяти, чем это необходимо, и возвращения к нему больше, когда они заканчиваются, и

3) они используют распределитель <T > для управления памятью. В этом контексте новый оператор недостаточно гибкий, поскольку он выделяет и инициализирует память.

Я повторяю, однако, что это не означает, что фактические реализации там просты. Но поскольку "Ускоренный С++" довольно распространен, заинтересованные могут найти в соответствующей главе односторонние векторные объекты, которые могут быть созданы, скопированы, назначены и уничтожены.

EDIT: В соответствующей заметке я только что нашел следующее сообщение в блоге Herb Sutter, в котором он комментирует предыдущую запись в блоге Andrew Koenig относительно того, следует ли беспокоиться о непрерывности векторных элементов в памяти: a href= "http://herbsutter.wordpress.com/2008/04/07/cringe-not-vectors-are-guaranteed-to-be-contiguous/" rel= "nofollow noreferrer" > Cringe not: векторы гарантированно будут смежными.

Ответ 7

Я считаю, что STL использует параметр № 2 (или что-то подобное), потому что std::vector < > гарантированно сохраняет элементы в смежной памяти.

Если вы ищете структуру памяти, которая не должна использовать непрерывную память, посмотрите на std:: deque.

Ответ 8

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

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

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

Стандартный stl-вектор всегда все вместе, но не все реализации работают так (я знаю, написав некоторые из них). Вероятно, ни один из них не является связанным списком.

Ответ 9

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

1) Элементы векторов смежны, поддерживая O (1) случайный доступ, а векторы должны быть совместимы с C-массивами. Это просто означает, что нет связанных списков.

2) Когда вы вызываете резерв, он резервирует дополнительную память. Но резерв вызывает

new T[newSize]

чтобы зарезервировать больше памяти. В противном случае он вызовет конструктор по умолчанию. Поскольку uncleben объяснял, когда резерв называется векторным классом, он просто выделяет больше неинициализированной памяти в свой распределитель (если требуется) и копирует новые объекты в эту память с использованием места размещения new (если больше памяти выделено)

3) Изначально вектор имеет некоторую емкость по умолчанию. для которого выделяется неинициализированная память при создании векторного объекта

4) push_back copy создает объект в первом доступном месте. Если требуется больше памяти, необходимо выделить так же, как резерв