First of all, let’s start from the definition of each method:

push_back: Adds a new element at the end of the container, after its current last element. The content of val is copied (or moved) to the new element. This effectively increases the container size by one, which causes an automatic reallocation of the allocated storage space if and only if the new vector size surpasses the current vector capacity.

void push_back (const value_type& val);
void push_back (value_type&& val);

emplace_back: Inserts a new element at the end of the container, right after its current last element. This new element is constructed in place using args as the arguments for its constructor. This effectively increases the container size by one, which causes an automatic reallocation of the allocated storage space if and only if the new vector size surpasses the current vector capacity.

template <class... Args>  void emplace_back (Args&&... args);

push_back vs emplace_back

Following will happen if we call push_back?

  1. A constructor will be called to create a temporary object.
  2. A copy of the temporary object will be constructed in the memory for the container. Note that the move constructor will be called if exist because the temporary object is an rvalue, otherwise the copy constructor should be called.
  3. The destructor will be called to destroy the temporary object after copy.

As a comparison, emplace_back directly takes constructor arguments for objects to be inserted. In other words, the emplacement function avoids constructing and destructing temporary objects. It will be much more efficient if object creation/destroy are time consuming.

#include <format>
#include <vector>
#include <queue>
#include <iostream>
#include <algorithm>
#include <string>
#include <unordered_set>
#include <functional>
#include <map>
#include <set>
#include <string>
#include <stack>

using namespace std;

class MyClass {
public:
    MyClass(int x, int y) : x_(x), y_(y) {
        std::cout << format("Constructor\n");
    }

    ~MyClass() {
        std::cout << format("Destructor\n");
    }

    // Copy Constructor
    MyClass(const MyClass& my_class) {
        std::cout << format("Copy Constructor\n");
        x_ = my_class.x_;
        y_ = my_class.y_;
    }

    // Move Constructor
    MyClass (MyClass&& my_class) noexcept {
        std::cout << format("Move Constructor\n");
        x_ = std::move(my_class.x_);
        y_ = std::move(my_class.y_);
    }

private:
    int x_ = 0;
    int y_ = 0;

};

int main() {
    std::vector<MyClass> vector;
    // Reserve space to avoid reallocation
    vector.reserve(2);
    
    std::cout << "\n--- push_back ---" << std::endl;
    // push_back accepts the only object of the type
    vector.push_back(MyClass(1, 2));
    
    std::cout << "\n--- emplace_back ---" << std::endl;
    // emplace_back accept arguments of the constructor of the type.
    vector.emplace_back(1, 2);
    
    std::cout << "\n--- Finish ---" << std::endl;
    return 0;
}

/* Program Output 
--- push_back ---
Constructor
Move Constructor
Destructor

--- emplace_back ---
Constructor

--- Finish ---
Destructor
Destructor
*/

In the above example, As we expected, push_back method calls the move constructor to make a copy and the destructor to destroy the temporary object. But emplace_back construct the object directly.