简体   繁体   中英

std::vector::emplace_back with lvalue expression

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?

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.

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 S s. 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.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM