Helge Klein
Helge Klein

Reputation: 9085

Should I replace all calls to push_back with emplace_back?

In my C++ application I heavily use STL containers like vector. There are a lot of calls to push_back, and I have been concerned about unnecessary constructions and copy operations.

My application is pretty low-level and I am very concerned about CPU and memory usage. Should I replace all calls to push_back with calls to emplace_back?

I am using Visual Studio 2013.

Upvotes: 8

Views: 1802

Answers (3)

Howard Hinnant
Howard Hinnant

Reputation: 218750

This test:

#include <type_traits>
#include <typeinfo>
#include <iostream>
#ifndef _MSC_VER
#   include <cxxabi.h>
#endif
#include <memory>
#include <string>
#include <cstdlib>
#include <vector>

template <typename T>
std::string
type_name()
{
    typedef typename std::remove_reference<T>::type TR;
    std::unique_ptr<char, void(*)(void*)> own
           (
#ifndef _MSC_VER
                abi::__cxa_demangle(typeid(TR).name(), nullptr,
                                           nullptr, nullptr),
#else
                nullptr,
#endif
                std::free
           );
    std::string r = own != nullptr ? own.get() : typeid(TR).name();
    if (std::is_const<TR>::value)
        r += " const";
    if (std::is_volatile<TR>::value)
        r += " volatile";
    if (std::is_lvalue_reference<T>::value)
        r += "&";
    else if (std::is_rvalue_reference<T>::value)
        r += "&&";
    return r;
}

template <int N>
struct member
{
    member()
    {
        std::cout << type_name<member>() << "()\n";
    }

    ~member()
    {
        std::cout << "~" << type_name<member>() << "()\n";
    }

    member(member const& x)
    {
        std::cout << type_name<member>()
                  << "(" << type_name<decltype(x)>() << ")\n";
    }

    member& operator=(member const& x)
    {
        std::cout << type_name<member>() << "::operator=("
                  << type_name<decltype(x)>() << ")\n";
        return *this;
    }

    member(member&& x)
    {
        std::cout << type_name<member>()
                  << "(" << type_name<decltype(x)>() << ")\n";
    }

    member& operator=(member&& x)
    {
        std::cout << type_name<member>() << "::operator=("
                  << type_name<decltype(x)>() << ")\n";
        return *this;
    }
};

int
main()
{
    std::vector<member<1>> v;
    v.reserve(10);
    member<1> m;
    std::cout << "\npush_back an lvalue\n";
    v.push_back(m);
    std::cout << "\nemplace_back an lvalue\n";
    v.emplace_back(m);
    std::cout << "\npush_back an xvalue\n";
    v.push_back(std::move(m));
    std::cout << "\nemplace_back an xvalue\n";
    v.emplace_back(std::move(m));
    std::cout << "\npush_back a prvalue\n";
    v.push_back(member<1>{});
    std::cout << "\nemplace_back an prvalue\n";
    v.emplace_back(member<1>{});
    std::cout << "\nDone\n";
}

For me outputs:

member<1>()

push_back an lvalue
member<1>(member<1> const&)

emplace_back an lvalue
member<1>(member<1> const&)

push_back an xvalue
member<1>(member<1>&&)

emplace_back an xvalue
member<1>(member<1>&&)

push_back a prvalue
member<1>()
member<1>(member<1>&&)
~member<1>()

emplace_back an prvalue
member<1>()
member<1>(member<1>&&)
~member<1>()

Done
~member<1>()
~member<1>()
~member<1>()
~member<1>()
~member<1>()
~member<1>()
~member<1>()

I.e. I would not expect any difference whatsoever.

Upvotes: 2

Helge Klein
Helge Klein

Reputation: 9085

I replaced all calls to push_back with calls to emplace_back and noticed the following:

  • RAM usage is reduced by approximately 20% (update: this may have been due to other effects)
  • CPU usage is unchanged
  • The binary is slightly smaller (x64)
  • There were no compatibility problems

Based on these experiences I can highly recommend to make the move from push_back to emplace_back if your project does not need to be backwards-compatible with older compilers.

Upvotes: 6

galop1n
galop1n

Reputation: 8824

It is an almost always rule. You cannot rely on side effect of copy constructors so it should means that skipping it explicitly is the right thing to do, but there is one case.

std::vector<std::unique_ptr<A>> foo;
foo.emplace_back( new A );

If at some time a throw is triggered, like when the vector resize, you end with a leak. So emplace_back is not possible.

If A constructors and the parameter you sent are exception safe, then there is no reason to not use an emplace_back.

Upvotes: 4

Related Questions