In Stl Maps, Is It Better to Use Map::Insert Than []

In STL maps, is it better to use map::insert than []?

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.

What is the preferred/idiomatic way to insert into a map?

First of all, operator[] and insert member functions are not functionally equivalent :

  • The operator[] will search for the key, insert a default constructed value if not found, and return a reference to which you assign a value. Obviously, this can be inefficient if the mapped_type can benefit from being directly initialized instead of default constructed and assigned. This method also makes it impossible to determine if an insertion has indeed taken place or if you have only overwritten the value for an previously inserted key
  • The insert member function will have no effect if the key is already present in the map and, although it is often forgotten, returns an std::pair<iterator, bool> which can be of interest (most notably to determine if insertion has actually been done).

From all the listed possibilities to call insert, all three are almost equivalent. As a reminder, let's have look at insert signature in the standard :

typedef pair<const Key, T> value_type;

/* ... */

pair<iterator, bool> insert(const value_type& x);

So how are the three calls different ?

  • std::make_pair relies on template argument deduction and could (and in this case will) produce something of a different type than the actual value_type of the map, which will require an additional call to std::pair template constructor in order to convert to value_type (ie : adding const to first_type)
  • std::pair<int, int> will also require an additional call to the template constructor of std::pair in order to convert the parameter to value_type (ie : adding const to first_type)
  • std::map<int, int>::value_type leaves absolutely no place for doubt as it is directly the parameter type expected by the insert member function.

In the end, I would avoid using operator[] when the objective is to insert, unless there is no additional cost in default-constructing and assigning the mapped_type, and that I don't care about determining if a new key has effectively inserted. When using insert, constructing a value_type is probably the way to go.

STL map insertion efficiency: [] vs. insert

Both accomplish different things.

m[key] = val;

Will insert a new key-value pair if the key doesn't exist already, or it will overwrite the old value mapped to the key if it already exists.

m.insert(make_pair(key, val));

Will only insert the pair if key doesn't exist yet, it will never overwrite the old value. So, choose accordingly to what you want to accomplish.

For the question what is more efficient: profile. :P Probably the first way I'd say though. The assignment (aka copy) is the case for both ways, so the only difference lies in construction. As we all know and should implement, a default construction should basically be a no-op, and thus be very efficient. A copy is exactly that - a copy. So in way one we get a "no-op" and a copy, and in way two we get two copies.

Edit: In the end, trust what your profiling tells you. My analysis was off like @Matthieu mentions in his comment, but that was my guessing. :)


Then, we have C++0x coming, and the double-copy on the second way will be naught, as the pair can simply be moved now. So in the end, I think it falls back on my first point: Use the right way to accomplish the thing you want to do.

Recommended way to insert elements into map

  1. insert is not a recommended way - it is one of the ways to insert into map. The difference with operator[] is that the insert can tell whether the element is inserted into the map. Also, if your class has no default constructor, you are forced to use insert.
  2. operator[] needs the default constructor because the map checks if the element exists. If it doesn't then it creates one using default constructor and returns a reference (or const reference to it).

Because map containers do not allow for duplicate key values, the insertion operation checks for each element inserted whether another element exists already in the container with the same key value, if so, the element is not inserted and its mapped value is not changed in any way.

best way to insert std::map

cache.insert(std::make_pair(id,ps));

This will copy id and ps. If they have "heavy" copy constructors this will waste some time and memory.

cache.insert(std::pair<std::string,A>(id,ps));

Its indeed analog for make_pair

if(cache.find(id) == cache.end()){
cache[id] = ps;
}

Its better to use without check: cache[id] = ps;. But, at first element insertion the "default" object of type decltype(ps) will be constructed. Then, its assigned (using operator=) to ps. If its heavy, this can be problem too. But, i think its preferred way if swap method is not available.

cache.emplace(id, ps);

This can look like zero-copy emplace, but its not. emplace receives constructor parameters, so default copy constructor will be called. Also its C++11.

I think most efficient way is

cache[id].swap(ps);

This should make minimum amount of copies, but drawback is your ps will be reset (or contain old value).

std::map difference between index and insert calls

I believe insert() will not overwrite an existing value, and the result of the operation can be checked by testing the bool value in the iterator/pair value returned

The assignment to the subscript operator [] just overwrites whatever's there (inserting an entry if there isn't one there already)

Either of the insert and [] operators can cause issues if you're not expecting that behaviour and don't accommodate for it.

Eg with insert:

std::map< int, std::string* > intMap;
std::string* s1 = new std::string;
std::string* s2 = new std::string;
intMap.insert( std::make_pair( 100, s1 ) ); // inserted
intMap.insert( std::make_pair( 100, s2 ) ); // fails, s2 not in map, could leak if not tidied up

and with [] operator:

std::map< int, std::string* > intMap;
std::string* s1 = new std::string;
std::string* s2 = new std::string;
intMap[ 100 ] = s1; // inserted
intMap[ 100 ] = s2; // inserted, s1 now dropped from map, could leak if not tidied up

I think those are correct, but haven't compiled them, so may have syntax errors

Why use `std::map::find` for checking if maps have a key?

Is there something about the latter that makes it safer, or is the
first only applicable to pointers keys and values?

Yes, std::map::operator[] performs an insertion if no key exists. And the std::map::find does not.

From cppreference.com the std::map::operator[]

Returns a reference to the value that is mapped to a key equivalent to
key, performing an insertion if such key does not already exist.

maps holding queues: using [] vs .insert

Queue's don't have keys or [] operators, so your first question can't really be answered. You insert into queue's by pushing onto the back. If there are elements there, it will go after them. You read off a queue by popping things off of the front, if there are any. You don't read or write anywhere other than that.

As for maps, like you said, insert will add a new key-value pair if it does not exist already. It will not overwrite an existing key. Find will find a value if it exists already, but will not insert it if it doesn't. And then the [] operator does both, and also allows you to change existing elements. The documentation here is very good.

One thing to be aware of is that using the map's [] operator to read from the map will also insert a default valuetype element into the map with that key, and is probably not what you would expect when first looking at it.

std::map<int, int> myMap;
if(myMap[1] == 0) //[] create's a key-value pair <1,0>
cout << "This will output";
if(myMap.size() == 1)
cout << "This too";

As for the thread safety aspect, no STL containers are thread safe based on the standard. You need to add proper locking in your code to prevent exactly what you asked about. If 2 threads tried to read and write from a queue at the same time, it will almost definitely cause an error. I would google around about writing thread safe programs for general help on how to do that.



Related Topics



Leave a reply



Submit