In STL maps, is it better to use map::insert than []?
Solution 1
When you write
map[key] = value;
there's no way to tell if you replaced the value
for key
, or if you created a new key
with value
.
map::insert()
will only create:
using std::cout; using std::endl;
typedef std::map<int, std::string> MyMap;
MyMap map;
// ...
std::pair<MyMap::iterator, bool> res = map.insert(MyMap::value_type(key,value));
if ( ! res.second ) {
cout << "key " << key << " already exists "
<< " with value " << (res.first)->second << endl;
} else {
cout << "created key " << key << " with value " << value << endl;
}
For most of my apps, I usually don't care if I'm creating or replacing, so I use the easier to read map[key] = value
.
Solution 2
The two have different semantics when it comes to the key already existing in the map. So they aren't really directly comparable.
But the operator[] version requires default constructing the value, and then assigning, so if this is more expensive then copy construction, then it will be more expensive. Sometimes default construction doesn't make sense, and then it would be impossible to use the operator[] version.
Solution 3
Another thing to note with std::map
:
myMap[nonExistingKey];
will create a new entry in the map, keyed to nonExistingKey
initialized to a default value.
This scared the hell out of me the first time I saw it (while banging my head against a nastly legacy bug). Wouldn't have expected it. To me, that looks like a get operation, and I didn't expect the "side-effect." Prefer map.find()
when getting from your map.
Solution 4
If the performance hit of the default constructor isn't an issue, the please, for the love of god, go with the more readable version.
:)
Solution 5
insert
is better from the point of exception safety.
The expression map[key] = value
is actually two operations:
-
map[key]
- creating a map element with default value. -
= value
- copying the value into that element.
An exception may happen at the second step. As result the operation will be only partially done (a new element was added into map, but that element was not initialized with value
). The situation when an operation is not complete, but the system state is modified, is called the operation with "side effect".
insert
operation gives a strong guarantee, means it doesn't have side effects (https://en.wikipedia.org/wiki/Exception_safety). insert
is either completely done or it leaves the map in unmodified state.
http://www.cplusplus.com/reference/map/map/insert/:
If a single element is to be inserted, there are no changes in the container in case of exception (strong guarantee).
Related videos on Youtube
danio
Updated on August 11, 2020Comments
-
danio over 3 years
A while ago, I had a discussion with a colleague about how to insert values in STL maps. I preferred
map[key] = value;
because it feels natural and is clear to read whereas he preferredmap.insert(std::make_pair(key, value))
.I just asked him and neither of us can remember the reason why insert is better, but I am sure it was not just a style preference rather there was a technical reason such as efficiency. The SGI STL reference simply says: "Strictly speaking, this member function is unnecessary: it exists only for convenience."
Can anybody tell me that reason, or am I just dreaming that there is one?
-
danio over 15 yearsThanks for all the great responses - they've been really helpful. This is a great demo of stack overflow at its best. I was torn as to which should be the accepted answer: netjeff is more explicit about the different behaviour, Greg Rogers mentioned performance issues. Wish I could tick both.
-
einpoklum almost 10 yearsActually, with C++11, you're probably best off using map::emplace which avoids the double construction
-
Thomas Eding over 8 years@einpoklum: Actually, Scott Meyers suggests otherwise in his talk "The evolving search for effective C++".
-
einpoklum over 8 years@ThomasEding: Link? Also, IIANM, there should not be something faster than emplace, and another preference would probably be a matter of style. Although I'm certainly not an expert.
-
Thomas Eding over 8 years@einpoklum: That is the case when emplacing into newly constructed memory. But due to some standards requirements for map, there are technical reasons why emplace can be slower than insert. The talk is freely available on youtube, such as this link youtube.com/watch?v=smqT9Io_bKo @ ~38-40 min mark. For an SO link, here's stackoverflow.com/questions/26446352/…
-
einpoklum over 8 yearsI actually would argue with some of what Meyers presented, but that's beyond the scope of this comment thread and anyway, I guess I have to retract my earlier comment.
-
-
Admin over 15 yearsmake_pair may require a copy constructor - that would be worse than default one. +1 anyway.
-
josesuero over 15 yearsThe main thing is, as you said, that they have different semantics. So neither is better than the other, just use the one that does what you need.
-
Steve Jessop over 15 yearsWhy would the copy constructor be worse than the default constructor followed by assignment? If it is, then the person who wrote the class has missed something, because whatever operator= does, they should have done the same in the copy constructor.
-
Mr.Ree over 15 yearsSecond! Gotta mark this up. Too many folks trade off obtuseness for nano-second speedups. Have mercy on us poor souls that must maintain such atrocities!
-
dalle over 15 yearsShould be noted that map::insert never replaces values. And in the general case I would say that it is better to use
(res.first)->second
instead ofvalue
also in the second case. -
dalle over 15 yearsAs Greg Rogers wrote: "The two have different semantics when it comes to the key already existing in the map. So they aren't really directly comparable."
-
netjeff over 15 yearsI updated to be more clear that map::insert never replaces. I left the
else
because I think usingvalue
is clearer than than the iterator. Only if the value's type had an unusual copy ctor or op== would it be different, and that type would cause other issues using STL containers like map. -
Greg Rogers over 15 yearsSometimes default constructing is as expensive as the assignment itself. Naturally assignment and copy construction will be equivalent.
-
Stephen J almost 12 yearsThat is a decent view, although hash maps are pretty universal for this format. It might be one of those "oddities that nobody thinks is strange" just because of how widespread they use the same conventions
-
Potatoswatter over 11 yearsMultiple different people have written that? Certainly use
insert
(notinput
), as theconst_cast
will cause any previous value to be overwritten, which is very non-const. Or, don't mark the value type asconst
. (That sort of thing is usually the ultimate result ofconst_cast
, so it's almost always a red flag indicating an error somewhere else.) -
dk123 over 11 years@Potatoswatter You're right. I'm just seeing that const_cast [] is used with const map values by some people when they're sure that they won't be replacing an old value in certain bits of code; since [] itself is more readable. As I've mentioned in the final bit of my answer, I'd recommend using
insert
in cases where you want to prevent values from being overwritten. (Just changed theinput
toinsert
- thanks) -
dk123 over 11 years@Potatoswatter If I recall correctly, the main reasons people seem to be using
const_cast<T>(map[key])
were 1. [] is more readable, 2. they're confident in certain bits of code they won't be overwriting values, and 3. they don't want other bits of unknowing code overwriting their values - hence theconst value
. -
Potatoswatter over 11 yearsI've never heard of this being done; where did you see this? Writing
const_cast
seems to more than negate the extra "readability" of[]
, and that sort of confidence is almost grounds enough to fire a developer. Tricky runtime conditions are solved by bulletproof designs, not gut feelings. -
dk123 over 11 years@Potatoswatter I remember it was during one of my past jobs developing educational games. I could never get the people writing the code to change their habits. You're absolutely right and I strongly agree with you. From your comments, I've decided that this is probably more worth noting than the my original answer and hence I've updated it to reflect this. Thanks!
-
antred almost 9 yearsNow run that test again with full optimizations enabled.
-
antred almost 9 years@Arkadiy In an optimized build, the compiler will often remove many unnecessary copy constructor calls.
-
antred almost 9 yearsAlso, consider what operator [] actually does. It first searches the map for an entry that matches the specified key. If it finds one, then it overwrites that entry's value with the one specified. If it doesn't, it inserts a new entry with the specified key and value. The larger your map gets, the longer it will take operator [] to search the map. At some point, this will more than make up for an extra copy c'tor call (if that even stays in the final program after the compiler has done its optimization magic).
-
Sz. over 8 years@antred,
insert
has to do the same search, so no difference in that from[]
(because map keys are unique). -
vallentin almost 8 yearsThis is an old question and answer. But "more readable version" is a stupid reason. Because which is most readable depends on the person.
-
rbennett485 over 7 yearsNice illustration of what's going on with the printouts - but what are these 2 bits of code actually doing? Why are 3 copies of the original object necessary at all?
-
Fl0 over 7 years1. must implement assignment operator, or you will get wrong numbers in destructor 2. use std::move when constructing pair to avoid excess copy-constructing "map.insert( std::make_pair<int,Sample>( 1, std::move( sample) ) );"
-
UmNyobe about 7 yearsmore important, insert doesn't require value to be default constructible.
-
David Rodríguez - dribeas over 6 years
map.insert(std::make_pair(key,value))
should bemap.insert(MyMap::value_type(key,value))
. The type returned frommake_pair
does not match the type taken byinsert
and the current solution requires a conversion -
463035818_is_not_a_number about 6 yearsimho the fact that [] first default constructs is the main difference and this should be the accepted answer. Telling if the element was created or inserted is easily possible with [] by comparing the size before and after, but being only able to put default constructible elements in the map is a major restriction
-
463035818_is_not_a_number about 6 yearsthere is a way to tell if you inserted or just assigned with
operator[]
, just compare the size before and afterwards. Imho being able to callmap::operator[]
only for default constructible types is much more important. -
netjeff almost 6 years@DavidRodríguez-dribeas: Good suggestion, I updated the code in my answer.