Last Updated
Viewed 290,622 Times

I'm a bit confused regarding the difference between push_back and emplace_back.

void emplace_back(Type&& _Val);
void push_back(const Type& _Val);
void push_back(Type&& _Val);

As there is a push_back overload taking a rvalue reference I don't quite see what the purpose of emplace_back becomes?

I'm currently learning C++ on my own, and I am curious about how push_back() and emplace_back() work under the hood. I've always assumed that emplace_back() is faster when you are trying to construct and push a large object to the back of a container, like a vector.

Let's suppose I have a Student object that I want to append to the back of a vector of Students.

struct Student {
   string name;
   int student_ID;
   double GPA;
   string favorite_food;
   string favorite_prof;
   int hours_slept;
   int birthyear;
   Student(string name_in, int ID_in, double GPA_in, string food_in, 
           string prof_in, int sleep_in, int birthyear_in) :
           /* initialize member variables */ { }
};

Suppose I call push_back() and push a Student object to the end of a vector:

vector<Student> vec;
vec.push_back(Student("Bob", 123456, 3.89, "pizza", "Smith", 7, 1997));

My understanding here is that push_back creates an instance of the Student object outside of the vector and then moves it to the back of the vector.

Diagram: https://ibb.co/hV6Jho

I can also emplace instead of push:

vector<Student> vec;
vec.emplace_back("Bob", 123456, 3.89, "pizza", "Smith", 7, 1997);

My understanding here is that the Student object is constructed at the very back of the vector so that no moving is required.

Diagram: enter image description here

Thus, it would make sense that emplacing would be faster, especially if many Student objects are added. However, when I timed these two versions of code:

for (int i = 0; i < 10000000; ++i) {
    vec.push_back(Student("Bob", 123456, 3.89, "pizza", "Smith", 7, 1997));
}

and

for (int i = 0; i < 10000000; ++i) {
    vec.emplace_back("Bob", 123456, 3.89, "pizza", "Smith", 7, 1997);
}

I expected the latter to be faster, since the large Student object wouldn't have to be moved. Oddly enough, the emplace_back version ended up being slower (across multiple attempts). I also tried inserting 10000000 Student objects, where the constructor takes in references and the arguments in push_back() and emplace_back() are stored in variables. This also didn't work, as emplace was still slower.

I've checked to make sure that I'm inserting the same number of objects in both cases. The time difference isn't too large, but emplacing ended up slower by a few seconds.

Is there something wrong with my understanding of how push_back() and emplace_back() work? Thank you very much for your time!

Here's the code, as requested. I'm using the g++ compiler.

Push back:

struct Student {
   string name;
   int student_ID;
   double GPA;
   string favorite_food;
   string favorite_prof;
   int hours_slept;
   int birthyear;
   Student(string name_in, int ID_in, double GPA_in, string food_in, 
           string prof_in, int sleep_in, int birthyear_in) :
           name(name_in), student_ID(ID_in), GPA(GPA_in), 
           favorite_food(food_in), favorite_prof(prof_in),
           hours_slept(sleep_in), birthyear(birthyear_in) {}
};

int main() {
    vector<Student> vec;
    vec.reserve(10000000);
    for (int i = 0; i < 10000000; ++i) 
         vec.push_back(Student("Bob", 123456, 3.89, "pizza", "Smith", 7, 1997));
    return 0;
}

Emplace back:

struct Student {
   string name;
   int student_ID;
   double GPA;
   string favorite_food;
   string favorite_prof;
   int hours_slept;
   int birthyear;
   Student(string name_in, int ID_in, double GPA_in, string food_in, 
           string prof_in, int sleep_in, int birthyear_in) :
           name(name_in), student_ID(ID_in), GPA(GPA_in), 
           favorite_food(food_in), favorite_prof(prof_in),
           hours_slept(sleep_in), birthyear(birthyear_in) {}
};

int main() {
    vector<Student> vec;
    vec.reserve(10000000);
    for (int i = 0; i < 10000000; ++i) 
         vec.emplace_back("Bob", 123456, 3.89, "pizza", "Smith", 7, 1997);
    return 0;
}

I defined the following

std::vector<std::pair<int,int> > my_vec;
my_vec.push_back( {1,2} ); //this works
my_vec.emplace_back( {1,2} ); // this doesn't work
std::pair<int,int> temp_pair = {1,2}; 
my_vec.emplace_back( temp_pair );         //this works

I am compiling with c++11. The third line is problematic, but I thought you can use emplace_back() anywhere that you have push_back(), but this is apparently wrong. Why does the third line not work?

I'm wondering if the emplace_back and push_back methods of std::vector behave any different when using primitive scalar types, such as std::uint32_t or std::uint8_t. Intuitively, I would guess that, after compilation, both variants would lead to the same bytecode here:

void copyListContent(std::uint8_t * list, std::size_t nElems,
                     std::vector<std::uint8_t> & vec)
{
    vec.clear();
    vec.reserve(nElems);
    for (std::size_t i = 0; i < nElems; ++i)
    {
        //variant 1:
        vec.push_back(list[i]);
        //variant 2:
        vec.emplace_back(list[i]);
    }        
}

Please correct me if that should be already wrong...

Now, where I'm starting to struggle is when I'm asking myself what happens if the types of the "list" and vector don't match:

void copyListContent(std::uint8_t * list, std::size_t nElems,
                     std::vector<std::uint32_t> & vec)
{
    //... same code as above      
}

The std::uint8_t elements will be converted to std::uint32_t when putting them into the vector (using emplace_back or push_back), so I'm wondering if that triggers some "constructor" to be called? In that case, would emplace_back be more efficient, because it would avoid to construct a temporary object that would be copied? Or are these implicit conversions that don't make any difference, and emplace_back and push_back will behave the same?

So, I'm asking myself, and you: For primitive types like these, do emplace_back and push_back always behave similarly?

As a vague guess I'd say "probably yes", but I have not enough knowledge about C++ internals to reliably answer this for myself. I'd be happy to learn how things work in this case - thanks a lot in advance!

Similar Question 5 (1 solutions) : Vector Push_Back VS Emplace_Back

Similar Question 6 (1 solutions) : push_back vs emplace_back with a volatile

Similar Question 9 (2 solutions) : Emplace_back instead of push_back on stl containers?

cc