Требуется ли сохранять емкость при перемещении std::vector?

Рассмотрим следующий код:

std::vector vec;
vec.reserve(500);
size_t cap = vec.capacity();

std::vector newVec = std::move(vec);
assert(cap == newVec.capacity());

Практически в любой реализации, с которой вы столкнетесь, это будет работать. Меня не волнует, что делают реализации. Я хочу знать, что стандарт требует. Будет ли перенесенный vector иметь ту же емкость, что и исходный? Или сработает утверждение?


person Nicol Bolas    schedule 29.10.2012    source источник
comment
Если бы емкость изменилась, это полностью разрушило бы мотивацию использования семантики перемещения в первую очередь, которая заключается в том, чтобы избежать ненужного копирования и перераспределения. У меня нет стандарта под рукой, поэтому я не могу дать вам правильный ответ.   -  person amaurea    schedule 30.10.2012


Ответы (2)


Глядя на стандарт, кажется, что от конструктора перемещения ничего не требуется, однако, как говорит @amaurea, это полностью противоречит цели семантики перемещения, если конструктор перемещения пытается выделить или освободить память, поэтому я ожидаю, что емкость оставаться одинаковым во всех реализациях.


23.2.1 Общие требования к контейнеру

Выражение

X u(a);
X u = a;

Утверждение/примечание до/после условия

Требуется: T должен быть CopyInsertable в X (см. ниже).
сообщение: u == a


Стандарт требует только, чтобы newVec == vec. Поскольку емкость std::vector::operator== не учитывается, newVec не обязательно должна иметь ту же емкость, что и vec.

person Peter Alexander    schedule 29.10.2012
comment
Первый абзац вашего ответа противоречит вашему предупреждению в последнем абзаце, поэтому трудно сказать, какое утверждение вы делаете более решительным. - person seh; 12.01.2013

Стандартные требования C++11 к конструктору перемещения для std::vector (Таблица 99 — Требования к контейнеру с поддержкой распределителя):

X(rv)
X u(rv)
  • перемещение конструкции распределителя не должно выходить через исключение
  • post: u должен иметь те же элементы, что и rv до этой конструкции; значение get_allocator() должно быть таким же, как значение rv.get_allocator() до этой конструкции.
  • сложность: постоянная

Здесь нет требований/гарантий по емкости. Но мы можем сделать вывод, что постоянная сложность неявно отрицает любые перераспределения. И я не вижу никакой другой логической причины для изменения емкости, кроме перераспределения. Значит будет так же.

С другой точки зрения, если вектор move-from пуст, вполне законно просто игнорировать его и конструировать по умолчанию. Это по-прежнему будет O (1), так как не требует никаких конструкций для каждого элемента. (Спасибо Николу Боласу за эту проблему).

Также реализация, возможно, может уменьшить емкость до размера, используя параметр hint функции std::allocator::allocate:

pointer allocate(size_type, allocator<void>::const_pointer hint = 0);

Использование hint не указано, но предназначено для помощи в определении местоположения, если этого требует реализация. Таким образом, какое-то сложное решение, возможно, может передавать указатель векторного хранилища как hint и использовать для него realloc для уменьшения емкости.

Вывод: похоже, что стандарт не гарантирует сохранения емкости при перемещении std::vector, объем хранилища потенциально может уменьшиться.

person Rost    schedule 29.10.2012
comment
Но мы можем сделать вывод, что постоянная сложность неявно отрицает любые перераспределения. +1 - person ildjarn; 30.10.2012
comment
Кажется, что реализация может просто уменьшить емкость до размера и по-прежнему удовлетворять всем юридическим требованиям, хотя это было бы бессмысленной тратой. - person aschepler; 30.10.2012
comment
Я мог видеть разумную реализацию, выполняющую realloc, чтобы уменьшить vector так, чтобы size() == capacity(). Постоянная сложность не исключает этого. - person Peter Alexander; 30.10.2012
comment
@PeterAlexander: Как std::allocator<> может использовать realloc с его текущим интерфейсом? - person ildjarn; 30.10.2012
comment
@ildjarn: Теоретически специализация шаблона, которая знает, что ее распределитель использует malloc внутри, может просто вызвать realloc для указателя. - person aschepler; 30.10.2012
comment
@ildjarn: Это не обязательно. В стандарте есть правило высокого уровня, которое позволяет реализации выполнять любую оптимизацию, которую она считает подходящей, пока наблюдаемое поведение остается тем же. Кроме того, что сказал Ашеплер :-P - person Peter Alexander; 30.10.2012
comment
@aschepler: Но какая часть интерфейса std::allocator<> может использовать realloc под прикрытием? - person Mooing Duck; 30.10.2012
comment
Но изначально я думал о странной реализации, которая даже не делала realloc или чего-то подобного: просто меняла векторную емкость без уважительной причины. Память по-прежнему выделена, но никогда не будет использоваться. - person aschepler; 30.10.2012
comment
Очень умная реализация могла бы даже оптимизировать весь вектор и разместить все в стеке (если позволяет ситуация). Я не думаю, что есть что-то, что запрещает это в определенных ситуациях. - person Peter Alexander; 30.10.2012
comment
@PeterAlexander: это известно как оптимизация малого буфера, и std::string Dinkumware/Microsoft делает это. - person Mooing Duck; 30.10.2012
comment
@PeterAlexander: Ты хочешь сказать, что std::allocator<T> может это сделать? Стандарт требует, чтобы аллокатор по умолчанию для std::vector<T> был std::allocator<T>, без специализаций, без настройки... - person ildjarn; 30.10.2012
comment
@Rost: Проблема здесь в том, что, поскольку vec пуст, для newVec совершенно законно просто игнорировать пустой перемещенный объект и саму конструкцию по умолчанию. Это по-прежнему будет O (1), так как не требует никаких конструкций для каждого элемента. - person Nicol Bolas; 30.10.2012
comment
@MooingDuck Не думаю, что такой трюк применим здесь, как вы можете перемещать объект автоматического хранения за постоянное время? Вы должны перемещать его по элементам, которые имеют линейную или, по крайней мере, амортизированную константную сложность. - person Rost; 30.10.2012
comment
@ildjarn: я повторяюсь здесь, но реализация может делать все, что захочет, пока наблюдаемое поведение остается прежним. - person Peter Alexander; 30.10.2012
comment
@Nicol Bolas Хорошая мысль, похоже, это не запрещено стандартом. - person Rost; 30.10.2012
comment
@PeterAlexander: выделение в стеке не будет иметь такое же наблюдаемое поведение. - person ildjarn; 30.10.2012
comment
@Rost: Перемещение автоматического хранилища всегда занимает постоянное время, поскольку оно имеет небольшой фиксированный верхний предел. Ильджарн прав, что это не относится к vector, которые содержат типы, отличные от POD. string делает это, потому что требует, чтобы содержащийся тип был POD. - person Mooing Duck; 30.10.2012
comment
@PeterAlexander Stack и другие грязные уловки здесь не применимы, для использования ::operator new(std::size_t) требуется распределитель по умолчанию. - person Rost; 30.10.2012
comment
@Мычание утки Правда? Как насчет перемещения/замены std::array? Это не постоянное, а линейное время. - person Rost; 30.10.2012
comment
@Rost: Вы правы, я не рассматривал типы, размер стека которых зависит от N. У кортежей та же проблема. - person Mooing Duck; 30.10.2012
comment
@Rost: повторю в третий раз: реализация может делать все, что захочет, пока наблюдаемое поведение остается прежним. - person Peter Alexander; 30.10.2012
comment
@Peter Александр О, наконец-то я это вижу. Но ждать! Выделение в стеке и через ::operator new(size_t) — это не одно и то же наблюдаемое поведение! - person Rost; 30.10.2012
comment
@Rost: наблюдаемое поведение new заключается в том, что он создает объект. Что касается стандарта, то здесь нет ни стека, ни кучи, ни malloc. - person Peter Alexander; 30.10.2012