Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

is insert() necessary in a map or unordered_map?

Tags:

c++

I see a lot of examples that add items to a map or unordered_map via operator[], like so:

int main() {
    unordered_map <string, int> m;
    m["foo"] = 42;
    cout << m["foo"] << endl;
}

Is there any reason to use the insert member function instead? It would appear they both do the same thing.

like image 214
johnbakers Avatar asked Apr 03 '13 14:04

johnbakers


4 Answers

They are not.

operator[] will overwrite the value for this key, if it exists, while insert will not.

In case operator[] is used for inserting element, it is expected to be a little slower (see @MatthieuM's comment below for details), but this is not that significant here.

While std::map::insert returns std::pair< iterator, bool >, where the .second will tell you if the value is inserted or it already exists.


Regarding your comment: you cannot have 2 elements with the same key and different value. This is not a multimap.

If there's an element in the map, with the same key you're trying to insert, then:

  • operator[] will overwrite the existing value
  • std::map::insert will not do anything.* return a std::pair< iterator, bool >, where the .second will be false (saying "the new element is not inserted, as such key already exists") and the .first will point to the found element.

* I changed this thanks to the note/remark, given from @luk32; but by writing "will not do anything", I didn't mean it literally, I meant that it will not change the value of the existing element

like image 66
Kiril Kirov Avatar answered Sep 19 '22 01:09

Kiril Kirov


Using insert() can help improve performance in certain situations (more specifically for std::map since search time is O(log(n)) instead of constant amortized). Take the following common example:

std::map<int, int> stuff;

// stuff is populated, possibly large:

auto iterator = stuff.find(27);

if(stuff.end() != iterator)
{
   // subsequent "find", set to 15
   iterator->second = 15;
}
else
{
   // insert with value of 10
   stuff[27] = 10;
}

The code above resulted in effectively finding the element twice. We can make that (slightly) more efficient written like this:

// try to insert 27 -> 10
auto result = stuff.insert(std::make_pair(27, 10));

// already existed
if(false == result.second)
{
   // update to 15, already exists
   result.first->second = 15;
}

The code above only tries to find an element once, reducing algorithmic complexity. For frequent operations, this can improve performance drastically.

like image 45
Chad Avatar answered Sep 18 '22 01:09

Chad


The two are not equivalent. insert will not overwrite an existing value, and it returns a pair<iterator, bool>, where iterator is the location of the key, regardless of whether or not it already existed. The bool indicates whether or not the insert occurred.

operator[] effectively does a lower_bound on key. If the result of that operation is an iterator with the same key, it returns a reference to the value. If not, it inserts a new node with a default-constructed value, and then returns a reference to the value. This is why operator[] is a non-const member - it auto-vivifies the key-value if it doesn't exist. This may have performance implications if the value type is costly to construct.

Also note in C++11, we have an emplace method that works nearly identical to insert, except it constructs the key-value pair in-place from forwarded arguments, if an insert occurs.

like image 22
Nathan Ernst Avatar answered Sep 21 '22 01:09

Nathan Ernst


Well I disagree with Kiril's answer to a certain degree and I think it's not full so I give mine.

According to cppreference std::map::operator[] is equivalent to a certain insert() call. By this I also think he is wrong saying the value will be overwritten. It says: "Return value Reference to the mapped value of the new element if no element with key key existed. Otherwise a reference to the mapped value of the existing element is returned."

So it seems it is a convenient wrapper. The insert(), however has this advantage of being overloaded, so it provides more functionality under one name.

I give a point to Kiril, that they do seem to have a bit different functionality at first glance, however IHMO the examples he provides are not equivalent to each other.

Therefore, as an example/reason to use insert I would point out, inserting many elements at once, or using hint ( Calls 3-6 in here).

So is insert() necessary in a map or unordered_map? I would say yes. Moreover, the operator[] is not necessary as it can be emulated/implemented using insert, while the other way is impossible! It simply provides more functinality. However, writing stuff like (insert(std::make_pair(key, T())).first)->second) (after cppreference) seems cumbersome than [].

Thus, is there any reason to use the insert member function instead? I'd say for overlapping functionality, hell no.

like image 26
luk32 Avatar answered Sep 18 '22 01:09

luk32