Deep Analysis of push_back vs emplace_back in C++ STL: From Temporary Objects to Perfect Forwarding

Nov 05, 2025 · Programming · 24 views · 7.8

Keywords: C++ | STL | push_back | emplace_back | perfect forwarding | variadic templates

Abstract: This article provides an in-depth exploration of the core differences between push_back and emplace_back in C++ STL, focusing on how emplace_back's perfect forwarding mechanism through variadic templates avoids unnecessary temporary object construction. By comparing function signatures, implementation principles, and performance characteristics of both methods, with concrete code examples demonstrating emplace_back's advantages in complex object construction scenarios, and explaining historical limitations in early Visual Studio implementations. The article also discusses best practices for choosing between push_back and emplace_back to help developers write more efficient C++ code.

Introduction

In the C++ Standard Template Library (STL), std::vector as the most commonly used dynamic array container provides multiple element insertion methods. Among them, push_back and emplace_back are two primary methods for insertion at the end, but they differ fundamentally in implementation mechanisms and performance characteristics. Understanding these differences is crucial for writing efficient C++ code.

Function Signatures and Basic Differences

The push_back method has two overloaded versions:

void push_back(const Type& _Val);  // lvalue reference version
void push_back(Type&& _Val);     // rvalue reference version

Whereas the true C++11 standard emplace_back function signature is:

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

The key difference lies in: push_back accepts an already constructed object (whether lvalue or rvalue), while emplace_back accepts a parameter pack required to construct the object, building it directly in the container's memory.

Temporary Object Construction Issues

Consider this typical scenario using push_back:

std::vector<std::string> vec;
vec.push_back(std::string("Hello"));

This process involves:

  1. Constructing a temporary std::string object
  2. Calling the move constructor (or copy constructor) to move the temporary object into the container
  3. Destroying the temporary object

Even with move semantics and RVO (Return Value Optimization), unnecessary copies or moves cannot be avoided in certain complex situations.

Perfect Forwarding Advantage of emplace_back

emplace_back achieves true in-place construction through variadic templates and perfect forwarding:

std::vector<std::string> vec;
vec.emplace_back("Hello");  // Construct string directly in container

This process completely avoids temporary object creation, with constructor parameters being perfectly forwarded to the construction location within the container.

Complex Object Construction Case Study

For complex types requiring multiple construction parameters, emplace_back's advantages become more pronounced. Consider a map containing complex objects:

class Complicated {
public:
    Complicated(int a, double b, const std::string& c) 
        : intVal(a), doubleVal(b), strVal(c) {}
private:
    int intVal;
    double doubleVal;
    std::string strVal;
};

std::map<int, Complicated> m;
int anInt = 4;
double aDouble = 5.0;
std::string aString = "C++";

Using traditional insert method:

// Requires creating temporary pair object
m.insert(std::make_pair(4, Complicated(anInt, aDouble, aString)));

Using emplace method:

// Directly forwards parameters, avoiding temporary objects
m.emplace(4, anInt, aDouble, aString);

Performance Comparison Experiment

Observe performance differences between the two methods using a simple class:

class Widget {
public:
    Widget(int x) : value(x) {
        std::cout << "Constructor: " << value << std::endl;
    }
    
    Widget(const Widget& other) : value(other.value) {
        std::cout << "Copy Constructor: " << value << std::endl;
    }
    
    Widget(Widget&& other) noexcept : value(other.value) {
        std::cout << "Move Constructor: " << value << std::endl;
    }
    
    ~Widget() {
        std::cout << "Destructor: " << value << std::endl;
    }
    
private:
    int value;
};

std::vector<Widget> vec;
vec.reserve(3);  // Pre-allocate space to avoid reallocation effects

Using push_back:

vec.push_back(Widget(1));
// Output:
// Constructor: 1
// Move Constructor: 1
// Destructor: 1

Using emplace_back:

vec.emplace_back(1);
// Output:
// Constructor: 1

We can see that emplace_back completely avoids the overhead of move construction and temporary object destruction.

Historical Implementation Issues

In Visual Studio 2010, Microsoft implemented a non-standard version of emplace_back:

void emplace_back(Type&& _Val);

This implementation is completely equivalent to push_back(Type&& _Val), not leveraging the advantages of variadic templates. According to Visual C++ standard library maintainer Stephan T Lavavej, this was because VC10 had not yet implemented variadic templates, and using preprocessor simulation would severely impact compilation speed and code maintainability.

Usage Scenario Recommendations

When to use push_back:

When to use emplace_back:

Important Considerations

It's important to note that emplace_back is not always faster than push_back. When passing lvalues:

std::string s = "Hello";
vec.emplace_back(s);  // Calls copy constructor
vec.push_back(s);     // Also calls copy constructor

In this case, both methods have identical performance.

Conclusion

emplace_back, through variadic templates and perfect forwarding technology, can significantly improve performance in appropriate usage scenarios, particularly when constructing complex objects or needing to avoid temporary objects. However, developers need to choose the appropriate method based on specific scenarios, balancing performance, readability, and compatibility requirements. Understanding the underlying mechanisms of these two methods helps in writing more efficient, modern C++ code.

Copyright Notice: All rights in this article are reserved by the operators of DevGex. Reasonable sharing and citation are welcome; any reproduction, excerpting, or re-publication without prior permission is prohibited.