289

I'm using maps for the first time and I realized that there are many ways to insert an element. You can use emplace(), operator[] or insert(), plus variants like using value_type or make_pair. While there is a lot of information about all of them and questions about particular cases, I still can't understand the big picture. So, my two questions are:

  1. What is the advantage of each one of them over the others?

  2. Was there any need for adding emplace to the standard? Is there anything that wasn't possible before without it?

Nicol Bolas
  • 449,505
  • 63
  • 781
  • 982
German Capuano
  • 5,183
  • 6
  • 23
  • 35

6 Answers6

338

In the particular case of a map the old options were only two: operator[] and insert (different flavors of insert). So I will start explaining those.

The operator[] is a find-or-add operator. It will try to find an element with the given key inside the map, and if it exists it will return a reference to the stored value. If it does not, it will create a new element inserted in place with default initialization and return a reference to it.

The insert function (in the single element flavor) takes a value_type (std::pair<const Key,Value>), it uses the key (first member) and tries to insert it. Because std::map does not allow for duplicates if there is an existing element it will not insert anything.

The first difference between the two is that operator[] needs to be able to construct a default initialized value, and it is thus unusable for value types that cannot be default initialized. The second difference between the two is what happens when there is already an element with the given key. The insert function will not modify the state of the map, but instead return an iterator to the element (and a false indicating that it was not inserted).

// assume m is std::map<int,int> already has an element with key 5 and value 0
m[5] = 10;                      // postcondition: m[5] == 10
m.insert(std::make_pair(5,15)); // m[5] is still 10

In the case of insert the argument is an object of value_type, which can be created in different ways. You can directly construct it with the appropriate type or pass any object from which the value_type can be constructed, which is where std::make_pair comes into play, as it allows for simple creation of std::pair objects, although it is probably not what you want...

The net effect of the following calls is similar:

K t; V u;
std::map<K,V> m;           // std::map<K,V>::value_type is std::pair<const K,V>

m.insert( std::pair<const K,V>(t,u) );      // 1
m.insert( std::map<K,V>::value_type(t,u) ); // 2
m.insert( std::make_pair(t,u) );            // 3

But the are not really the same... [1] and [2] are actually equivalent. In both cases the code creates a temporary object of the same type (std::pair<const K,V>) and passes it to the insert function. The insert function will create the appropriate node in the binary search tree and then copy the value_type part from the argument to the node. The advantage of using value_type is that, well, value_type always matches value_type, you cannot mistype the type of the std::pair arguments!

The difference is in [3]. The function std::make_pair is a template function that will create a std::pair. The signature is:

template <typename T, typename U>
std::pair<T,U> make_pair(T const & t, U const & u );

I have intentionally not provided the template arguments to std::make_pair, as that is the common usage. And the implication is that the template arguments are deduced from the call, in this case to be T==K,U==V, so the call to std::make_pair will return a std::pair<K,V> (note the missing const). The signature requires value_type that is close but not the same as the returned value from the call to std::make_pair. Because it is close enough it will create a temporary of the correct type and copy initialize it. That will in turn be copied to the node, creating a total of two copies.

This can be fixed by providing the template arguments:

m.insert( std::make_pair<const K,V>(t,u) );  // 4

But that is still error prone in the same way that explicitly typing the type in case [1].

Up to this point, we have different ways of calling insert that require the creation of the value_type externally and the copy of that object into the container. Alternatively you can use operator[] if the type is default constructible and assignable (intentionally focusing only in m[k]=v), and it requires the default initialization of one object and the copy of the value into that object.

In C++11, with variadic templates and perfect forwarding there is a new way of adding elements into a container by means of emplacing (creating in place). The emplace functions in the different containers do basically the same thing: instead of getting a source from which to copy into the container, the function takes the parameters that will be forwarded to the constructor of the object stored in the container.

m.emplace(t,u);               // 5

In [5], the std::pair<const K, V> is not created and passed to emplace, but rather references to the t and u object are passed to emplace that forwards them to the constructor of the value_type subobject inside the data structure. In this case no copies of the std::pair<const K,V> are done at all, which is the advantage of emplace over the C++03 alternatives. As in the case of insert it will not override the value in the map.


An interesting question that I had not thought about is how emplace can actually be implemented for a map, and that is not a simple problem in the general case.

