Say I have some expensive class X
, and take this code:
X functor() {
X x;
//do stuff
return x;
}
int main() {
std::vector<X> vec;
vec.push_back(functor());
vec.push_back(std::move(functor()));
return 0;
}
Which push_back
is more efficient? In the first case, won't the NRVO be activated and prevent the copy as the move does? Should I be relying on the NRVO instead of doing manual moving, since the NRVO is basically automatic moving?
CodePudding user response:
Neither piece of code is more efficient; they do the exact same thing. And neither push_back
expression involves elision of any kind. The only actual elision happened back in the return
statement, which the push_back
expressions don't interact with.
In both push_back
expressions, the prvalue returned by the function will manifest a temporary. In the latter case, the temporary will be cast into an xvalue, but that doesn't represent any actual runtime changes. Both versions will call the same push_back
overload: the one which takes an rvalue reference. And therefore in both cases the return value will be moved from.
CodePudding user response:
NRVO implies that automatic storage described by x
is identical to storage referenced by rvalue result of functor()
call. std::move()
in this case is late for the party and gets no credit.
CodePudding user response:
@Nichol Bolas' answer is correct, as far as it goes.
It seems to me that you don't entirely understand what std::move
does, or when it's intended to be used.
std::move
is (at least primarily) used when you have a parameter or rvalue reference type, something like this: int foo(int &¶m);
. The problem is that even though param
has to be initialized from an rvalue, and refers to an rvalue, param
itself has a name (i.e., param) so it is not itself an rvalue. Because of that, the compiler can't/won't automatically recognize that param
can be used as the source of a move operation. So, if you want to do a move out of param
, you need to use std::move
to tell the compiler that this is an rvalue, so the compiler can move from it instead of copying from it.
In your code: vec.push_back(std::move(functor()));
, you're applying the std::move
to the temporary value that holds the return from the function. Since this is a temporary rather than a named variable, the compiler can/will automatically recognize that it can be used as the source of a move, so std::move
has no hope of accomplishing anything.
Although it doesn't really apply in this specific case, the other point to keep in mind for situations somewhat like this is using emplace_back
instead of push_back
. This can allow your code to avoid building and then copying/moving an object at all. Instead, it can create references to the parameters that you pass to the ctor to create the object, so that inside of emplace_back
itself, you create an object (once) in the spot it's going to occupy in the target vector. For example:
struct foo {
int i;
double d;
foo(int i, double d) : i(i), d(d) {}
};
std::vector<foo> f;
int a = 123;
double b = 456.789;
f.push_back(foo(a, b));
f.emplace_back(a, b);
In this case, the push_back
creates a temporary foo
object, initialized from a
and b
. Since that's a temporary, the compiler recognizes that it can do a move from there into the spot it's going to occupy in the vector.
But the emplace_back
avoids creating the temporary at all. Instead, it just passes references to a
and b
, and inside of emplace_back
, when it's ready to create the object in the vector
, it creates it, initializing it directly from a
and b
.
foo
directly contains data, rather than containing a pointer to the data. That mean in this case, doing a move is likely to gain little or nothing compared to doing a copy, so even though push_back
can accept an rvalue and do a move out of it into the target, it wont' do much good in this case--it'll end up copying the data anyway. But with emplace_back
, we'll avoid that completely, and just initialize the object directly from the source data (a
and b
), so we'll copy them once--but only once.