12

Is there any advantage to using an r value reference when you create an object, that would otherwise be in a normal local variable?

Foo&& cn = Foo();
cn.m_flag = 1;
bar.m_foo = std::move(cn);
//cn is not used again

Foo cn;
cn.m_flag = 1;
bar.m_foo = std::move(cn); //Is it ok to move a non rvalue reference?
//cn is not used again

In the first code snippet, it seems clear that there will not be any copies, but I'd guess that in the second the compile would optimize copies out?

Also in the first snippet, where is the object actually stored in memory (in the second it is stored in the stack frame of the enclosing function)?

Jonathan.
  • 53,997
  • 54
  • 186
  • 290
  • Your first part should not even compile. The second part does exactly what you want. std::move makes cn to an rvalue and then moves it to m_foo (assuming the move operators are not deleted etc). – Hayt Sep 07 '16 at 12:40
  • @Hayt, why shouldn't it compile? As I understand it, assigning a temporary to a r value ref, extends the life of the temporary (if it was a normal reference then yes it wouldn't compile) – Jonathan. Sep 07 '16 at 12:44
  • Ah ok. just never saw someone coding that way (it's not really necessary). But still move is mostly made to make an non-rvalue object into an rvalue. – Hayt Sep 07 '16 at 12:50

1 Answers1

11

Those code fragments are mostly equivalent. This:

Foo&& rf = Foo();

is binding a temporary to a reference, which extends the lifetime of the temporary to that of the reference. The Foo will only be destroyed when rf goes out of scope. Which is the same behavior you get with:

Foo f;

with the exception that in the latter example f is default-initialized but in the former example rf is value-initialized. For some types, the two are equivalent. For others, they are not. If you had instead written Foo f{}, then this difference goes away.

One remaining difference pertains to copy elision:

Foo give_a_foo_rv() {
    Foo&& rf = Foo();
    return rf;
}

Foo give_a_foo() {
    Foo f{};
    return f;
}

RVO is not allowed to be performed in the first example, because rf does not have the same type as the return type of give_a_foo_rv(). Moreover, rf won't even be automatically moved into the return type because it's not an object so it doesn't have automatic storage duration, so that's an extra copy (until C++20, in which it's an extra move):

Foo f = give_a_foo_rv(); // a copy happens here!
Foo g = give_a_foo();    // no move or copy

it seems clear that there will not be any copies

That depends entirely on what moving a Foo actually does. If Foo looks like:

struct Foo {
    Foo() = default;
    Foo(Foo const& ) = default;
    Foo& operator=(Foo const& ) = default;

    // some members
};

then moving a Foo still does copying.


And yes, it is perfectly okay to std::move(f) in the second example. You don't need an object of type rvalue reference to T to move from it. That would severely limit the usefulness of moving.

Barry
  • 286,269
  • 29
  • 621
  • 977