David Rodríguez - dribeas
  • 204,818
  • 23
  • 294
  • 489
  • 19
    This is hinted in the answer, but map[]=val will overwrite the previous value if one exists. – dk123 Sep 30 '13 at 09:47
  • a more interesting question in my sense, is that it serves little purpose. Because you save the pair copy, which is good because no pair copy means no `mapped_type` isntance copy. What we want, is emplace the construction of the `mapped_type` in the pair, and emplace the pair construction in the map. Therefore, the `std::pair::emplace` function, and its forwarding support in `map::emplace` are both missing. In its current form, you still have to give a constructed mapped_type to the pair constructor which will copy it, once. its better than twice, but still no good. – v.oddou Jul 03 '14 at 03:29
  • actually I amend that comment, in C++11 there is a template pair constructor that serves the exact same purpose than emplace in the case of 1 argument construction. and some weird piecewise construct, as they call it, using tuples to forward arguments, so we can still have perfect forwarding it seems. – v.oddou Jul 09 '14 at 01:53
  • Looks like there is a performance bug of insert in unordered_map and map: [link](http://www.forwardscattering.org/post/37) – Deqing Aug 10 '16 at 00:43
  • @Deqing: Nothing new, that's been known for ages in most implementations, but it is not a bug in the standard. Some implementations need to pay the cost, others are just not being smart enough about how to do it because it complicates the code sufficiently to do the right thing, and it does not really take all the cost away. A common solution for some cases is using `operator[]` if the Value's value-initialized is not a valid entry in the map (say `map>` in most cases an empty `shared_ptr` is not a valid value) – David Rodríguez - dribeas Aug 10 '16 at 18:05
  • 4
    Might be nice to update this with info on `insert_or_assign` and `try_emplace` (both from C++17), which help fill some gaps in functionality from the existing methods. – ShadowRanger Jul 15 '19 at 19:32
18

Emplace: Takes advantage of the rvalue reference to use the actual objects that you have already created. This means that no copy or move constructor is called, good for LARGE objects! O(log(N)) time.

Insert: Has overloads for standard lvalue reference and rvalue reference, as well as iterators to lists of elements to insert, and "hints" as to the position an element belongs. The use of a "hint" iterator can bring the time insertion takes down to contant time, otherwise it is O(log(N)) time.

Operator[]: Checks to see if the object exists, and if it does, modifies the reference to this object, otherwise uses the provided key and value to call make_pair on the two objects, and then does the same work as the insert function. This is O(log(N)) time.

make_pair: Does little more than make a pair.

There was no "need" for adding emplace to the standard. In c++11 I believe the && type of reference was added. This removed the necessity for move semantics, and allowed optimization of some specific type of memory management. In particular, the rvalue reference. The overloaded insert(value_type &&) operator does not take advantage of the in_place semantics, and is therefore much less efficient. While it provides the capability of dealing with rvalue references, it ignores their key purpose, which is in place construction of objects.

MobA11y
  • 18,425
  • 3
  • 49
  • 76
  • 8
    "_There was no "need" for adding emplace to the standard."_ This is patently false. `emplace()` is simply the only way to insert an element that cannot be copied or moved. (& yes, perhaps, to most efficiently insert one whose copy and move constructors cost a lot more than construction, if such a thing exists) It also seems you got the idea wrong: it's not about "_[taking] advantage of the rvalue reference to use the actual objects that you have already created_"; no object is created yet, & you forward the `map` the arguments **it** needs to create it inside itself. You don't make the object. – underscore_d Dec 02 '18 at 21:01
  • @underscore_d Fixed in edit. –  Dec 06 '21 at 00:33
  • @Ben_LCDB Thanks for taking the time to try! But I don't agree with edits that substantially change the meaning of the post. If the author wants to fix their post, they can. I don't think it's other members' place to 'fix' it for them by changing the sentiment. Otherwise no one would ever have time to post good answers, as they'd be spending it 'fixing' all the bad ones... – underscore_d Dec 07 '21 at 09:00
  • Is it the change of order in the section that made you think it was substantially ? Nevermind ! –  Dec 07 '21 at 13:59
15

The main difference is in what constructors are/aren't called. As stated at cppreference: "Careful use of emplace allows [...] avoiding unnecessary copy or move operations." This is best explained by an example.

Say you're in main() adding Foo objects to a set<Foo> object, where Foo has copy/move constructors and a constructor Foo(int). Then the main "big picture" difference is that:

  • emplace(0) - which calls set::emplace(int && args) - forwards the given list of arguments (for emplace(0), the list consists of the single int 0) to a Foo constructor somewhere in the definition/body of the set::emplace method (e.g. there is a call like Foo(0) somewhere in that method's code). Importantly: NO Foo copy or move constructor is called.

  • insert(0) - which calls set::insert(Foo && value) - needs a Foo object as input but is given the int 0 instead, so it: (1) first creates the (temporary) Foo object Foo(0) to use as the method's argument value. Then (2) somewhere in the definition of the set::insert method, this Foo object (in value) is used as an argument to a Foo copy or move constructor call. Finally, (3) the temporary Foo object from (1) is destroyed once set::insert is finished executing.

The code below shows the "big picture idea" of how insert() differs from emplace() by tracking every constructor call and telling you info about them as they happen. Comparing the output to the code will make the difference between insert() and emplace() clear.

The code is easy but a little long so to save time, I recommend you read the summary and then take a quick look through the code (this should be enough understand the code and its output).

Summary of code:

  • The Foo class: uses static int foo_counter to keep track of the total number of Foo objects that have been constructed (or moved, copied, etc.) thus far. Each Foo object stores the (unique) value of foo_counter at the time of its creation in its local variable val. The unique object with val == 8 is called "foo8" or "Foo 8" (ditto for 1, 2, etc.). Every constructor/destructor call prints info about the call (e.g. calling Foo(11) will output "Foo(int) with val: 11").
  • main()'s body: insert()s and emplace()s Foo objects into an unordered_map<Foo,int> object umap with calls such as "umap.emplace(Foo(11), 0);" and "umap.insert({12, 0})" (0 is just some arbitrary int, it can be any value). Every line of code is printed to cout before it is executed.

Code

#include <iostream>
#include <unordered_map>
#include <utility>
using namespace std;

//Foo simply outputs what constructor is called with what value.
struct Foo {
  static int foo_counter; //Track how many Foo objects have been created.
  int val; //This Foo object was the val-th Foo object to be created.
  Foo() { val = foo_counter++;
    cout << "Foo() with val:                " << val << '\n';
  }
  Foo(int value) : val(value) { foo_counter++;
    cout << "Foo(int) with val:             " << val << '\n';
  }
  Foo(Foo& f2) { val = foo_counter++;
    cout << "Foo(Foo &) with val:           " << val
         << " \tcreated from:      \t" << f2.val << '\n';
  }
  Foo(const Foo& f2) { val = foo_counter++;
    cout << "Foo(const Foo &) with val:     " << val
         << " \tcreated from:      \t" << f2.val << '\n';
  }
  Foo(Foo&& f2) { val = foo_counter++;
    cout << "Foo(Foo&&) moving:             " << f2.val
         << " \tand changing it to:\t" << val << '\n';
  }
  ~Foo() { cout << "~Foo() destroying:             " << val << '\n'; }
  Foo& operator=(const Foo& rhs) {
    cout << "Foo& operator=(const Foo& rhs) with rhs.val: " << rhs.val
         << " \tcalled with lhs.val = \t" << val
         << " \tChanging lhs.val to: \t" << rhs.val << '\n';
    val = rhs.val; return *this;
  }
  bool operator==(const Foo &rhs) const { return val == rhs.val; }
  bool operator<(const Foo &rhs)  const { return val <  rhs.val; }
};
int Foo::foo_counter = 0;

//Create a hash function for Foo in order to use Foo with unordered_map
template<> struct std::hash<Foo> {
   size_t operator()(const Foo &f) const { return hash<int>{}(f.val); }
};

int main() {
    unordered_map<Foo, int> umap;
    int d; //Some int that will be umap's value. It is not important.

    //Print the statement to be executed and then execute it.

    cout << "\nFoo foo0, foo1, foo2, foo3;\n";
    Foo foo0, foo1, foo2, foo3;

    cout << "\numap.insert(pair<Foo, int>(foo0, d))\n";
    umap.insert(pair<Foo, int>(foo0, d));
    //Side note: equivalent to: umap.insert(make_pair(foo0, d));

    cout << "\numap.insert(move(pair<Foo, int>(foo1, d)))\n";
    umap.insert(move(pair<Foo, int>(foo1, d)));
    //Side note: equiv. to: umap.insert(make_pair(foo1, d));
    
    cout << "\npair<Foo, int> pair(foo2, d)\n";
    pair<Foo, int> pair(foo2, d);

    cout << "\numap.insert(pair)\n";
    umap.insert(pair);

    cout << "\numap.emplace(foo3, d)\n";
    umap.emplace(foo3, d);
    
    cout << "\numap.emplace(11, d)\n";
    umap.emplace(11, d);

    cout << "\numap.insert({12, d})\n";
    umap.insert({12, d});

    cout.flush();
}

Output

Foo foo0, foo1, foo2, foo3;
Foo() with val:                0
Foo() with val:                1
Foo() with val:                2
Foo() with val:                3

umap.insert(pair<Foo, int>(foo0, d))
Foo(Foo &) with val:           4    created from:       0
Foo(Foo&&) moving:             4    and changing it to: 5
~Foo() destroying:             4

umap.insert(move(pair<Foo, int>(foo1, d)))
Foo(Foo &) with val:           6    created from:       1
Foo(Foo&&) moving:             6    and changing it to: 7
~Foo() destroying:             6

pair<Foo, int> pair(foo2, d)
Foo(Foo &) with val:           8    created from:       2

umap.insert(pair)
Foo(const Foo &) with val:     9    created from:       8

umap.emplace(foo3, d)
Foo(Foo &) with val:           10   created from:       3

umap.emplace(11, d)
Foo(int) with val:             11

umap.insert({12, d})
Foo(int) with val:             12
Foo(const Foo &) with val:     13   created from:       12
~Foo() destroying:             12

~Foo() destroying:             8
~Foo() destroying:             3
~Foo() destroying:             2
~Foo() destroying:             1
~Foo() destroying:             0
~Foo() destroying:             13
~Foo() destroying:             11
~Foo() destroying:             5
~Foo() destroying:             10
~Foo() destroying:             7
~Foo() destroying:             9

The BIG picture

The main "big picture" difference between insert() and emplace() is:

Whereas using insert() almost always requires the construction or pre-existence of some Foo object in main()'s scope (followed by a copy or move), if using emplace() then any call to a Foo constructor is done entirely internally in the unordered_map (i.e. inside the scope of the emplace() method's definition). The argument(s) for the key that you pass to emplace() are directly forwarded to a Foo constructor call within unordered_map::emplace()'s definition (optional additional details: where this newly constructed object is immediately incorporated into one of unordered_map's member variables so that no destructor is called when execution leaves emplace() and no move or copy constructors are called).

