Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

c++11 emplace_back and push_back syntax with struct

I'm using MSVC, Visual Studio 2013.

Suppose I have a struct:

struct my_pair {
    int foo, bar;
};

And I want to add a bunch of these efficiently, without too creating a temporary and then discarding it:

vector<my_pair> v;
v.push_back(41, 42); // does not work              [a]
v.push_back({41,42}); // works                     [b]
v.emplace_back(41,42); // does not work            [c]
v.emplace_back({41,42}); // does not work          [d]
v.emplace_back(my_pair{41,42}); //works            [e]

Now if I add a constructor and copy constructor to my code:

my_pair(int foo_, int bar_) : foo(foo_), bar(bar_) 
{
    cout << "in cstor" << endl;
}
my_pair(const my_pair& copy) : foo(copy.foo), bar(copy.bar)
{
    cout << "in copy cstor" << endl;
}

Then the behavior changes:

v.push_back(41, 42); // does not work                              [f]
v.push_back({41,42}); // displays "in cstor" and "in copy cstor"   [g]
v.emplace_back(41,42); // displays "in cstor"                      [h]
v.emplace_back({41,42}); // does not work                          [i]
v.emplace_back(my_pair{41,42}); // "in cstor" and "in copy cstor"  [j]

If I add a move constructor:

my_pair(my_pair&& move_) : foo(move_.foo), bar(move_.bar)
{
    cout << "in move cstor" << endl;
}

Then:

v.emplace_back(my_pair{41,42}); //displays "in cstor", "in move cstor"   [k]
v.emplace_back({41,42}); // still does not work                          [l]
v.push_back({41,42}); // displays "in cstor", "in move cstor"            [m]

Questions:
for [a,b] I understand the reason for working and not working.
for [c], it doesn't work because there is no constructor to forward the arguments to.
for [d], why doesn't this work like in the push case?
for [e], why does it work when the class name is added?
for [h], it seems like this is the most efficient code if there is a constructor that maps the arguments to the members
for [j], it seems like this is as bad as a push_back and with extra typing I'm not sure why anyone should do this over push_back
for [k,m], with the addition of a move constructor it seems like push_back(T&&) is being called which results in the same performance as emplace. But again, with the extra typing I'm not sure why anyone would do this.

I read that MSVC doesn't add a move constructor for you: Why is copy constructor called in call to std::vector::emplace_back()?

What is the difference between [d,e] and why is emplace picky about it. And why does push_back(T&&) work without addition of the struct's name?

I can only get the full benefits of emplace if I know that there is a constructor that takes each member as argument?

Should I just stick with push_back? Is there any reason to use emplace_back(structname{1,2,3}) instead of push_back({1,2,3}) because it will end up calling push_back(T&&) anyway, and is easier to type?

Third, how does emplace_back(arg1,arg2,etc), do its magic to avoid the copy or move constructor completely?

like image 973
stewart99 Avatar asked Dec 16 '13 04:12

stewart99


People also ask

What is Emplace_back ()?

C++ Vector Library - emplace_back() Function The C++ function std::vector::emplace_back() inserts new element at the end of vector. Reallocation happens if there is need of more space. This method increases container size by one.

When should I use Emplace_back?

Specific use case for emplace_back : If you need to create a temporary object which will then be pushed into a container, use emplace_back instead of push_back . It will create the object in-place within the container.

Why Emplace_back back is faster than Push_back?

because emplace_back would construct the object immediately in the vector, while push_back , would first construct an anonymous object and then would copy it to the vector.


Video Answer


1 Answers

For v.emplace_back({41,42});, see how to use std::vector::emplace_back for vector<vector<int> >?


v.emplace_back(41,42); does not work because of some rules in the standard (some emphasis mine):

Table 101 — Optional sequence container operations

Expression: a.emplace_back(args)

Return type: void

Operational semantics:
Appends an object of type T constructed with std::forward<Args>(args)....

Requires: T shall be EmplaceConstructible into X from args. For vector, T shall also be MoveInsertable into X.

For a type to be EmplaceConstructible,

§ 23.2.1.13

— T is EmplaceConstructible into X from args, for zero or more arguments args, means that the following expression is well-formed:

allocator_traits<A>::construct(m, p, args);

std::allocator_traits::construct() in turn does (if possible) a.construct(p, std::forward<Args>(args)...) (where a is m in the EmplaceConstructible expression).

a.construct() here is std::allocator::construct(), which calls ::new((void *)p) U(std::forward<Args>(args)...). This is what causes the compile error.

U(std::forward<Args>(args)...) (take note of the use of direct initialization) will find a constructor of U which accepts the forwarded arguments. However, in your case, my_pair is an aggregate type, which can only be initialized with braced-initialization syntax (aggregate initialization).


v.emplace_back(my_pair{41,42}); works because it calls either the implicitly generated default copy constructor or move constructor (note that these two may not always be generated). A temporary my_pair is first constructed which goes through the same process as that of v.emplace_back(41,42);, only that the argument is an r-value my_pair.


ADDITIONAL 1:

And why does push_back(T&&) work without addition of the struct's name?

It's because of push_back's signatures. push_back()'s argument isn't deduced, which means by doing push_back({1, 2}), a temporary object with the type of the vector's element type is first created and initialized with {1, 2}. That temporary object will then be the one that is passed to push_back(T&&).


Should I just stick with push_back? Is there any reason to use emplace_back(structname{1,2,3}) instead of push_back({1,2,3}) because it will end up calling push_back(T&&) anyway, and is easier to type?

Basically, emplace* functions is meant to optimize and remove the cost of creating temporaries and copying or move constructing objects when inserting them. However, for the case of aggregate data types where doing something like emplace_back(1, 2, 3) isn't possible, and the only way you could insert them is through creating a temporary then copying or moving, then by all means prefer the leaner syntax, and go for push_back({1,2,3}), where it would basically have the same performance as that of emplace_back(structname{1,2,3}).

like image 81
Mark Garcia Avatar answered Oct 30 '22 01:10

Mark Garcia