Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What is the difference between unordered_map::emplace and unordered_map::insert in C++?

What is the difference between std::unordered_map::emplace and std::unordered_map::insert in C++?

like image 788
Harsh M. Shah Avatar asked Oct 19 '14 01:10

Harsh M. Shah


People also ask

What is the difference between std::map and std :: unordered_map?

std::map Internally store elements in a balanced BST. Therefore, elements will be stored in sorted order of keys. std::unordered_map store elements using hash table. Therefore, elements will not be stored in any sorted order.

What happens if we insert duplicate key in unordered_map?

Because unordered_map containers do not allow for duplicate keys, this means that the function actually returns 1 if an element with that key exists in the container, and zero otherwise.

Does unordered_map maintain insertion order?

No. It's called "unordered" for a reason. If you need to maintain an order of insertion, you've chosen an unsuitable data structure.

What is faster than unordered_map?

For the unordered_map + map , it takes 70 ms for unordered_map insertion and 80 ms for map insertion. So the hybrid implementation is 50 ms faster.


2 Answers

unordered_map::insert copies or moves a key-value pair into the container. It is overloaded to accept reference-to-const or an rvalue reference:

std::pair<iterator,bool> insert(const std::pair<const Key, T>& value);  template<class P> std::pair<iterator,bool> insert(P&& value); 

unordered_map::emplace allows you to avoid unnecessary copies or moves by constructing the element in place. It uses perfect forwarding and a variadic template to forward arguments to the constructor of the key-value pair:

template<class... Args> std::pair<iterator,bool> emplace(Args&&... args); 

But there is a great deal of overlap between the two functions. emplace can be used to forward to the copy/move constructor of the key-value pair which allows it to be used just as insert would. This means that use of emplace doesn't guarantee you will avoid copies or moves. Also the version of insert that takes an rvalue-reference is actually templated and accepts any type P such that the key-value pair is constructible from P.

Scott Meyers says:

In principle, emplacement functions should sometimes be more efficient than their insertion counterparts, and they should never be less efficient.

( Edit: Howard Hinnant ran some experiments that showed sometimes insert is faster than emplace)

If you definitely do want to copy/move into the container it might be wise to use insert because you are more likely to get a compilation error if you pass incorrect arguments. You need to be more careful you are passing the correct arguments to the emplacement functions.

Most implementations of unordered_map::emplace will cause memory to be dynamically allocated for the new pair even if the map contains an item with that key already and the emplace will fail. This means that if there is a good chance that an emplace will fail you may get better performance using insert to avoid unneccessary dynamic memory allocations.

Small example:

#include <unordered_map> #include <iostream>  int main() {   auto employee1 = std::pair<int, std::string>{1, "John Smith"};    auto employees = std::unordered_map<int, std::string>{};    employees.insert(employee1);  // copy insertion   employees.insert(std::make_pair(2, "Mary Jones"));  // move insertion    employees.emplace(3, "James Brown");  // construct in-place    for (const auto& employee : employees)     std::cout << employee.first << ": " << employee.second << "\n"; } 

Edit2: On request. It is also possible to use unordered_map::emplace with a key or value that takes more than one constructor parameter. Using the std::pair piecewise constructor you can still avoid unnecessary copies or moves.

#include <unordered_map> #include <iostream>  struct Employee {   std::string firstname;   std::string lastname;   Employee(const std::string& firstname, const std::string& lastname)    : firstname(firstname), lastname(lastname){}     };  int main() {   auto employees = std::unordered_map<int, Employee>{};   auto employee1 = std::pair<int, Employee>{1, Employee{"John", "Smith"}};    employees.insert(employee1);  // copy insertion   employees.insert(std::make_pair(2, Employee{"Mary", "Jones"}));  // move insertion   employees.emplace(3, Employee("Sam", "Thomas")); // emplace with pre-constructed Employee   employees.emplace(std::piecewise_construct,                     std::forward_as_tuple(4),                     std::forward_as_tuple("James", "Brown"));  // construct in-place } 
like image 103
Chris Drew Avatar answered Oct 06 '22 00:10

Chris Drew


The difference between emplace() and insert() has already been well explained in Chris Drew's answer. However, for the sake of completeness I'd like to add that since C++17 std::unordered_map provides two new insertion methods: try_emplace() and insert_or_assign(). Let me summarize these methods briefly:

  • try_emplace() is an "improved" version of emplace(). In contrast to emplace(), try_emplace() doesn't modify its arguments (due to move operations) if insertion fails due to a key already existing in the unordered_map.
  • insert_or_assign() is an "improved" version of operator[]. In contrast to operator[], insert_or_assign() doesn't require the value type of the unordered_map to be default constructible.

I have written a more detailed answer on the above mentioned new insertion methods for std::map here. That answer also applies to std::unordered_map.

Simple example code on Coliru

like image 20
honk Avatar answered Oct 05 '22 23:10

honk