The reason for the "almost" in "almost always" above is because one overload of insert() is actually equivalent to emplace(). As described in this cppreference.com page, the overload template<class P> pair<iterator, bool> insert(P&& value) (which is overload (2) of insert() on that page) is equivalent to emplace(forward<P>(value)). Since we're interested in differences, I'm going to ignore this overload and not mention this particular technicality again.

Stepping through the code

I will now go through the code and its output in detail.

  1. First, notice that an unordered_map always internally stores Foo objects (and not, say, Foo *s) as keys, which are all destroyed when the unordered_map is destroyed. Here, the unordered_map's internal keys were foos 13, 11, 5, 10, 7, and 9.
  • So technically, our unordered_map actually stores pair<const Foo, int> objects, which in turn store the Foo objects. But to understand the "big picture idea" of how emplace() differs from insert() (see highlighted box above), it's okay to temporarily imagine this pair object as being entirely passive. Once you understand this "big picture idea," it's important to then back up and understand how the use of this intermediary pair object by unordered_map introduces subtle, but important, technicalities.
  1. insert()ing each of foo0, foo1, and foo2 required 2 calls to one of Foo's copy/move constructors and 2 calls to Foo's destructor (as I now describe):

    • insert()ing each of foo0 and foo1 created a temporary object (foo4 and foo6, respectively) whose destructor was then immediately called after the insertion completed. In addition, the unordered_map's internal Foos (which are foos 5 and 7) also had their destructors called when the unordered_map was destroyed once execution reached the end of main().
    • To insert() foo2, we instead first explicitly created a non-temporary pair object (called pair), which called Foo's copy constructor on foo2 (creating foo8 as an internal member of pair). We then insert()ed this pair, which resulted in unordered_map calling the copy constructor again (on foo8) to create its own internal copy (foo9). As with foos 0 and 1, the end result was two destructor calls for this insert()ion with the only difference being that foo8's destructor was called only when we reached the end of main() rather than being called immediately after insert() finished.
  2. emplace()ing foo3 resulted in only 1 copy/move constructor call (creating foo10 internally in the unordered_map ) and only 1 call to Foo's destructor. The reason why calling umap.emplace(foo3, d) called Foo's non-const copy constructor is the following: Since we're using emplace(), the compiler knows that foo3 (a non-const Foo object) is meant to be an argument to some Foo constructor. In this case, the most fitting Foo constructor is the non-const copy constructor Foo(Foo& f2). This is why umap.emplace(foo3, d) called a copy constructor while umap.emplace(11, d) did not.

  3. For foo11, we directly passed the integer 11 to emplace(11, d) so that unordered_map would call the Foo(int) constructor while execution is within its emplace() method. Unlike in (2) and (3), we didn't even need some pre-exiting foo object to do this. Importantly, notice that only 1 call to a Foo constructor occurred (which created foo11).

  4. We then directly passed the integer 12 to insert({12, d}). Unlike with emplace(11, d) (which recall resulted in only 1 call to a Foo constructor), this call to insert({12, d}) resulted in two calls to Foo's constructor (creating foo12 and foo13).


Epilogue

Where to go from here?

a. Play around with the above source code and study documentation for insert() (e.g. here) and emplace() (e.g. here) that's found online. If you're using an IDE such as eclipse or NetBeans then you can easily get your IDE to tell you which overload of insert() or emplace() is being called (in eclipse, just keep your mouse's cursor steady over the function call for a second). Here's some more code to try out:

cout << "\numap.insert({{" << Foo::foo_counter << ", d}})\n";
umap.insert({{Foo::foo_counter, d}});
//but umap.emplace({{Foo::foo_counter, d}}); results in a compile error!

cout << "\numap.insert(pair<const Foo, int>({" << Foo::foo_counter << ", d}))\n";
umap.insert(pair<const Foo, int>({Foo::foo_counter, d}));
//The above uses Foo(int) and then Foo(const Foo &), as expected. but the
// below call uses Foo(int) and the move constructor Foo(Foo&&). 
//Do you see why?
cout << "\numap.insert(pair<Foo, int>({" << Foo::foo_counter << ", d}))\n";
umap.insert(pair<Foo, int>({Foo::foo_counter, d}));
//Not only that, but even more interesting is how the call below uses all 
// three of Foo(int) and the Foo(Foo&&) move and Foo(const Foo &) copy 
// constructors, despite the below call's only difference from the call above 
// being the additional { }.
cout << "\numap.insert({pair<Foo, int>({" << Foo::foo_counter << ", d})})\n";
umap.insert({pair<Foo, int>({Foo::foo_counter, d})});


//Pay close attention to the subtle difference in the effects of the next 
// two calls.
int cur_foo_counter = Foo::foo_counter;
cout << "\numap.insert({{cur_foo_counter, d}, {cur_foo_counter+1, d}}) where " 
  << "cur_foo_counter = " << cur_foo_counter << "\n";
umap.insert({{cur_foo_counter, d}, {cur_foo_counter+1, d}});

cout << "\numap.insert({{Foo::foo_counter, d}, {Foo::foo_counter+1, d}}) where "
  << "Foo::foo_counter = " << Foo::foo_counter << "\n";
umap.insert({{Foo::foo_counter, d}, {Foo::foo_counter+1, d}});


//umap.insert(initializer_list<pair<Foo, int>>({{Foo::foo_counter, d}}));
//The call below works fine, but the commented out line above gives a 
// compiler error. It's instructive to find out why. The two calls
// differ by a "const".
cout << "\numap.insert(initializer_list<pair<const Foo, int>>({{" << Foo::foo_counter << ", d}}))\n";
umap.insert(initializer_list<pair<const Foo, int>>({{Foo::foo_counter, d}}));

You'll soon see that which overload of the pair constructor (see reference) ends up being used by unordered_map can have an important effect on how many objects are copied, moved, created, and/or destroyed as well as when this all occurs.

b. See what happens when you use some other container class (e.g. set or unordered_multiset) instead of unordered_map.

c. Now use a Goo object (just a renamed copy of Foo) instead of an int as the range type in an unordered_map (i.e. use unordered_map<Foo, Goo> instead of unordered_map<Foo, int>) and see how many and which Goo constructors are called. (Spoiler: there is an effect but it isn't very dramatic.)

Matthew K.
  • 464
  • 5
  • 10
  • 2
    I believe its worth mentioning, say if the `Foo(int)` is changed to something like `Foo(int, int)` where there are multiple arguments on constructor, then to achieve something similar to `umap.emplace(11, d)`, we can use `std::piecewise_construct` and `std::forward_as_tuple`. So the statement would be `umap.emplace(std::piecewise_construct, std::forward_as_tuple(11, 12), std::forward_as_tuple(d)); ` – BAdhi Mar 31 '21 at 03:45
12

Apart from the optimisation opportunities and the simpler syntax, an important distinction between insertion and emplacement is that the latter allows explicit conversions. (This is across the entire standard library, not just for maps.)

Here's an example to demonstrate:

#include <vector>

struct foo
{
    explicit foo(int);
};

int main()
{
    std::vector<foo> v;

    v.emplace(v.end(), 10);      // Works
    //v.insert(v.end(), 10);     // Error, not explicit
    v.insert(v.end(), foo(10));  // Also works
}

This is admittedly a very specific detail, but when you're dealing with chains of user-defined conversions, it's worth keeping this in mind.

Kerrek SB
  • 464,522
  • 92
  • 875
  • 1,084
  • Imagine that foo required two ints in its ctor rather than one. Would you be able to use this call? `v.emplace(v.end(), 10, 10);` ...or would you now need to use: `v.emplace(v.end(), foo(10, 10) );` ? – Kaitain Dec 18 '15 at 15:50
  • 1
    I don't have access to a compiler right now, but I will assume that this means that both versions will work. Almost all examples you see for `emplace` make use of a class that takes a single parameter. IMO it would actually make the nature of emplace's variadic syntax a good deal more clear if multiple parameters were used in examples. – Kaitain Dec 18 '15 at 16:24
1

There is an additional issue that hasn't been discussed yet in the other answers, and it applies for std::map as well as for std::unordered_map, std::set, and std::unordered_set:

  • insert works with a key object, which implies it does not need to allocate a node if the key is already present in the container.

  • emplace needs to construct the key first, which, generally, requires allocating a node each time it is called.

From this perspective, emplace may be less efficient than insert if the key is already present in the container. (This may be significant for instance in multithreaded applications with thread-local dictionaries, where allocations need to be synchronized.)

Live demo: https://godbolt.org/z/ornYcTqW9. Note that with libstdc++, emplace allocates 10 times, while insert only once. With libc++, there is only one allocation with emplace as well; it seems there is some optimization that copies/moves keys*. I got the same outcome with Microsoft STL, so it actually seems that there is some missing optimization in libstdc++. However, the whole problem may not be related only to standard containers. For instance, concurrent_unordered_map from Intel/oneAPI TBB behaves the same as libstdc++ in this regard.


*Note that this optimization cannot be applied in cases where keys are both non-copyable and non-movable. In this live demo, we have 10 allocations even with emplace and libc++: https://godbolt.org/z/1b6b331qf. (Of course, with non-copyable and non-movable keys, we cannot use insert nor try_emplace, so then there is no other option.)

Daniel Langr
  • 22,196
  • 3
  • 50
  • 93
-2

In terms of functionality or output, they are both same.

For both large memory, object emplace is memory-optimized which don't use copy constructors

For simple detailed explanation https://medium.com/@sandywits/all-about-emplace-in-c-71fd15e06e44

frostcs
  • 353
  • 5
  • 10
  • Emplace is not memory-optimized only for both large memory, it's why i downvoted. –  Dec 05 '21 at 23:50