8

Does it ever make practical sense to use emplace_back with lvalue of some struct S:

like this:

std::vector<S> v;
auto s = S(/*...*/);
v.emplace_back(s);

Instead of just:

v.emplace_back(/* S constructor arguments */);

or is it just plain misuse of emplace_back, that only happen to work because const S& (and thus a copy constructor) is legitimate instantiation for Args... args inside emplace_back, and it is not forbidden explicitely?

463035818_is_not_an_ai
  • 109,796
  • 11
  • 89
  • 185
Student4K
  • 916
  • 2
  • 9
  • 20

2 Answers2

6

As you already said, passing const S& would just invoke the copy constructor.

Unless you intend to use s in some way before passing it to emplace_back, it is therefore not necessarily wise.

However, if the code to create s was, for instance, exceptionally long, it could improve readability to put it and the code for emplace_back on separate lines. Compilers are extremely good at optimizing such cases and will probably generate the same code anyways (if the copy constructor is default). Basic example: https://godbolt.org/z/D1FClE

If it improves readability or maintainability do it, otherwise there’s no value in it.

idmean
  • 14,540
  • 9
  • 54
  • 83
  • `std::move(s)` might allow to use move constructor though to avoid copy. – Jarod42 Mar 18 '19 at 14:32
  • 1
    @Jarod42 True. But std::move converts `s` to an "xvalue" and the question is about lvalues. – idmean Mar 18 '19 at 14:35
  • "if the copy constructor is default". I was trying at first to user-define copy constructor with some print-out to cout to see if it is called, but I guess that actually influenced the test by calling it. However, the emplace_back with argument pack only for the actual constructor arguments does not call copy-constructor, even if it is user-defined. – Student4K Mar 18 '19 at 20:32
  • @Student4K Your last sentence describes the expected behavior. Adding a print statement will certainly prevent the compiler from optimizing the copy constructor away. – idmean Mar 18 '19 at 20:56
1

If s is not needed later in the code, then it is a misuse of the emplace_back() function. This is because you are invoking the copy constructor of the S class instead of passing the arguments to the emplace_back() which will use the correct constructor from S.

Consider the following code:

#include <iostream>
#include <vector>

struct S
{
    S()          {std::cout<< "     default ctor" <<std::endl;}
    S(int)       {std::cout<< "     user-def ctor" <<std::endl;}
    S(const S &) {std::cout<< "     copy ctor" <<std::endl;}
    S(S &&)      {std::cout<< "     move ctor" <<std::endl;}
};

int main()
{
    std::vector<S> v;
    v.reserve(5);

    std::cout<< "auto calls: " <<std::endl;
    auto s = S();
    std::cout<<std::endl;

    std::cout<< "emplace_back( s ) calls: " <<std::endl;
    v.emplace_back(s);
    std::cout<<std::endl;

    std::cout<< "emplace_back( std::move(s) ) calls: " <<std::endl;
    v.emplace_back(std::move(s));
    std::cout<<std::endl;

    std::cout<< "emplace_back( S{} ) calls: " <<std::endl;
    v.emplace_back(S{});
    std::cout<<std::endl;

    std::cout<< "emplace_back( ) calls: " <<std::endl;
    v.emplace_back();
    std::cout<<std::endl;

    std::cout<< "emplace_back( 2 ) calls: " <<std::endl;
    v.emplace_back(2);
    std::cout<<std::endl;
}

The results are:

auto calls: 
     default ctor

emplace_back( s ) calls: 
     copy ctor

emplace_back( std::move(s) ) calls: 
     move ctor

emplace_back( S{} ) calls: 
     default ctor
     move ctor

emplace_back( ) calls: 
     default ctor

emplace_back( 2 ) calls: 
     user-def ctor

The reserve is used to allocate space for 5 Ss. Without reserving the space, the outputs would include additional calls to the copy ctors from the vector.

When you just pass the arguments to the constructor of S (in this case, nothing), the emplace_back() creates an S object using the default ctor directly inside the vector.

Btw, see the example in godbolt which is your friend in these cases to see exactly what happens in the background.

Constantinos Glynos
  • 2,952
  • 2
  • 14
  • 32