[英]When to rely on RVO vs move semantics in C++?
Say I have some expensive class X
, and take this code:假设我有一些昂贵的 class
X
,并采用以下代码:
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?哪个
push_back
更有效率? In the first case, won't the NRVO be activated and prevent the copy as the move does?在第一种情况下,NRVO 不会像 move 一样被激活并阻止复制吗? Should I be relying on the NRVO instead of doing manual moving, since the NRVO is basically automatic moving?
我应该依靠 NRVO 而不是手动移动,因为 NRVO 基本上是自动移动的吗?
Neither piece of code is more efficient;这两段代码都没有效率更高; they do the exact same thing.
他们做同样的事情。 And neither
push_back
expression involves elision of any kind.并且
push_back
表达式都不涉及任何类型的省略。 The only actual elision happened back in the return
statement, which the push_back
expressions don't interact with.唯一实际的省略发生在
return
语句中, push_back
表达式不与之交互。
In both push_back
expressions, the prvalue returned by the function will manifest a temporary.在两个
push_back
表达式中,function 返回的纯右值将显示为临时值。 In the latter case, the temporary will be cast into an xvalue, but that doesn't represent any actual runtime changes.在后一种情况下,临时值将转换为一个 xvalue,但这并不代表任何实际的运行时更改。 Both versions will call the same
push_back
overload: the one which takes an rvalue reference.两个版本都将调用相同的
push_back
重载:采用右值引用的重载。 And therefore in both cases the return value will be moved from.因此,在这两种情况下,返回值都将从中移出。
NRVO implies that automatic storage described by x
is identical to storage referenced by rvalue result of functor()
call. NRVO 意味着
x
描述的自动存储与functor()
调用的右值结果引用的存储相同。 std::move()
in this case is late for the party and gets no credit.在这种情况下,
std::move()
迟到了,没有得到任何荣誉。
@Nichol Bolas' answer is correct, as far as it goes.就目前而言,@Nichol Bolas 的回答是正确的。
It seems to me that you don't entirely understand what std::move
does, or when it's intended to be used.在我看来,您并不完全了解
std::move
的作用,或者何时打算使用它。
std::move
is (at least primarily) used when you have a parameter or rvalue reference type, something like this: int foo(int &¶m);
std::move
(至少主要)在你有参数或右值引用类型时使用,如下所示: 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 (ie, param) so it is not itself an rvalue.问题是,即使
param
必须从右值初始化,并引用右值, param
本身有一个名称(即 param),所以它本身不是右值。 Because of that, the compiler can't/won't automatically recognize that param
can be used as the source of a move operation.因此,编译器不能/不会自动识别
param
可以用作移动操作的来源。 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.所以,如果你想从
param
中移出,你需要使用std::move
来告诉编译器这是一个右值,这样编译器就可以从它移动而不是从它复制。
In your code: vec.push_back(std::move(functor()));
在您的代码中:
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. ,您正在将
std::move
应用于保存 function 返回值的临时值。由于这是一个临时变量而不是命名变量,编译器可以/将自动识别它可以用作移动,所以std::move
没有完成任何事情的希望。
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
.虽然它并不真正适用于这种特定情况,但对于类似这种情况要记住的另一点是使用
emplace_back
而不是push_back
。 This can allow your code to avoid building and then copying/moving an object at all.这可以让您的代码完全避免构建然后复制/移动 object。 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.相反,它可以创建对传递给 ctor 的参数的引用以创建 object,以便在
emplace_back
本身内部,您可以在目标向量中它将要占据的位置创建一个 object(一次)。 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
.在这种情况下,
push_back
创建一个临时的foo
object,从a
和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.但是
emplace_back
避免创建临时文件。 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
.相反,它只是传递对
a
和b
的引用,并在emplace_back
内部传递,当它准备好在vector
中创建 object 时,它会创建它,并直接从a
和b
初始化它。
foo
directly contains data, rather than containing a pointer to the data. foo
直接包含数据,而不是包含指向数据的指针。 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.这意味着在这种情况下,与进行复制相比,进行移动可能会获得很少或没有任何收益,因此即使
push_back
可以接受右值并将其移出到目标中,它在这种情况下也不会有太大好处--无论如何它最终都会复制数据。 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.但是使用
emplace_back
,我们将完全避免这种情况,直接从源数据( a
和b
)初始化 object ,所以我们将复制它们一次——但只复制一次。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.