简体   繁体   English

何时依赖 RVO 与 C++ 中的移动语义?

[英]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 &&param); std::move (至少主要)在你有参数或右值引用类型时使用,如下所示: int foo(int &&param); . . 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,从ab初始化。 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 .相反,它只是传递对ab的引用,并在emplace_back内部传递,当它准备好在vector中创建 object 时,它会创建它,并直接从ab初始化它。

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 ,我们将完全避免这种情况,直接从源数据( ab )初始化 object ,所以我们将复制它们一次——但只复制一次。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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