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

Указывает ли векторное присвоение "резерв"?

Предположим, что я пишу

std::vector<T> littleVector(1);
std::vector<T> bigVector;

bigVector.reserve(100);
bigVector = littleVector;

Знает ли стандарт, что bigVector все равно будет сохранено 100 элементов? Или я могу перераспределить память, если бы был push_back 99 элементов? Возможно, он даже меняется между реализациями STL.

Это ранее обсуждалось здесь, но никаких стандартных ссылок не было.

4b9b3361

Ответ 1

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

Мы знаем (из таблицы 28 и 23.2.1p7), что если allocator_traits<allocator_type>::propagate_on_container_copy_assignment::value true, тогда распределитель заменяется на назначение копии. Далее, из таблиц 96 и 99 мы обнаруживаем, что сложность назначения копирования является линейной, а пост-условие для операции a = t заключается в том, что a == t, т.е. (Таблица 96), что distance(a.begin(), a.end()) == distance(t.begin(), t.end()) && equal(a.begin(), a.end(), t.begin()). Из 23.2.1p7 после назначения копии, если распределитель распространяется, тогда a.get_allocator() == t.get_allocator().

Что касается векторной емкости, то 23.3.6.3 [vector.capacity] имеет:

5 - Замечания: Reallocation делает недействительными все ссылки, указатели и итераторы, ссылающиеся на элементы в последовательности. Гарантируется, что перераспределение не происходит во время вставок, которые происходят после вызова reserve() до момента, когда вставка сделает размер вектора большим, чем значение capacity().

Если мы возьмем библиотеку DR341 в качестве руководства для чтения стандарта:

Однако текст параграфа 23.3.6.3 [vector.capacity] исключает возможность уменьшения емкости вектора после вызова функции reserve(). Это приводит к недействительности идиомы, так как swap() не позволяет уменьшить емкость. [...]

DR341 был разрешен путем добавления абзацев в 23.3.6.3:

void swap(vector<T,Allocator>& x);
7 - Эффекты: Обменивает содержимое и capacity() *this с текстом x.
8 - Сложность: постоянное время.

Вывод состоит в том, что с точки зрения Библиотечного комитета операции только изменяют capacity(), если они указаны в 23.3.6.3. Назначение копии не упоминается в 23.3.6.3 и поэтому не изменяет capacity(). (Перемещение назначения имеет ту же проблему, особенно с учетом предлагаемой резолюции Библиотека DR2321.)

Ясно, что это дефект в стандарте, поскольку назначение копий, распространяющих неравные распределители, должно привести к перераспределению, что противоречит 23.3.6.3p5.

Мы можем ожидать и надеяться, что этот дефект будет разрешен в пользу:

  • нередуцированный capacity() при назначении копирования без выделения распределителя;
  • неуказанный capacity() при назначении распределения с помощью распределителя;
  • нередуцированный capacity() при назначении перемещения, не распространяющего распределитель,
  • source-container capacity() при назначении распределения, распространяющего распределитель.

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

bigVector.assign(littleVector.begin(), littleVector.end());

Ответ 2

Единственное требование для operator= для стандартных контейнеров - это то, что впоследствии src == dst, как указано в таблице 96 (в 23.2, Общие требования к контейнеру). Кроме того, в той же таблице указано значение operator ==:

distance(lhs.begin(), lhs.end()) == distance(rhs.begin(), rhs.end()) // same size
  && equal(lhs.begin(), lhs.end(), rhs.begin()) // element-wise equivalent

Обратите внимание, что это никак не связано с пропускной способностью. Также никакая другая часть стандарта не упоминает возможности за пределами общего инварианта, что capacity() >= size(). Таким образом, значение пропускной способности после назначения не определено, и контейнер может выполнять назначение независимо от того, что он хочет, до тех пор, пока сохраняются требования к распределителю.


В общем, вы обнаружите, что реализации ведут себя так, что

  • если распределители сравнивают значение equal и dst имеет достаточную емкость, он сохранит свое старое хранилище,
  • иначе он выделит достаточно места для хранения новых элементов и
  • ни в коем случае не будет заботиться о возможностях src.

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

Ответ 3

Это зависит от характеристик распределителя.

Здесь выдержка из http://en.cppreference.com/w/cpp/container/vector/operator%3D:

Если std:: allocator_traits:: propagate_on_container_copy_assignment() истинно, целевой распределитель заменяется копией источника-распределителя. Если целевые и исходные распределители не сравниваются с равными, целевой (* this) распределитель используется для освобождения памяти, тогда другой распределитель используется для выделения его перед копированием элементов (начиная с С++ 11)

В принципе, память перераспределяется с новым распределителем, если распределители несовместимы (если они не могут освободить память друг от друга.

Это не должно иметь значения между векторными реализациями, но между реализациями распределителей (что имеет смысл).