Я впервые использую карты и понял, что есть много способов вставить элемент. Вы можете использовать emplace()
, operator[]
или insert()
, плюс варианты, такие как использование value_type
или make_pair
. Хотя есть много информации обо всех из них и вопросы о конкретных случаях, я до сих пор не могу понять общую картину. Итак, два моих вопроса:
В чем преимущество каждого из них перед остальными?
Была ли необходимость в добавлении emplace к стандарту? Есть ли что-то, что было бы невозможно без этого раньше?
operator[]
основано наtry_emplace
. Стоит также упомянутьinsert_or_assign
.Ответы:
В частном случае карты старых вариантов было только два:
operator[]
иinsert
(разныеinsert
). Поэтому я начну объяснять это.operator[]
Является находкой или, добавить оператор. Он попытается найти элемент с заданным ключом внутри карты и, если он существует, вернет ссылку на сохраненное значение. Если этого не произойдет, он создаст новый элемент, вставленный на место с инициализацией по умолчанию, и вернет ссылку на него.insert
Функция (в единственном элементе аромата) занимаетvalue_type
(std::pair<const Key,Value>
), он использует ключ (first
элемент) и пытается вставить ее. Потомуstd::map
что не допускает дублирования, если есть существующий элемент, он ничего не вставит.Первое различие между ними заключается в том, что
operator[]
необходимо иметь возможность создавать инициализированное значение по умолчанию , и поэтому оно непригодно для типов значений, которые не могут быть инициализированы по умолчанию. Второе различие между ними заключается в том, что происходит, когда уже есть элемент с данным ключом.insert
Функция не будет изменять состояние карты, но вместо того, чтобы вернуть итератор к элементу (иfalse
о том , что она не была установлена).В случае
insert
аргумента это объектvalue_type
, который может быть создан по-разному. Вы можете напрямую сконструировать его с соответствующим типом или передать любой объект, из которогоvalue_type
может быть сконструирован объект , и именно здесь онstd::make_pair
вступает в игру, поскольку он позволяет легко создаватьstd::pair
объекты, хотя это, вероятно, не то, что вы хотите ...Чистый эффект от следующих вызовов аналогичен :
Но на самом деле это не одно и то же ... [1] и [2] на самом деле эквивалентны. В обоих случаях код создает временный объект того же типа (
std::pair<const K,V>
) и передает егоinsert
функции.insert
Функция создает соответствующий узел в бинарном дереве поиска , а затем скопироватьvalue_type
часть из аргумента в узел. Преимущество использованияvalue_type
в том, что, ну,value_type
всегда совпадаетvalue_type
, вы не можете неправильно набирать типstd::pair
аргументов!Разница в [3]. Функция
std::make_pair
представляет собой шаблонную функцию, которая создастstd::pair
. Подпись:Я намеренно не предоставил аргументы шаблона
std::make_pair
, так как это обычное использование. И подразумевается, что аргументы шаблона выводятся из вызова, в данном случаеT==K,U==V
, так что вызовstd::make_pair
возвратитstd::pair<K,V>
(обратите внимание на отсутствиеconst
). Для подписи требуется,value_type
чтобы она была близка, но не совпадала с возвращаемым значением из вызоваstd::make_pair
. Поскольку он достаточно близко, он создаст временный объект правильного типа и скопирует его, инициализирует. Это, в свою очередь, будет скопировано в узел, создав в общей сложности две копии.Это можно исправить, указав аргументы шаблона:
Но это все равно подвержено ошибкам так же, как и явная типизация в случае [1].
До этого момента у нас были разные способы вызова,
insert
которые требуютvalue_type
внешнего создания и копирования этого объекта в контейнер. В качестве альтернативы вы можете использовать,operator[]
если тип является конструируемым и назначаемым по умолчанию (преднамеренно фокусируясь только вm[k]=v
), и это требует инициализации по умолчанию одного объекта и копии значения в этот объект.В C ++ 11 с использованием шаблонов с переменным числом строк и совершенной пересылки существует новый способ добавления элементов в контейнер посредством размещения (создания на месте). Эти
emplace
функции в различных контейнерах делают в основном то же самое: вместо того , чтобы получить источник , из которого скопировать в контейнер, функция принимает параметры , которые будут переданы в конструктор объекта , хранящегося в контейнере.В работе [5],
std::pair<const K, V>
не создаются и передаютсяemplace
, а скорее ссылки наt
иu
объект передаются ,emplace
который пересылает их в конструкторvalue_type
подобъекта внутри структуры данных. В этом случае нет Копииstd::pair<const K,V>
не выполняются вообще, что является преимуществомemplace
над C ++ 03 альтернатив. Как и в случае сinsert
ним не будет переопределять значение на карте.Интересный вопрос, о котором я не задумывался, заключается в том, как на
emplace
самом деле его можно реализовать для карты, и это не простая проблема в общем случае.источник
mapped_type
копии. То, что мы хотим, это наложить конструкциюmapped_type
пары в и наложение пары на карту. Следовательно,std::pair::emplace
функция и ее поддержка пересылкиmap::emplace
отсутствуют. В его текущей форме вам все равно придется передать созданный mapped_type в конструктор пары, который скопирует его один раз. это лучше, чем в два раза, но все равно ничего хорошего.insert_or_assign
иtry_emplace
(оба из C ++ 17), которые помогают заполнить некоторые пробелы в функциональности существующих методов.Emplace: использует ссылку rvalue для использования реальных объектов, которые вы уже создали. Это означает, что конструктор копирования или перемещения не вызывается, что хорошо для БОЛЬШИХ объектов! Время O (log (N)).
Вставка: Имеет перегрузки для стандартной ссылки lvalue и ссылки rvalue, а также итераторы для списков вставляемых элементов и «подсказки» относительно позиции, к которой принадлежит элемент. Использование итератора с «подсказкой» может привести к тому, что время вставки сокращается до постоянного, иначе это время O (log (N)).
Оператор []: проверяет, существует ли объект, и если он существует, изменяет ссылку на этот объект, в противном случае использует предоставленные ключ и значение для вызова make_pair для двух объектов, а затем выполняет ту же работу, что и функция вставки. Это время O (log (N)).
make_pair: делает немного больше, чем создает пару.
Не было необходимости добавлять emplace в стандарт. Я полагаю, что в C ++ 11 ссылка типа && была добавлена. Это устранило необходимость в семантике перемещения и позволило оптимизировать определенный тип управления памятью. В частности, ссылка на стоимость. Перегруженный оператор вставки (value_type &&) не использует семантику in_place и поэтому намного менее эффективен. Хотя он предоставляет возможность работы с ссылками на rvalue, он игнорирует их основное назначение - создание объектов.
источник
emplace()
это просто единственный способ вставить элемент, который нельзя скопировать или переместить. (& да, возможно, наиболее эффективно вставить тот, чьи конструкторы копирования и перемещения стоят намного дороже, чем конструирование, если такая вещь существует) Также кажется, что вы ошиблись: это не о том, чтобы " воспользоваться преимуществом ссылки на rvalue" использовать реальные объекты, которые вы уже создали "; ни один объект не создан, и вы перенаправлятьmap
аргументы он должен создать его внутри себя. Вы не делаете объект.Помимо возможностей оптимизации и более простого синтаксиса, важное различие между вставкой и размещением заключается в том, что последний допускает явные преобразования. (Это относится ко всей стандартной библиотеке, а не только к картам.)
Вот пример для демонстрации:
По общему признанию, это очень специфическая деталь, но когда вы имеете дело с цепочками пользовательских преобразований, стоит помнить об этом.
источник
v.emplace(v.end(), 10, 10);
... или вам теперь нужно использоватьv.emplace(v.end(), foo(10, 10) );
:?emplace
используют класс, который принимает один параметр. ИМО, это на самом деле сделало бы природу вариабельного синтаксиса emplace более понятным, если бы в параметрах использовались несколько параметров.Следующий код может помочь вам понять «общую идею» о том, чем она
insert()
отличаетсяemplace()
:Вывод, который я получил, был:
Заметь:
An
unordered_map
всегда внутренне хранитFoo
объекты (а не, скажем,Foo *
с) как ключи, которые все уничтожены , когдаunordered_map
будет уничтожен. Здесьunordered_map
внутренними ключами были foos 13, 11, 5, 10, 7 и 9.unordered_map
хранятstd::pair<const Foo, int>
объекты, которые, в свою очередь, хранятFoo
объекты. Но чтобы понять «идею общей картины» о том, как онаemplace()
отличается отinsert()
(см. Выделенную рамку ниже), можно временно представить этотstd::pair
объект как полностью пассивный. Как только вы поймете эту «общую идею», важно сделать резервную копию и понять, как использование этого промежуточногоstd::pair
объектаunordered_map
вводит тонкие, но важные технические детали.Для вставки каждого из
foo0
,foo1
иfoo2
требуется 2 вызова одного из конструкторовFoo
копирования / перемещения и 2 вызоваFoo
деструктора (как я сейчас опишу):а. Вставка каждого из
foo0
иfoo1
создание временного объекта (foo4
иfoo6
, соответственно), чей деструктор был вызван сразу после завершения вставки. Кроме того, внутреннимFoo
s в unordered_map (которые являютсяFoo
5 и 7) также были вызваны их деструкторы, когда unordered_map был уничтожен.б. Чтобы вставить
foo2
, мы вместо этого сначала явно создали не временный объект пары (вызываемыйpair
), который вызвалFoo
конструктор копированияfoo2
(создаваяfoo8
как внутренний членpair
). Затем мыinsert()
отредактировали эту пару, что привелоunordered_map
к повторному вызову конструктора копирования (onfoo8
), чтобы создать его собственную внутреннюю функцию copy (foo9
). Как и сfoo
s 0 и 1, конечным результатом были два вызова деструктора для этой вставки, с той лишь разницей, чтоfoo8
деструктор вызывался только тогда, когда мы достигли конца,main()
а не вызывался сразу послеinsert()
завершения.В
foo3
результате применения только 1 вызова конструктора копирования / перемещения (создаваемогоfoo10
внутриunordered_map
) и только 1 вызоваFoo
деструктора. (Я вернусь к этому позже).Для
foo11
, мы сразу прошли целое число 11 , чтобыemplace(11, d)
таким образом , чтоunordered_map
бы вызватьFoo(int)
конструктор во время выполнения находится в пределах егоemplace()
методы. В отличие от (2) и (3), нам даже не требовался какой-либо предварительный выходfoo
для этого. Важно отметить, чтоFoo
произошел только 1 вызов конструктора (который был созданfoo11
).Затем мы напрямую передали целое число от 12 до
insert({12, d})
. В отличие от withemplace(11, d)
(который вызвал только один вызовFoo
конструктора), этот вызов приводит кinsert({12, d})
двум вызовамFoo
конструктора (создаваяfoo12
иfoo13
).Это показывает, в чем заключается основная разница между «большой картиной»
insert()
иemplace()
:Примечание: причина « почти » в « почти всегда » выше объясняется в I) ниже.
umap.emplace(foo3, d)
вызываемыйFoo
неконстантный конструктор копирования, заключается в следующем: поскольку мы используемemplace()
, компилятор знает, чтоfoo3
(неконстантныйFoo
объект) должен быть аргументом для какого-либоFoo
конструктора. В этом случае наиболее подходящимFoo
конструктором является неконстантный конструктор копированияFoo(Foo& f2)
. Именно поэтомуumap.emplace(foo3, d)
вызвал конструктор копирования, покаumap.emplace(11, d)
не сделал.Эпилог:
I. Обратите внимание, что одна перегрузка
insert()
фактически эквивалентнаemplace()
. Как описано на этой странице cppreference.com , перегрузкаtemplate<class P> std::pair<iterator, bool> insert(P&& value)
(которая является перегрузкой (2)insert()
на этой странице cppreference.com) эквивалентнаemplace(std::forward<P>(value))
.II. Куда пойти отсюда?
а. Поиграйте с приведенным выше исходным кодом и учебной документацией
insert()
(например, здесь ) иemplace()
(например, здесь ), которые можно найти в Интернете. Если вы используете IDE, такую как eclipse или NetBeans, вы можете легко заставить свою IDE сообщать вам, какая перегрузка вызываетсяinsert()
илиemplace()
вызывается (в eclipse просто удерживайте курсор мыши постоянным над вызовом функции на секунду). Вот еще немного кода, чтобы попробовать:Вскоре вы увидите, какая перегрузка
std::pair
конструктора (см. Ссылку ) используетсяunordered_map
может иметь важное влияние на количество копируемых, перемещаемых, создаваемых и / или уничтожаемых объектов, а также на то, когда все это происходит.б. Посмотрите, что происходит, когда вы используете другой контейнерный класс (например,
std::set
илиstd::unordered_multiset
) вместоstd::unordered_map
.с. Теперь используйте
Goo
объект (просто переименованную копиюFoo
) вместо типаint
диапазона вunordered_map
(то есть используйтеunordered_map<Foo, Goo>
вместоunordered_map<Foo, int>
) и посмотрите, сколько и какиеGoo
конструкторы вызваны. (Спойлер: эффект есть, но он не очень драматичен.)источник
С точки зрения функциональности или выхода, они оба одинаковы.
Для больших объемов памяти объект emplace оптимизирован для памяти, в котором не используются конструкторы копирования
Для простого подробного объяснения https://medium.com/@sandywits/all-about-emplace-in-c-71fd15e06e44
источник