Что происходит с базовым хранилищем при копировании/переносе вектора?

При назначении копирования std::vector выполняется перераспределение хранилища и уменьшение емкости, если размер источника меньше целевой емкости? Или гарантируется, что перераспределение/сжатие не произойдет (т.е. Всегда соблюдайте предыдущий резерв())?

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

Как и для назначения переноса, я предполагаю, что перераспределение памяти не произойдет (хотя я не смог найти соответствующую часть в стандарте), значит ли это, что значение новой емкости назначения будет точно такой же, как и исходная старая емкость? Можно ли ожидать, что v = vector<T>{}; будет иметь тот же эффект, что и vector<T>{}.swap(v);?

Я полагаю, что ответы похоронены где-то в стандарте, но я просто не смог их найти. (В случае, если что-то другое для С++ 11 и С++ 03, я хотел бы знать различные требования от обоих.)

PS: Для любого ответа вышеуказанных вопросов это то же самое для std::string (только в С++ 11, что означает непрерывное хранилище, а COW, С++ 03 не выходит из радара)?

Ответ 1

std::vector<T,A>( std::vector<T,A>&& )

это гарантировано как постоянное время (N3797 Таблица 99 X u(rv)).

Мне неизвестно, как переносить вектор произвольного размера в постоянное время, просто не перемещая указатели в буфер. Если это (нет способа) истинно, то построенный вектор должен иметь буфер, по меньшей мере, такой же, как исходный буфер. В стандарте нет формулировки, согласно которой vector должно быть эффективным: правая сторона vector может иметь свою емкость, уменьшенную до любого значения, большего или равного его size: постусловие состоит только в том, что элементы одинаковы. Теоретически это может быть даже большая емкость, если правая сторона vector имела "скрытую емкость", которую компилятор выбрал по какой-либо причине (например, фаза луны).

Ни в какой точке в стандарте N3797 нет верхней границы на capacity, размещенной на любом контейнере. Соответствующая реализация может иметь все std::vector с минимальной емкостью в 2 миллиона элементов (запрет allocator), который может быть использован для принудительной емкости 0), без операции, способной уменьшить это значение ниже 2 миллионов. (shrink_to_fit - всего лишь предложение, а std::vector<T,A>().swap(x) может создать vector из 2 миллионов возможностей и поменять его.

Так как большая часть из приведенного выше относится к отрицательной форме, все, что я могу сказать, это поиск стандарта для каждого упоминания vector, allocator, allocate и capacity. capacity никогда не описывается верхней границей в любой точке. Никаких ограничений (кроме исключения-безопасности) не производится против дополнительных вызовов allocator в пустом конструкторе std::vector (если allocator не удалось, вам может потребоваться размер 0 и емкость 0, но извлечение этого состояния в все, что не использует то же самое allocator, является сложным).


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

Для перемещения-назначения это зависит от того, как это применимо:

23.2.1 [container.requirements.general]/10

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

a = rv (aka std::vector<T,A>& operator=(std::vector<T,A>&&)) случай из таблицы 96 и таблицы 99 относятся к нам. Также не упоминается, что значения, содержащиеся в rv, уничтожаются, и что итераторы к ним недействительны. Таким образом, в соответствии с 23.2.1/10 итераторы не являются недействительными.

Это, однако, не требует перемещения буфера. Либо буфер перемещается из rhs в lhs, либо он остается неповрежденным в rhs vector. В таблице 99 упоминается этот случай неявно, когда он говорит, что элементы lhs могут быть привязаны к переходу (что является единственным способом работы std::array).

Поскольку std::array не имеет выбора, кроме как перемещать элементы, а std::vector не дает никаких дополнительных гарантий относительно перемещения буфера, буфер не нужно перемещать. Похоже, что перемещение буфера является юридической реализацией.


На практике std::vector<T,A>( std::vector<T,A> const& ) выполнит копию содержимого правой стороны в левую сторону, и в каждой проверке, которую я проверил, левая сторона capacity равна значению size в результате vector. Аналогично, std::vector<T,A>( ForwardIterator, ForwardIterator ) создаст vector, который просто подходит для ввода.

Обратите внимание, что std::vector<T,A>::operator=(std::vector<T,A>&&) остается линейным по сложности.

Ответ 2

Я не могу найти ничего в стандарте, который позволил бы присвоить вектор к одному с достаточной способностью уменьшать емкость. Если я выполнил reserve перед назначением, я гарантирован что итераторы не будут аннулированы перераспределением, если вектор никогда не будет больше объема, который я зарезервировал.

Проблема с назначением перемещения является конкретной. Там нет представляется каким-то особым случаем, позволяющим ему аннулировать итераторы (если источник не превышает емкость пункт назначения), но этот вид побеждает цель перемещения назначение. Я подозреваю, что это дефект в стандарте.

EDIT:

Для чего стоит, в таблице 96, для a = rv (где a контейнер и rv является неконстантным значением r того же типа контейнер), стандарт дает линейную сложность и говорит, что "все существующие элементы a a либо перемещаются, либо уничтожили ". Таким образом, очевидно, что цель стандарта способность не сокращаться; преимущества времени выполнения перемещение применяется только к отдельным элементам, а не к контейнеру сам.

Ответ 3

Чтобы ответить на ваш PS: По крайней мере, для std::string вы не можете предположить, что обработка строк аналогична обработке вектора символов.

COW (Copy-on-Write) не является обязательным для реализации строки, и не все реализации библиотек делают это.