Вопреки тому, что говорится в связанной статье, я бы сказал, что язык стандарта почти гарантирует, что этот код делает неправильную вещь: он перемещает указатель из p
, а затем уничтожает объект, на который изначально указывает p
, потому что в конце ничего не вставляется в карту m
(поскольку ключ, построенный из "foo"
, уже присутствует). [Я говорю «почти» только потому, что язык Стандарта менее ясен, чем хотелось бы; очевидно, что данный вопрос просто не приходил в голову тому, кто это написал.]
Ссылаясь на таблицу 102 в 23.2.4, запись a_uniq.emplace(args)
, эффект
Вставляет объект value_type
t
, созданный с помощью std::forward<Args>(args)...
if и только в том случае, если в контейнере нет элемента с ключом, эквивалентным ключу t
.
Здесь value_type
для случая std::map
равно std::pair<const Key, T>
, в примере с Key
равным std::string
и T
равным std::unique_ptr<Foo>
. Таким образом, объект t
, о котором идет речь, конструируется (или будет) как
std::pair<const std::string, std::unique_ptr<Foo>> t("foo", std::move(p));
а «ключ t
» является первым компонентом этой пары. Как указано в связанной статье, язык неточен из-за смешения «конструкции» и «вставки»: можно было бы истолковать, что «если и только если» относится к ним обоим, и поэтому t
ни построено, ни вставляется, если в контейнере есть элемент с ключом, эквивалентным ключу t
; тогда в этом сценарии ничего не будет перемещено из p
(из-за отсутствия конструкции) и p
не станет нулевым. Однако в таком прочтении цитируемой фразы есть логическая непоследовательность: если t
никогда не следует строить, к чему, черт возьми, может относиться «ключ t
»? Поэтому я думаю, что единственно разумное прочтение этого текста таково: объект t
(безоговорочно) конструируется, как указано, а затем t
вставляется в контейнер тогда и только тогда, когда в контейнере нет элемента с ключом, эквивалентным ключу t
. В случае, если t
не вставлен (как в примере), временное исчезнет при возврате из обращения к emplace
, уничтожая перемещенный в него ресурс по ходу.
Конечно, это не означает, что реализация не может поступать правильно: отдельно создавать первый (ключевой) компонент t
, искать этот ключ в контейнере и только если он не найден, создайте полную пару t
(в это время переместив сопоставленную объектную форму p
во второй компонент t
) и вставив ее. (Для этого требуется, чтобы тип ключа был копируемым или перемещаемым, так как то, что станет первым компонентом t
, изначально создается в другом месте.) Именно потому, что такая реализация возможна, в статье предлагается предоставить средства для надежного просить о таком поведении. Но текущий язык стандарта, похоже, не дает лицензии на такую реализацию и тем более не обязывает вести себя подобным образом.
Позвольте мне добавить, что я столкнулся с этой проблемой на практике, потому что я наивно полагал, что, имея хороший новый метод emplace
, он наверняка будет хорошо работать с семантикой перемещения. Итак, я написал что-то вроде строк:
auto p = m.emplace(key,std::move(mapped_to_value));
if (not p.second) // no insertion took place
{ /* some action with value p.first->second about to be overwritten here */
p.first->second = std::move(mapped_to_value) // replace mapped-to value
}
Оказалось, что это не так, и в моем «отображенном на» типе, который содержит как общий указатель, так и уникальный указатель, компонент общего указателя вел себя нормально, но компонент уникального указателя стал бы нулевым в случае предыдущей записи. на карте было перезаписано. Учитывая, что эта идиома не работает, я переписал ее на
auto range = m.equal_range(key);
if (range.first==range.second) // the key was previously absent; insert a pair
m.emplace_hint(range.first,key,std::move(mapped_to_value));
else // the key was present, replace the associated value
{ /* some action with value range.first->second about to be overwritten here */
range.first->second = std::move(mapped_to_value) // replace mapped-to value
}
Это разумный обходной путь, который работает без особых предположений о типе сопоставления (в частности, он не должен быть конструируемым по умолчанию или копируемым, просто конструируемым с перемещением и назначаемым с помощью перемещения).
Похоже, эта идиома должна работать даже для unordered_map
, хотя я не пробовал ее для этого случая. На самом деле, приглядевшись, он работает, но использование emplace_hint
бессмысленно, так как в отличие от случая std::map
метод std::unordered_map::equal_range
обязан при отсутствии ключа возвращать пару итераторов, оба равные к (неинформативному) значению, возвращаемому std::unordered_map::end
, а не к какой-либо другой паре равных итераторов. Действительно, кажется, что std::unordered_map::emplace_hint
, которому разрешено игнорировать подсказку, почти вынужден это сделать, так как либо ключ уже присутствует и emplace_hint
не должен ничего делать (кроме как сожрать ресурсы, возможно перемещенные в его временную пару t
), либо (такого ключа нет) нет никакого способа получить полезную подсказку, так как ни методы m.find
, ни m.equal_range
не могут возвращать ничего, кроме m.end()
при вызове с отсутствующим ключом.
person
Marc van Leeuwen
schedule
17.07.2014