Доступ к паре после перемещения ее на карту
Если я переместил пару на карту, но вставка не удалась, потому что ключ уже существует, могу ли я безопасно использовать пару после этого?
//objects available: map, pair
auto insert_pair = map.insert(std::move(pair));
if (!insert_pair.second)
{
//can I safely access pair here?
}
Это было задокументировано в стандарте?
4 ответа
Как это может показаться бессмысленным (читайте ниже), учитывая текущее состояние спецификации, вы не можете делать какие-либо предположения о состоянии аргумента после возврата вызова функции.
Чтобы понять почему, давайте сначала отметим, что insert()
функция-член определяется с точки зрения emplace()
(см. 23.4.4.4/1):
Первая форма эквивалентна
return emplace(std::forward<P>(x))
, [...]
Пост-условия emplace()
в свою очередь определены как (см. § 23.2.4, Таблица 102):
Вставляет
value_type
объектt
построен сstd::forward<Args>(args)...
тогда и только тогда, когда в контейнере нет элемента с ключом, эквивалентным ключуt
,bool
Компонент возвращаемой парыtrue
тогда и только тогда, когда вставка происходит, и компонент итератора пары указывает на элемент с ключом, эквивалентным ключуt
,
В предложении, выделенном жирным шрифтом из приведенной выше цитаты (выделено мое), говорится, что пара, которая станет элементом карты, если ключа еще нет, будет построена на основе движения непосредственно из пары rvalue, которую вы предоставляете.
Это сделало бы очень разумным вывод о том, что реализации должны сначала проверить, присутствует ли ключ, и, только если нет, переместить-построить элемент новой карты из вашей пары.
Однако "очень очень разумный" не является допустимым аргументом при работе со спецификацией формального языка. В этом случае с формальной точки зрения нет ничего, что мешало бы реализации сделать следующее:
- Первый ход - построить пару
tmp
из вашего аргумента (что означает, что вы не можете делать предположения о состоянии своего аргумента после возврата функции); - Проверьте, присутствует ли ключ на карте;
- Если нет, сделайте необходимую уборку, чтобы вставить
tmp
в контейнер.
Или даже:
- Проверьте, присутствует ли ключ на карте;
- Если так, вставьте новый элемент move-constructed из вашего аргумента;
- Если нет, переместите-создайте новый объект из вашего аргумента и ничего не делайте с ним.
Пункт 3 выше абсолютно бессмыслен, но формально не запрещен. Имейте в виду фразу:
Вставляет
value_type
объектt
построен сstd::forward<Args>(args)...
тогда и только тогда, когда в контейнере нет элемента с ключом, эквивалентным ключуt
,
Это говорит только о том, что если в контейнере нет элемента с ключом, эквивалентным ключу t
, нет объекта, созданного с помощью t
будет вставлен в карту - но, как бы глупо это ни звучало, это не говорит о том, что ни один объект вообще не должен быть построен из t
: пока он не вставлен в карту, это разрешено.
При этом, поскольку Стандарт явно не ограничивает реализации в этом отношении, вы не можете делать предположения относительно того, был ли ваш аргумент перенесен или нет. Следовательно, вы не можете делать предположения о состоянии, в котором будет находиться ваша пара, когда вызов функции вернется (согласно пункту 17.6.5.15).
Если я могу позволить себе проникнуть в личное мнение, я считаю, что это недостаток.
Я не смог найти ничего конкретного, но что касается стандарта, относящегося к типам, определенным STL (например, пара):
17.6.5.15: Если не указано иное, такие перемещенные объекты должны быть помещены в действительное, но не указанное состояние.
Без удовлетворения того "иного указанного", которое я не смог найти для "неудачного" map::insert, это означало бы, что по стандарту вы не можете использовать пару. В действительности, основываясь на чувственных реализациях, я представляю, что пара останется неизменной, но полагаться на это попадет в область неопределенного поведения, специфичного для компилятора /stl.
Из таблицы 102 в разделе 23.2.4 Ассоциативные контейнеры стандарта C++11 (черновик n3337) указано (для выражения a_uniq.insert(t)
который применяется в этом случае):
Требуется: если t является неконстантным выражением-значением, value_type должен быть MoveInsertable в X; в противном случае value_type должен быть CopyInsertable в X. Эффекты: Вставляет t тогда и только тогда, когда в контейнере нет элемента с ключом, эквивалентным ключу t. Компонент bool возвращаемой пары имеет значение true тогда и только тогда, когда вставка происходит, а компонент итератора пары указывает на элемент с ключом, эквивалентным ключу t.
Он не делает никаких заявлений о влиянии на t
если вставка не происходит (я не уверен, относится ли это к неопределенному или определяемому реализацией поведению). Мне не удалось найти какой-либо другой пункт, который давал дополнительные пояснения, поэтому кажется, что стандарт не дает однозначного ответа. Можно было бы переписать размещенный код, чтобы только позвонить insert()
если пары нет или просто используйте возвращенный итератор.
Да, вы можете использовать пару. Вставка вернет ссылку на пару, которая уже существует на карте.
Документация находится здесь: http://www.cplusplus.com/reference/map/map/insert/