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(
The following code may help you understand the "big picture idea" of how insert() differs from emplace():
#include
#include
#include
//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++;
std::cout << "Foo() with val: " << val << '\n';
}
Foo(int value) : val(value) { foo_counter++;
std::cout << "Foo(int) with val: " << val << '\n';
}
Foo(Foo& f2) { val = foo_counter++;
std::cout << "Foo(Foo &) with val: " << val
<< " \tcreated from: \t" << f2.val << '\n';
}
Foo(const Foo& f2) { val = foo_counter++;
std::cout << "Foo(const Foo &) with val: " << val
<< " \tcreated from: \t" << f2.val << '\n';
}
Foo(Foo&& f2) { val = foo_counter++;
std::cout << "Foo(Foo&&) moving: " << f2.val
<< " \tand changing it to:\t" << val << '\n';
}
~Foo() { std::cout << "~Foo() destroying: " << val << '\n'; }
Foo& operator=(const Foo& rhs) {
std::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
namespace std {
template<> struct hash {
std::size_t operator()(const Foo &f) const {
return std::hash{}(f.val);
}
};
}
int main()
{
std::unordered_map umap;
Foo foo0, foo1, foo2, foo3;
int d;
//Print the statement to be executed and then execute it.
std::cout << "\numap.insert(std::pair(foo0, d))\n";
umap.insert(std::pair(foo0, d));
//Side note: equiv. to: umap.insert(std::make_pair(foo0, d));
std::cout << "\numap.insert(std::move(std::pair(foo1, d)))\n";
umap.insert(std::move(std::pair(foo1, d)));
//Side note: equiv. to: umap.insert(std::make_pair(foo1, d));
std::cout << "\nstd::pair pair(foo2, d)\n";
std::pair pair(foo2, d);
std::cout << "\numap.insert(pair)\n";
umap.insert(pair);
std::cout << "\numap.emplace(foo3, d)\n";
umap.emplace(foo3, d);
std::cout << "\numap.emplace(11, d)\n";
umap.emplace(11, d);
std::cout << "\numap.insert({12, d})\n";
umap.insert({12, d});
std::cout.flush();
}
The output that I got was:
Foo() with val: 0
Foo() with val: 1
Foo() with val: 2
Foo() with val: 3
umap.insert(std::pair(foo0, d))
Foo(Foo &) with val: 4 created from: 0
Foo(Foo&&) moving: 4 and changing it to: 5
~Foo() destroying: 4
umap.insert(std::move(std::pair(foo1, d)))
Foo(Foo &) with val: 6 created from: 1
Foo(Foo&&) moving: 6 and changing it to: 7
~Foo() destroying: 6
std::pair 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
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.
unordered_map actually stores std::pair objects, which in turn store the Foo objects. But to understand the "big picture idea" of how emplace() differs from insert() (see highlighted box below), it's okay to temporarily imagine this std::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 std::pair object by unordered_map introduces subtle, but important, technicalities. Inserting 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):
a. Inserting 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.
b. 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 insertion 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.
Emplacing foo3 resulted in only 1 copy/move constructor call (creating foo10 internally in the unordered_map ) and only 1 call to Foo's destructor. (I'll get back to this later).
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).
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).
This shows what the main "big picture" difference between insert() and emplace() is:
Whereas using
insert()almost always requires the construction or existence of someFooobject inmain()'s scope (followed by a copy or move), if usingemplace()then any call to aFooconstructor is done entirely internally in theunordered_map(i.e. inside the scope of theemplace()method's definition). The argument(s) for the key that you pass toemplace()are directly forwarded to aFooconstructor call withinunordered_map::emplace()'s definition (optional additional details: where this newly constructed object is immediately incorporated into one ofunordered_map's member variables so that no destructor is called when execution leavesemplace()and no move or copy constructors are called).
Note: The reason for the "almost" in "almost always" above is explained in I) below.
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.Epilogue:
I. Note that one overload of (value))insert() is actually equivalent to emplace(). As described in this cppreference.com page, the overload template (which is overload (2) of insert() on this cppreference.com page) is equivalent to emplace(std::forward.
II. 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:
std::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!
std::cout << "\numap.insert(std::pair({" << Foo::foo_counter << ", d}))\n";
umap.insert(std::pair({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?
std::cout << "\numap.insert(std::pair({" << Foo::foo_counter << ", d}))\n";
umap.insert(std::pair({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 { }.
std::cout << "\numap.insert({std::pair({" << Foo::foo_counter << ", d})})\n";
umap.insert({std::pair({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;
std::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}});
std::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(std::initializer_list>({{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".
std::cout << "\numap.insert(std::initializer_list>({{" << Foo::foo_counter << ", d}}))\n";
umap.insert(std::initializer_list>({{Foo::foo_counter, d}}));
You'll soon see that which overload of the std::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. std::set or std::unordered_multiset) instead of std::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 instead of unordered_map) and see how many and which Goo constructors are called. (Spoiler: there is an effect but it isn't very dramatic.)