Difference between emplace_back and push_back
Last week, I can across a problem regarding push_back
. My code can be summarized as:
#include <vector>
#include <iostream>
class B {
public :
B() {
// do some init here
}
~B() {
// do some freeing here
}
void someMethod();
};
class A {
public :
A() {
// do some init here
}
~A() {
// do some freeing here
}
B* memberB;
};
What happens when an new instance of A is push_back
ed into a vector?
std::vector<A> v;
v.push_back(A{});
std::cout << "push_back called!" << std::endl;
v[0].memberB->someMethod();
Segmentation error! What happened? To find out, I added print statements to all the constructors and deconstructors to see when they are called. I also added a field called id
to each class, and set it to an global variable that auto-increases when a constructor is called, so we can clearly see which instance is which. Let’s run it shall we:
A 1 is constructed
B 2 is constructed
A 1 is destructed
B 2 is destructed
push_back called!
v[0].id is 0
is v[0].memberB null: yes
A 0 is destructed
B is no longer there when A is placed into the array. It self destructed! Why would that happen? Well, push_back
doesn’t just put the element into the array. It calls the copy constructor and copies the array to a new blank instance of A. As the original copy is constructed in-place, it falls out of scope and is destroyed. If you add a line of print to A’s copy constructor:
A is pushed into v
A(const A& c) {
std::cout << "A " << c.id << " is being copied to A " << id << std::endl;
}
Then between the construction of B and destruction of A, the line:
A 1 is being copied to A 0
would be printed. A 0
means that the instance of A inside the vector didn’t have its constructor called, and its members are just uninitialized memory, which is zero.
The default copy constructor can only shallow copy, that is, copy the members themselves, but have no knowledge of the underlying resources required by them. So in order to make the code work, one would need to implement the copy constructor. In my case, the code was more complex than the example above, and the self-destructed resource was hiding behind many layers. So it took me some time to figure out the problem.
What if you don’t want to implement the copy constructor for some reason? Well, we could use emplace_back
:
v.emplace_back();
Note that there are no arguments, because this method does not accept a whole instance, but instead arguments for the type’s constructors. So if there are arguments, then the arguments goes there. The output is:
A 1 is constructed
B 2 is constructed
emplace_back called!
v[0].id is 1
is v[0].memberB null: no
A 1 is destructed
B 2 is destructed
You can see that the instance is constructed in place, and no copy constructors are called, so no problems with deep copy and shallow copy.
References: