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?
Best Answer
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):For a type to be
EmplaceConstructible
,std::allocator_traits::construct()
in turn does (if possible)a.construct(p, std::forward<Args>(args)...)
(wherea
ism
in the EmplaceConstructible expression).a.construct()
here isstd::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 ofU
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 temporarymy_pair
is first constructed which goes through the same process as that ofv.emplace_back(41,42);
, only that the argument is an r-valuemy_pair
.ADDITIONAL 1:
It's because of
push_back
's signatures.push_back()
's argument isn't deduced, which means by doingpush_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 topush_back(T&&)
.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 likeemplace_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 forpush_back({1,2,3})
, where it would basically have the same performance as that ofemplace_back(structname{1,2,3})
.