211

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 preferred map.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?

honk
  • 9,137
  • 11
  • 75
  • 83
danio
  • 8,548
  • 6
  • 47
  • 55
  • 3
    Thanks 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. – danio Dec 05 '08 at 11:36
  • 7
    Actually, with C++11, you're probably best off using [map::emplace](http://en.cppreference.com/w/cpp/container/map/emplace) which avoids the double construction – einpoklum May 22 '14 at 15:34
  • @einpoklum: Actually, Scott Meyers suggests otherwise in his talk "The evolving search for effective C++". – Thomas Eding Nov 19 '15 at 21:40
  • @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. – einpoklum Nov 19 '15 at 23:10
  • 3
    @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 https://www.youtube.com/watch?v=smqT9Io_bKo @ ~38-40 min mark. For an SO link, here's http://stackoverflow.com/questions/26446352/what-is-the-difference-between-unordered-map-emplace-and-unordered-map-ins – Thomas Eding Nov 20 '15 at 00:04
  • 1
    I 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. – einpoklum Nov 20 '15 at 01:10

13 Answers13

249

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.

Benjamin Loison
  • 3,782
  • 4
  • 16
  • 33
netjeff
  • 7,968
  • 4
  • 27
  • 30
  • 17
    Should 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 of `value` also in the second case. – dalle Nov 29 '08 at 10:53
  • 1
    I updated to be more clear that map::insert never replaces. I left the `else` because I think using `value` 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. – netjeff Nov 30 '08 at 04:44
  • 1
    `map.insert(std::make_pair(key,value))` should be `map.insert(MyMap::value_type(key,value))`. The type returned from `make_pair` does not match the type taken by `insert` and the current solution requires a conversion – David Rodríguez - dribeas Jan 04 '18 at 16:14
  • 1
    there is a way to tell if you inserted or just assigned with `operator[]`, just compare the size before and afterwards. Imho being able to call `map::operator[]` only for default constructible types is much more important. – 463035818_is_not_an_ai Feb 09 '18 at 14:52
  • @DavidRodríguez-dribeas: Good suggestion, I updated the code in my answer. – netjeff Jun 13 '18 at 05:13
53

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.

Greg Rogers
  • 35,641
  • 17
  • 67
  • 94
  • 1
    make_pair may require a copy constructor - that would be worse than default one. +1 anyway. –  Nov 28 '08 at 16:04
  • 1
    The 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. – jalf Nov 28 '08 at 16:20
  • Why 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. – Steve Jessop Nov 28 '08 at 18:33
  • 1
    Sometimes default constructing is as expensive as the assignment itself. Naturally assignment and copy construction will be equivalent. – Greg Rogers Dec 02 '08 at 15:53
  • @Arkadiy In an optimized build, the compiler will often remove many unnecessary copy constructor calls. – antred Jul 17 '15 at 15:46
  • imho 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_an_ai Feb 09 '18 at 14:50
36

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.

RiaD
  • 46,822
  • 11
  • 79
  • 123
Hawkeye Parker
  • 7,817
  • 7
  • 44
  • 47
  • 3
    That 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 – Stephen J May 01 '12 at 18:51
19

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.

:)

Torlack
  • 4,395
  • 1
  • 23
  • 24
  • 5
    Second! 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! – Mr.Ree Nov 28 '08 at 18:54
  • 6
    As 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." – dalle Nov 29 '08 at 10:56
  • This is an old question and answer. But "more readable version" is a stupid reason. Because which is most readable depends on the person. – vallentin Jun 13 '16 at 15:05
14

insert is better from the point of exception safety.

The expression map[key] = value is actually two operations:

  1. map[key] - creating a map element with default value.
  2. = 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).

anton_rh
  • 8,226
  • 7
  • 45
  • 73
13

If your application is speed critical i will advice using [] operator because it creates total 3 copies of the original object out of which 2 are temporary objects and sooner or later destroyed as.

But in insert(), 4 copies of the original object are created out of which 3 are temporary objects( not necessarily "temporaries") and are destroyed.

Which means extra time for: 1. One objects memory allocation 2. One extra constructor call 3. One extra destructor call 4. One objects memory deallocation

If your objects are large, constructors are typical, destructors do a lot of resource freeing, above points count even more. Regarding readability, i think both are fair enough.

The same question came into my mind but not over readability but speed. Here is a sample code through which I came to know about the point i mentioned.

class Sample
{
    static int _noOfObjects;

    int _objectNo;
public:
    Sample() :
        _objectNo( _noOfObjects++ )
    {
        std::cout<<"Inside default constructor of object "<<_objectNo<<std::endl;
    }

    Sample( const Sample& sample) :
    _objectNo( _noOfObjects++ )
    {
        std::cout<<"Inside copy constructor of object "<<_objectNo<<std::endl;
    }

    ~Sample()
    {
        std::cout<<"Destroying object "<<_objectNo<<std::endl;
    }
};
int Sample::_noOfObjects = 0;


int main(int argc, char* argv[])
{
    Sample sample;
    std::map<int,Sample> map;

    map.insert( std::make_pair<int,Sample>( 1, sample) );
    //map[1] = sample;
    return 0;
}

Output when insert() is used Output when [] operator is used

Rampal Chaudhary
  • 707
  • 1
  • 8
  • 18
  • 5
    Now run that test again with full optimizations enabled. – antred Jul 17 '15 at 15:44
  • 2
    Also, 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). – antred Jul 17 '15 at 16:21
  • 1
    @antred, `insert` has to do the same search, so no difference in that from `[]` (because map keys are unique). – Sz. Jan 28 '16 at 22:48
  • Nice 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? – rbennett485 Aug 25 '16 at 12:46
  • 1. 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( 1, std::move( sample) ) );" – Fl0 Dec 07 '16 at 10:09
10

Now in c++11 I think that the best way to insert a pair in a STL map is:

typedef std::map<int, std::string> MyMap;
MyMap map;

auto& result = map.emplace(3,"Hello");

The result will be a pair with:

  • First element (result.first), points to the pair inserted or point to the pair with this key if the key already exist.

  • Second element (result.second), true if the insertion was correct or false it something went wrong.

PS: If you don´t case about the order you can use std::unordered_map ;)

Thanks!

GutiMac
  • 2,402
  • 1
  • 20
  • 27
9

A gotcha with map::insert() is that it won't replace a value if the key already exists in the map. I've seen C++ code written by Java programmers where they have expected insert() to behave the same way as Map.put() in Java where values are replaced.

Anthony Cramp
  • 4,495
  • 6
  • 26
  • 27
2

One note is that you can also use Boost.Assign:

using namespace std;
using namespace boost::assign; // bring 'map_list_of()' into scope

void something()
{
    map<int,int> my_map = map_list_of(1,2)(2,3)(3,4)(4,5)(5,6);
}
rlbond
  • 65,341
  • 56
  • 178
  • 228
1

This is a rather restricted case, but judging from the comments I've received I think it's worth noting.

I've seen people in the past use maps in the form of

map< const key, const val> Map;

to evade cases of accidental value overwriting, but then go ahead writing in some other bits of code:

const_cast< T >Map[]=val;

Their reason for doing this as I recall was because they were sure that in these certain bits of code they were not going to be overwriting map values; hence, going ahead with the more 'readable' method [].

I've never actually had any direct trouble from the code that was written by these people, but I strongly feel up until today that risks - however small - should not be taken when they can be easily avoided.

In cases where you're dealing with map values that absolutely must not be overwritten, use insert. Don't make exceptions merely for readability.

jamylak
  • 128,818
  • 30
  • 231
  • 230
dk123
  • 18,684
  • 20
  • 70
  • 77
  • Multiple different people have written that? *Certainly* use `insert` (not `input`), as the `const_cast` will cause any previous value to be overwritten, which is very non-const. Or, don't mark the value type as `const`. (That sort of thing is usually the ultimate result of `const_cast`, so it's almost always a red flag indicating an error somewhere else.) – Potatoswatter Dec 27 '12 at 08:45
  • @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 the `input` to `insert` - thanks) – dk123 Dec 27 '12 at 08:52
  • @Potatoswatter If I recall correctly, the main reasons people seem to be using `const_cast(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 the `const value`. – dk123 Dec 27 '12 at 09:06
  • 2
    I'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. – Potatoswatter Dec 27 '12 at 09:24
  • @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! – dk123 Dec 27 '12 at 11:36
1

Here's another example, showing that operator[] overwrites the value for the key if it exists, but .insert does not overwrite the value if it exists.

void mapTest()
{
  map<int,float> m;


  for( int i = 0 ; i  <=  2 ; i++ )
  {
    pair<map<int,float>::iterator,bool> result = m.insert( make_pair( 5, (float)i ) ) ;

    if( result.second )
      printf( "%d=>value %f successfully inserted as brand new value\n", result.first->first, result.first->second ) ;
    else
      printf( "! The map already contained %d=>value %f, nothing changed\n", result.first->first, result.first->second ) ;
  }

  puts( "All map values:" ) ;
  for( map<int,float>::iterator iter = m.begin() ; iter !=m.end() ; ++iter )
    printf( "%d=>%f\n", iter->first, iter->second ) ;

  /// now watch this.. 
  m[5]=900.f ; //using operator[] OVERWRITES map values
  puts( "All map values:" ) ;
  for( map<int,float>::iterator iter = m.begin() ; iter !=m.end() ; ++iter )
    printf( "%d=>%f\n", iter->first, iter->second ) ;

}
bobobobo
  • 64,917
  • 62
  • 258
  • 363
1

The fact that std::map insert() function doesn't overwrite value associated with the key allows us to write object enumeration code like this:

string word;
map<string, size_t> dict;
while(getline(cin, word)) {
    dict.insert(make_pair(word, dict.size()));
}

It's a pretty common problem when we need to map different non-unique objects to some id's in range 0..N. Those id's can be later used, for example, in graph algorithms. Alternative with operator[] would look less readable in my opinion:

string word;
map<string, size_t> dict;
while(getline(cin, word)) {
    size_t sz = dict.size();
    if (!dict.count(word))
        dict[word] = sz; 
} 
mechatroner
  • 1,272
  • 1
  • 17
  • 25
0

The difference between insert() and operator[] has already been well explained in the other answers. However, new insertion methods for std::map were introduced with C++11 and C++17 respectively:

Let me give a brief summary of the "new" insertion methods:

  • emplace(): When used correctly, this method can avoid unnecessary copy or move operations by constructing the element to be inserted in place. Similar to insert(), an element is only inserted if there is no element with the same key in the container.
  • insert_or_assign(): This method is an "improved" version of operator[]. Unlike operator[], insert_or_assign() doesn't require the map's value type to be default constructible. This overcomes the disadvantage mentioned e.g. in Greg Rogers' answer.
  • try_emplace(): This method is an "improved" version of emplace(). Unlike emplace(), try_emplace() doesn't modify its arguments (due to move operations) if insertion fails due to a key already existing in the map.

For more details on insert_or_assign() and try_emplace() please see my answer here.

Simple example code on Coliru

honk
  • 9,137
  • 11
  • 75
  • 83