The common advice is to prefer using std::map::try_emplace in preference to std::map::emplace in almost every instance.
I wrote a simple test to trace object creation/copying/moving/destruction when calling those functions, with and without clashes, and the results show that try_emplace incurs an extra move and destruction of the key when it is not already in the map.
Why the difference in behaviour?
I do know that moves and destructions of moved-from objects are usually cheap, especially so for trivial keys, but I was still surprised by the results as they seem to imply that for some cases emplace might be more efficient.
Compiler explorer link (Clang 14, libc++, -O3)
Source:
#include <map>
#include <iostream>
struct F {
F(int i): i(i) { std::cout << "- ctor (" << i << ")\n"; }
~F() { std::cout << "- dtor (" << i << ")\n"; }
F(const F& f): i(f.i) { std::cout << "- copy ctor (" << i << ")\n"; }
F(F&& f): i(f.i) { std::cout << "- move ctor (" << i << ")\n"; }
F& operator=(const F& f) { i = f.i; std::cout << "- copy (" << i << ")\n"; return *this; }
F& operator=(F&& f) { i = f.i; std::cout << "- move (" << i << ")\n"; return *this; }
bool operator <(const F& f) const { return i < f.i; }
int i{};
};
int main() {
std::map<F, F> m;
std::cout << "emplace 1:\n";
m.emplace(1, 2);
std::cout << "emplace 2:\n";
m.emplace(1, 3);
std::cout << "clear:\n";
m.clear();
std::cout << "try_emplace 1:\n";
m.try_emplace(1, 2);
std::cout << "try_emplace 2:\n";
m.try_emplace(1, 3);
std::cout << "done:\n";
}
Results:
emplace 1:
- ctor (1)
- ctor (2)
emplace 2:
- ctor (1)
- ctor (3)
- dtor (3)
- dtor (1)
clear:
- dtor (2)
- dtor (1)
try_emplace 1:
- ctor (1)
- move ctor (1)
- ctor (2)
- dtor (1)
try_emplace 2:
- ctor (1)
- dtor (1)
done:
- dtor (2)
- dtor (1)
The difference between the two functions is:
std::map::emplaceconstructs avalue_type, i.e. astd::pairin-place, and then attempts to insert this pair.std::map::try_emplaceattempts to find an insert location first, and if one is found, it will construct thevalue_type, i.e. astd::pairin-place.try_emplaceappears to save us some work, but remember that even if we don't insert anything new into the map withtry_emplace, we still have to check whether we can, and where. This happens withstd::lessby default, which ends up calling:An
Fobject must exist to perform this comparison, and is being constructed once duringtry_emplace.Let's annotate your example with what is happening:
Conclusion
We can't say that
emplaceis universally better or worse thantry_emplace, rather there is a trade-off:emplacetry_emplaceThe trade-off changes when we have a pre-existing key instead of initializing one inside of
try_emplaceoremplace:emplacetry_emplaceIn conclusion,
try_emplaceis at worst wasting one move constructor compared toemplace, and at best, it's strictly better. Prefer it in most, but not all cases.1) "overhead" is relative to magically knowing the right location (or absence thereof) in the map and initializing a node in-place. Initialization of the pre-existing key is considered free.