8

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.

Helge Klein
  • 8,829
  • 8
  • 51
  • 71
  • FWIW, http://stackoverflow.com/questions/10890653/why-would-i-ever-use-push-back-instead-of-emplace-back?rq=1 – chris Mar 18 '14 at 02:03

3 Answers3

6

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.

Helge Klein
  • 8,829
  • 8
  • 51
  • 71
  • How do you tell replace a function will reduce RAM? – billz Mar 18 '14 at 01:42
  • 1
    @Billz, you measure RAM usage when your program uses `push_back`, then you replace it with `emplace_back` and measure again. – Rob Kennedy Mar 18 '14 at 02:33
  • @billz: As Rob wrote measure before and after is what I did. This is aided by the fact that my application is a user experience monitoring product (http://helgeklein.com/uberagent-for-splunk/). – Helge Klein Mar 18 '14 at 13:16
4

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.

galop1n
  • 8,573
  • 22
  • 36
  • Although in this case, you should write your own `make_unique` function that forwards the arguments to a `std::unique_ptr` constructor to never worry about this problem. In Visual Studio you'll have to manually stamp out a few overloads that take different numbers of arguments, which is a pain, but worth the increase in safety. – David Stone Mar 23 '14 at 18:17
2

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.

Howard Hinnant
  • 206,506
  • 52
  • 449
  • 577