Повторное использование перемещенного контейнера?

85

Как правильно повторно использовать перемещенный контейнер?

std::vector<int> container;
container.push_back(1);
auto container2 = std::move(container);

// ver1: Do nothing
//container2.clear(); // ver2: "Reset"
container = std::vector<int>() // ver3: Reinitialize

container.push_back(2);
assert(container.size() == 1 && container.front() == 2);

Из того, что я прочитал в стандартном проекте C ++ 0x; ver3 кажется правильным, поскольку объект после перемещения находится в

«Если не указано иное, такие перемещенные объекты должны быть переведены в допустимое, но неуказанное состояние».

Я ни разу не нашел ни одного случая, когда это было бы "иначе указано".

Хотя я нахожу ver3 немного окольным, и я бы предпочел ver1, хотя vec3 может допускать некоторую дополнительную оптимизацию, но, с другой стороны, может легко привести к ошибкам.

Верно ли мое предположение?

Ронаг
источник
4
Вы можете просто вызвать clear, поскольку он не имеет предварительных условий (и, следовательно, не зависит от состояния объекта).
Никол Болас
@Nicol: Допустим, была std::vectorреализация, в которой хранился указатель на ее размер (кажется глупым, но законным). Переход от этого вектора может оставить указатель NULL, после чего clearпроизойдет сбой. operator=также может потерпеть неудачу.
Ben Voigt
10
@Ben: Я думаю, это нарушит «действительную» часть «действительного, но неуказанного».
ildjarn
1
@ildjarn: Я думал, это просто означает, что запускать деструктор безопасно.
Ben Voigt
Думаю, вопрос в том, что такое «действительный»?
ronag 06

Ответы:

98

Из раздела 17.3.26 спецификации «действительное, но неуказанное состояние»:

состояние объекта, которое не указано, за исключением того, что инварианты объекта выполняются, и операции с объектом ведут себя так, как указано для его типа [Пример: если объект xтипа std::vector<int>находится в допустимом, но неуказанном состоянии, он x.empty()может вызываться безоговорочно и x.front()может быть вызван только если x.empty()возвращает false. —Конечный пример]

Следовательно, объект живой. Вы можете выполнить любую операцию, которая не требует предварительного условия (если вы предварительно не проверите предварительное условие).

clear, например, не имеет предварительных условий. И он вернет объект в известное состояние. Так что просто очистите его и используйте как обычно.

Никол Болас
источник
Где в стандарте я могу прочитать о «предварительных условиях», например, для методов std :: vector?
ronag
1
@ronag: §23.2 содержит таблицы, в которых они перечислены.
Grizzly
2
Я нашел следующее, что интересно: open-std.org/jtc1/sc22/wg21/docs/papers/2011/n3241.html , они пишут: «контейнеры могут быть« пустее, чем пустые »».
ronag 06
4
@ronag: 1) Если контейнер находится в допустимом состоянии, то вызов clearдействителен. 2) Пока контейнер находился в неуказанном состоянии, вызов clearпереводит контейнер в заданное состояние, потому что в стандарте предусмотрены постусловия (§23.2.3, таблица 100). std::vector<T>имеет инвариант класса, который push_back()всегда действителен (пока Tесть CopyInsertable).
ildjarn
3
@ronag: open-std.org/jtc1/sc22/wg21/docs/papers/2011/n3241.html цитировал один из комментариев национального органа по поводу цитаты «пустее, чем пусто». Комментарий национального органа был неверным. N3241 не предлагал такое состояние. Если реализация std :: container действительно имеет состояние «пустее, чем пусто», возникающее в результате перемещения, то это состояние должно быть допустимым состоянием (т.е. вы можете делать с этим объектом все, что не требует предварительных условий).
Ховард Хиннант,
11

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

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

Следовательно, и ver2, и ver3 должны быть в порядке.

Гризли
источник
Вектор всегда будет пустым, но это не относится к общему случаю (массив IE)
Mooing Duck
"Вектор всегда будет пустым", на чем вы это основываете?
ronag 06
1
@ronag: Я, конечно, имел в виду версии 2 и 3 (как должно быть ясно из текста, исправлена ​​опечатка
Grizzly
Интересно, что предварительные условия для front()указаны только для std::array, и даже не в таблице.
Ben Voigt
1
@Ben: §23.2.3 таблица 100 говорит, что операционная семантика front()are *a.begin(), §23.2.1 / 6 говорит: « Если контейнер пуст, тоbegin() == end() », а §24.2.1 / 5 говорит: « Библиотека никогда не предполагает, что прошлое- конечные значения можно разыменовать. ". Следовательно, я думаю, что предварительные условия для этого front()можно сделать вывод, хотя, безусловно, можно было бы сделать более ясным.
ildjarn 07
-8

Я не думаю, что с перемещенным объектом можно НИЧЕГО сделать (кроме как уничтожить его).

Разве вы не можете использовать swapвместо этого, чтобы получить все преимущества перемещения, но оставить контейнер в известном состоянии?

Бен Фойгт
источник
+1. swap - хорошая идея, хотя она не будет работать во всех случаях, например, использование auto не будет работать. Может быть, идея safe_move, которая использует подкачку внутри, может быть идеей?
ronag 06
5
Это живой объект, и вы можете использовать любые функции, у которых нет предварительных условий (кроме инвариантов)
Mooing Duck
Основной шаблон для std::swapимеет 2 назначения перемещения, при этом целевые значения этих назначений перемещаются из значений. Для меня это
считается «деланием