push_back vs emplace_back: When to use what
In today's post, I like to address a topic already discussed by others. However, in classes or code reviews, I still see the confusion. That's why I want to highlight this once more. What I'm talking about? The difference between push_back
and emplace_back
or better when using which function of the std::vector
.
emplace_back
for better performance
People often tell me they use emplace_back
when this topic comes up for performance reasons. Because it is faster than push_back
as it creates the object in place. And that is true. However, when I look at the code, I see that the statement does not apply to the code written. Let's use an example and test your knowledge.
We start with a bit of helper code:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
|
I will use Widget
later with std::vector
. The purpose of Widget
is to see which special member function gets invoked.
Testing your knowledge
Equipped with this helper class
, I make three attempts to add a new value to a std::vector
efficiently:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
|
A reduces the output to the interesting one. We don't want to see allocations and moves when the vector
has to resize.
The interesting part starts in B, where I use push_back
to add a newly created temporary object to my vector
v
. In C, I use a different approach with emplace_back
to add a newly created temporary object to v
. Last, in D, I use a variation of C, emplace_back
, together with just a value that Widget
has a constructor for.
The question for you is, which version is the best regarding efficiency and performance?
Looking at the reality
For the example given, the answer is D. This is the purpose of emplace_back
. Let's have a look at the output of this small program:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
|
Here we can see that the first emplace_back
leads to the same invocations of Widget
's special member functions as push_back
. Both first create a temporary Widget
by calling the constructor, taking an int
. After that, both move this freshly created temporary object into v
and finally destroy the now moved-from temporary object. Yes, I know, we still moved the data, so it looks perfect.
But, look at the second invocation of emplace_back
, the one from D. All we see here is the call to the constructor taking an int
of Widget
. No additional move, no destructor call. Why? Because we did not create an intermediated temporary object.
Constructing an object in-place
The purpose of emplace_back
is to construct an object in place. Whenever you see the containers type in an emplace_back
call, this code is simply wrong. Either you can then use push_back
, or you should get rid of the temporary object.
The ability of emplace_back
is to create an object in place. As we can already see from the output, we don't beat that with a push_back
or a std::move
.
There is more. Some people sometimes tell me that B and C aren't that bad because the compiler optimizes things away. Well, optimizations are great and good, but not relying on optimization is even better. Say we have a std::vector<std::string>
. The internal implementation of std::string
is not just a pointer and a length. The std::string
comes with some additional bytes for the Small String Optimization, something around 16 bytes. These bytes must be copied in case of push_back
and even the emplace_back
attempt in C. Because despite that, we see a call to Widget(Widget&&)
in our output, this doesn't mean that all the data in Widget
is moveable. In the case of std::string
, we have some non-moveable data. Be aware of that.
Summary
Use push_back
when you have an existing temporary object that you want to move into your std::vector
. Or, more generally, use push_back
when you want to move an existing object into your std::vector
.
Use emplace_back
when you create a new temporary object. Instead of creating that temporary object, pass the object's constructor arguments directly to emplace_back
.
Andreas