C++ vector : emplace_back vs push_back

Share the Article

C++ vector has these two very special functions. Both of them insert object at the end position of vector. Therefore, fundamental topic is “emplace_back vs push_back”.

How push_back works ?

The push_back function of cpp vector accepts an argument object of vector type T. Therefore, the user can first construct an object and then pass this object as argument. However, when this argument is an L-value object, then push_back will invoke copy constructor. This will make a copy of object and pushes that copy in vector. But if the argument is R-value, then push_back calls move constructor of type T to enable taking ownership of R-value in a second object. Overall, process calls the constructor two times for using vector push_back.

Example: suppose “MainFunda” is a simple user-defined class

#include <iostream> //main header #include <vector> //std vector using namespace std;//namespace class MainFunda { std::string name; public: MainFunda() : name("basic") { cout << "Default Constructor: " << name << endl; } MainFunda(std::string s) : name(s) { cout << "Parametrized Constructor: " << name << endl; } MainFunda(const MainFunda& rhs) : name(rhs.name) { cout << "Copy Constructor: From " << name << endl; } MainFunda(MainFunda&& rhs) noexcept : name(std::move(rhs.name)) { cout << "Move Constructor : From " << name << endl; } ~MainFunda() { cout << "Destructor : " << name << endl; } }; int main() { std::vector<MainFunda> vtest; vtest.reserve(2); MainFunda a1("one"); vtest.push_back(a1); //Pushing an L-value vtest.push_back(MainFunda("two")); //Pushing an R-value return 0; }

The L-value case, will produce the output :

Parametrized Constructor: one <--Before push_back Copy Constructor: From one <--During push_back Destructor : one <-- When a1 goes out-of-scope Destructor : one <--When vector goes out-of-scope

For R-value case, the output may look like

Parametrized Constructor: two <--During push_back Move Constructor : From two <--During push_back Destructor : <--During push_back Destructor : two <--When vector goes out-of-scope

How emplace_back works ?

The emplace_back function of vector can accept an argument which will be just like push_back. Otherwise, it can directly accepts those arguments which the constructor of object needs. After this, it uses those arguments to create an object directly on the container. For instance, this can be done by calling relevant constructor (copy constructor or move constructor). Therefore, the vector emplace_back function calls the constructor only one time which is more efficient.

int main() { std::vector<MainFunda> vtest; vtest.reserve(3); MainFunda a1("one"); vtest.emplace_back(a1); //Pushes L-value vtest.emplace_back(MainFunda("two")); //Pushes R-value object vtest.emplace_back("three"); //Pushes argument of A return 0; }

The L-value case will produce an output. Besides, this i similar to push_back,

Parametrized Constructor: one <--Before emplace_back Copy Constructor: From one <--During emplace_back Destructor : one <-- When a1 goes out-of-scope Destructor : one <--When vector goes out-of-scope

The R-value case output will also be similar to push_back case.

Parametrized Constructor: two <--During emplace_back Move Constructor : From two <--During emplae_back Destructor : <--During emplace_back Destructor : two <--When vector goes out-of-scope

The third case when pushing argument of the class shows following output

Parametrized Constructor: three <--During emplace_back Destructor : three <--When vector goes out-of-scope

Sample Implementation of C++ vector push_back and emplace_back

The general implementation of stl vector is easily available in sources. However, a dummy raw implementation, is available here. In particular, it will help to understand the semantics of using vectors in c++.

template<typename T> class fake_vector { public: void dummy_push_back(T&& item); //R-value param void dummy_push_back(T& item); //L-value param template<typename ...TArg> void dummy_emplace_back(TArg&&... arg); }; template<typename T> void fake_vector<T>::dummy_push_back(T&& item) { cout << "push_back(R-Value): "; cout << item << endl; } template<typename T> void fake_vector<T>::dummy_push_back(T& item) { cout << "push_back(L-Value): "; cout << item << endl; } template<typename T> template<typename ...TArg> void fake_vector<T>::dummy_emplace_back(TArg&&... arg) { cout << "emplace_back : "; dummy_push_back( T(std::forward<TArg>(arg)...) ); } ostream& operator<<(ostream& os, A& rhs) { os << rhs.getName(); return os; }

Explanation:

Firstly, push_back has 2 variations for accepting L-value & R-value C++ reference type objects.

Secondly, emplace_back accepts variable arguments. It then perfectly forwards them to constructor of T.

template<typename T> template<typename ...TArg> void fake_vector<T>::dummy_emplace_back(TArg&&... arg) { cout << "emplace_back : "; dummy_push_back( T(std::forward<TArg>(arg)...) ); }

There will be 3 cases, where perfect forwarding works, for instance, when

  1. L-value object passed
  2. R-value object passed
  3. Direct argument passed

L-value object

a8 is an L-value.

MainFunda a8("eight"); vs.dummy_emplace_back(a8);

Therefore, this will deduce universal reference TArg&& to become an L-value MainFunda&. Hence, std::forward(MainFunda&) will return an L-value object “MainFunda&”. This means, this argument will invoke copy constructor of T (i.e. MainFunda).

T(std::forward<TArg>(arg)...) ==> MainFunda(MainFunda& arg)

The above input will generate an output:

Parametrized Constructor: eight emplace_back : Copy Constructor: From eight push_back(R-Value): eight Destructor : eight Destructor : eight

R-value object

MainFunda(“seven”) is an R-value

vs.dummy_emplace_back(MainFunda("seven"));

This call will deduce universal reference TArg&& to become an R-value, MainFunda&&. Hence, std::forward(MainFunda&&) will return an R-value object “MainFunda&&”.

This means, this argument will invoke move constructor of T (i.e. MainFunda)

T(std::forward<TArg>(arg)...) ==> MainFunda(MainFunda&& arg)

The above input will generate an output

Parametrized Constructor: seven emplace_back : Move Constructor : From seven push_back(R-Value): seven Destructor : seven Destructor :

Direct Argument

“six” is an argument of type char*. This will become std::string when it goes to parametrized constructor of MainFunda.

vs.dummy_emplace_back("six");

This will deduce universal reference TArg&& to become R-value “char* && arg”. Hence, std::forward(char*&& ) will return an R-value “char* &&arg”. Certainly, this is convertible to std::string type.

This means, the argument will invoke Parametrized constructor.

T(std::forward<TArg>(arg)...) ==> MainFunda(std::string arg)

The above input will generate an output

emplace_back : Parametrized Constructor: six push_back(R-Value): six Destructor : six

Passing zero argument to emplace_back

When the caller do not provides any argument, then emplace_back will invoke default constructor of vector type T. The class must have a default constructor, i.e. which do not take any argument.

vs.dummy_emplace_back();

The output is:

emplace_back : Default Constructor: basic push_back(R-Value): basic Destructor : basic

Main Funda: In vectors, emplace_back can do everything which push_back does.

Related Topics:

 What are the drawbacks of using enum ?
Which member functions are generated by compiler in class?
How to stop compiler from generating special member functions?
Compiler Generated Destructor is always non-virtual
How to make a class object un-copyable?
Explaining C++ casts
How pointer to class members are different ?
How std::forward( ) works?
Rule of Three
How std::move() function works?
What is reference collapsing?
How delete keyword can be used to filter polymorphism
Copy Constructor and Move Constructor: Test with vector

Share the Article

Leave a Reply

Your email address will not be published. Required fields are marked *