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- Fooconstructor somewhere in the definition/body of the- set::emplacemethod (e.g. there is a call like- Foo(0)somewhere in that method's code). Importantly: NO- Foocopy or move constructor is called.
 
- insert(0)- which calls- set::insert(Foo && value)- needs a- Fooobject as input but is given the- int- 0instead, so it: (1) first creates the (temporary)- Fooobject- Foo(0)to use as the method's argument- value. Then (2) somewhere in the definition of the- set::insertmethod, this- Fooobject (in- value) is used as an argument to a- Foocopy or move constructor call. Finally, (3) the temporary- Fooobject from (1) is destroyed once- set::insertis 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 Fooclass: usesstatic int foo_counterto keep track of the total number ofFooobjects that have been constructed (or moved, copied, etc.) thus far. EachFooobject stores the (unique) value offoo_counterat the time of its creation in its local variableval. The unique object withval==8is called "foo8" or "Foo8" (ditto for1,2, etc.). Every constructor/destructor call prints info about the call (e.g. callingFoo(11)will output "Foo(int) with val: 11").
- main()'s body:- insert()s and- emplace()s- Fooobjects into an- unordered_map<Foo,int>object- umapwith calls such as "- umap.emplace(Foo(11), 0);" and "- umap.insert({12, 0})" (- 0is just some arbitrary- int, it can be any value). Every line of code is printed to- coutbefore 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.
- First, notice that an unordered_mapalways internally storesFooobjects (and not, say,Foo *s) as keys, which are all destroyed when theunordered_mapis destroyed. Here, theunordered_map's internal keys were foos 13, 11, 5, 10, 7, and 9.
- So technically, our unordered_mapactually storespair<const Foo, int>objects, which in turn store theFooobjects. But to understand the "big picture idea" of howemplace()differs frominsert()(see highlighted box above), it's okay to temporarily imagine thispairobject 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 intermediarypairobject byunordered_mapintroduces subtle, but important, technicalities.
- insert()ing each of- foo0,- foo1, and- foo2required 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- foo0and- foo1created a temporary object (- foo4and- 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_mapwas destroyed once execution reached the end of- main().
- To insert()foo2, we instead first explicitly created a non-temporary pair object (calledpair), which calledFoo's copy constructor onfoo2(creatingfoo8as an internal member ofpair). We theninsert()ed this pair, which resulted inunordered_mapcalling the copy constructor again (onfoo8) to create its own internal copy (foo9). As withfoos 0 and 1, the end result was two destructor calls for thisinsert()ion with the only difference being thatfoo8's destructor was called only when we reached the end ofmain()rather than being called immediately afterinsert()finished.
 
- emplace()ing- foo3resulted in only 1 copy/move constructor call (creating- foo10internally 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- Fooobject) is meant to be an argument to some- Fooconstructor. In this case, the most fitting- Fooconstructor 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.
 
- For - foo11, we directly passed the integer 11 to- emplace(11, d)so that- unordered_mapwould 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- fooobject to do this. Importantly, notice that only 1 call to a- Fooconstructor 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- Fooconstructor), this call to- insert({12, d})resulted in two calls to- Foo's constructor (creating- foo12and- 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.)