Jonathan. Jonathan. - 1 year ago 50
C++ Question

Creating an object, local variable vs rvalue reference

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)?

Answer Source

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:

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.