Я выделил четыре разных способа вставки элементов в std::map
:
std::map<int, int> function;
function[0] = 42;
function.insert(std::map<int, int>::value_type(0, 42));
function.insert(std::pair<int, int>(0, 42));
function.insert(std::make_pair(0, 42));
Какой из них предпочтительный / идиоматический? (А есть ли другой способ, о котором я не подумал?)
Ответы:
Прежде всего,
operator[]
иinsert
функции-члены функционально не эквивалентны:operator[]
Будет искать для ключа, вставьте по умолчанию строится значение , если не найдено, и возвращает ссылку на который вы присвоить значение. Очевидно, что это может быть неэффективным, еслиmapped_type
может быть выгодна прямая инициализация вместо создания и назначения по умолчанию. Этот метод также делает невозможным определить, действительно ли произошла вставка или вы только перезаписали значение для ранее вставленного ключа.insert
член не будет иметь никакого эффекта, если ключ уже присутствует на карте и, хотя о нем часто забывают, возвращает значение,std::pair<iterator, bool>
которое может представлять интерес (в первую очередь, чтобы определить, действительно ли была произведена вставка).Из всех перечисленных возможностей вызова
insert
все три почти эквивалентны. Напоминаем, что давайте посмотрим наinsert
подпись в стандарте:Так чем же отличаются эти три вызова?
std::make_pair
полагается на вывод аргументов шаблона и может (и в этом случае будет ) создавать что-то другого типа, чем фактическаяvalue_type
карта, что потребует дополнительного вызоваstd::pair
конструктора шаблона для преобразования вvalue_type
(то есть: добавленияconst
вfirst_type
)std::pair<int, int>
также потребуется дополнительный вызов конструктора шаблонаstd::pair
, чтобы преобразовать параметр вvalue_type
(то есть: добавитьconst
вfirst_type
)std::map<int, int>::value_type
не оставляет абсолютно никаких сомнений, поскольку это непосредственно тип параметра, ожидаемыйinsert
функцией-членом.В конце концов, я бы избегал использования,
operator[]
когда целью является вставка, если только нет дополнительных затрат на построение по умолчанию и назначениеmapped_type
, и что меня не волнует определение того, действительно ли новый ключ вставлен. При использованииinsert
,value_type
вероятно, лучше всего построить a .источник
Начиная с C ++ 11, у вас есть два основных дополнительных параметра. Во-первых, вы можете использовать
insert()
синтаксис инициализации списка:Это функционально эквивалентно
но гораздо более лаконичный и читаемый. Как отмечали другие ответы, это имеет несколько преимуществ по сравнению с другими формами:
operator[]
Подход требует отображенный типа быть назначаемыми, это не всегда так.operator[]
подход может перезаписать существующие элементы и не дает возможности узнать, произошло ли это.insert
которые вы перечисляете, включают неявное преобразование типов, которое может замедлить ваш код.Главный недостаток в том, что эта форма требовала, чтобы ключ и значение были копируемыми, поэтому она не работала, например, с картой со
unique_ptr
значениями. Это было исправлено в стандарте, но исправление, возможно, еще не достигло вашей стандартной реализации библиотеки.Во-вторых, вы можете использовать
emplace()
метод:Это более лаконично, чем любая из форм
insert()
, отлично работает с типами, предназначенными только для перемещенияunique_ptr
, и теоретически может быть немного более эффективным (хотя приличный компилятор должен оптимизировать разницу). Единственный серьезный недостаток заключается в том, что это может немного удивить ваших читателей, посколькуemplace
методы обычно так не используются.источник
Первая версия:
может или не может вставить значение 42 в карту. Если ключ
0
существует, то он назначит ему 42, перезаписав любое значение, которое было у этого ключа. В противном случае он вставляет пару ключ / значение.Функции вставки:
с другой стороны, ничего не делайте, если ключ
0
уже существует на карте. Если ключ не существует, вставляется пара ключ / значение.Три функции вставки практически идентичны.
std::map<int, int>::value_type
являетсяtypedef
forstd::pair<const int, int>
и,std::make_pair()
очевидно, производитstd::pair<>
магию вывода через шаблон. Однако конечный результат должен быть таким же для версий 2, 3 и 4.Какой бы я использовал? Я лично предпочитаю версию 1; это лаконично и «естественно». Конечно, если его поведение перезаписи нежелательно, я бы предпочел версию 4, поскольку она требует меньше ввода, чем версии 2 и 3. Я не знаю, существует ли де-факто единственный способ вставки пар ключ / значение в
std::map
.Другой способ вставить значения в карту через один из ее конструкторов:
источник
Если вы хотите перезаписать элемент с помощью ключа 0
В противном случае:
источник
Поскольку C ++ 17
std::map
предлагает два новых метода вставки:insert_or_assign()
иtry_emplace()
, как также упоминалось в комментарии sp2danny .insert_or_assign()
По сути,
insert_or_assign()
это «улучшенная» версияoperator[]
. В отличие отoperator[]
,insert_or_assign()
не требует, чтобы тип значения карты был конструктивным по умолчанию. Например, следующий код не компилируется, потомуMyClass
что не имеет конструктора по умолчанию:Однако, если вы замените
myMap[0] = MyClass(1);
следующую строку, тогда код компилируется и вставка происходит, как задумано:Более того, как и
insert()
,insert_or_assign()
возвращаетpair<iterator, bool>
. Логическое значение -true
если произошла вставка и былоfalse
ли выполнено присвоение. Итератор указывает на элемент, который был вставлен или обновлен.try_emplace()
Подобно вышесказанному,
try_emplace()
это «улучшение»emplace()
. В отличие отemplace()
,try_emplace()
не изменяет свои аргументы, если вставка не удалась из-за ключа, уже существующего на карте. Например, следующий код пытается разместить элемент с ключом, который уже хранится на карте (см. *):Вывод (по крайней мере, для VS2017 и Coliru):
Как видите,
pMyObj
больше не указывает на исходный объект. Однако, если вы заменитеauto [it, b] = myMap2.emplace(0, std::move(pMyObj));
следующий код, результат будет выглядеть иначе, потому чтоpMyObj
останется неизменным:Вывод:
Код на Колиру
Обратите внимание: я старался, чтобы мои объяснения были как можно короче и просты, чтобы уместить их в этот ответ. Для более точного и исчерпывающего описания я рекомендую прочитать эту статью о Fluent C ++ .
источник
Я провел некоторое время для сравнения вышеупомянутых версий:
Оказывается, разница во времени между версиями вставок очень мала.
Это дает соответственно для версий (я запускал файл 3 раза, отсюда 3 последовательных разницы во времени для каждой):
2198 мс, 2078 мс, 2072 мс
2290 мс, 2037 мс, 2046 мс
2592 мс, 2278 мс, 2296 мс
2234 мс, 2031 мс, 2027 мс
Следовательно, результатами между разными версиями вставок можно пренебречь (хотя проверка гипотез не проводилась)!
map_W_3[it] = Widget(2.0);
Версия занимает около 10-15% больше времени для этого примера из - за инициализации с помощью конструктора по умолчанию для виджета.источник
Короче говоря,
[]
оператор более эффективен для обновления значений, поскольку он включает вызов конструктора по умолчанию для типа значения и последующее присвоение ему нового значения, в то время какinsert()
он более эффективен для добавления значений.Цитируемый отрывок из книги «Эффективный STL: 50 конкретных способов улучшить использование стандартной библиотеки шаблонов » Скотта Мейерса, пункт 24 может помочь.
Вы можете выбрать версию без общего программирования, но дело в том, что я считаю эту парадигму (разграничение «добавления» и «обновления») чрезвычайно полезной.
источник
Если вы хотите вставить элемент в std :: map - используйте функцию insert (), а если вы хотите найти элемент (по ключу) и назначить ему какой-то - используйте оператор [].
Для упрощения вставки используйте библиотеку boost :: assign, например:
источник
Я просто немного изменил задачу (карту строк), чтобы показать другой интерес вставки:
тот факт, что компилятор не показывает ошибки при "rancking [1] = 42;" может иметь разрушительные последствия!
источник
std::string::operator=(char)
существует, но они показывают ошибку для последнего, потому что конструкторstd::string::string(char)
не существует. Это не должно вызывать ошибки, потому что C ++ всегда свободно интерпретирует любой литерал целочисленного стиля какchar
, так что это не ошибка компилятора, а ошибка программиста. По сути, я просто говорю, что независимо от того, приведет ли это к ошибке в вашем коде, вы должны сами следить за собой. Кстати, вы можете распечатать,rancking[0]
и компилятор, использующий ASCII, выведет*
, что есть(char)(42)
.