Copy constructor and emplace_back() in C++
A copy constructor of class T is a non-template constructor whose first parameter is
T&
,const T&
,volatile T&
, orconst volatile T&
, and either there are no other parameters, or the rest of the parameters all have default values.from cppreference.com
Syntax
1 | // Typical declaration of a copy constructor. |
When is copy constructor called?
In C++, a Copy Constructor may be called in following cases:
- When an object of the class is returned by value.
- When an object of the class is passed (to a function) by value as an argument.
- When an object is constructed based on another object of the same class.
- When compiler generates a temporary object.
Let’s talk about some cases where you might not even realize how many copies it has done. Copy is not cheap, sometimes. In some cases, copy constructor can be avoided by Copy Elision, but it is not in the scope of this discussion.
Show me an example
How many times the copy constructor is called?
1 |
|
The answer is 3
.
- When
i
is0
, it creates a new Point object, calls normal constructor. - Pushes it into vector, copy once.
- When
i
is1
, it creates a new Point object again, calls normal constructor. - Pushes again, copy again.
- Wait, before push, vector
points
needs to resize (usually double the size), copypoints[0]
to new vector first, then push. - In total,
3
copy constructor calls and2
normal calls.
1 | === 0 === |
You can run this code online, https://onlinegdb.com/BkuZ6vFB7. Play with it, change n
to some other value to see if you can get the right number of copy constructor calls.
Can we do better?
Yes, we can use std::vector::emplace_back()
instead of std::vector::push_back()
.
(It) Appends a new element to the end of the container. The element is constructed through
std::allocator_traits::construct
, which typically uses placement-new to construct the element in-place at the location provided by the container.from cppreference.com
Let’s change the main()
.
1 | int main() |
How many times does copy constructor get called? Only once.
- When
i
is0
,emplace_back
calls the normal constructor ofPoint
, put the newly created object in vector directly. no copy. - When
i
is1
,points
resizes itself, need to copy the existing elementpoints[0]
, one copy. - Then, as the first step,
emplace_back
creates new object in place, no copy needed.
1 | === 0 === |
You can also run the code online, https://onlinegdb.com/SJo_lOKSX. Change n
to some other values to see if you can get the right number of copy constructor calls.
Should I always use emplace_back()?
We can not deny the fact that std::vector::emplace_back()
saves unnecessary copy operations and it can be a big win for some operations on large objects. However, using emplace_back
means you need to take care of the constructor arguments, which sometimes could be arbitrary. Check the example in Tip of the Week #112: emplace vs. push_back, the second example constructs a vector of over a million elements, allocating several megabytes of memory in the process, if the variable type is not as what you think, and this is dangerous.
For more expensive types, this may be a reason to use
emplace_back()
instead ofpush_back()
, despite the readability and safety costs, but then again it may not. Very often the performance difference just won’t matter. As always, the rule of thumb is that you should avoid optimizations that make the code less safe or less clear, unless the performance benefit is big enough to show up in your application benchmarks.
Let’s keep this in mind.