0

I'm using VC++2012 to run the following code:

#include <utility>

struct A
{
    int* m_p;

    A() { m_p = new int; }
    ~A() { delete m_p;  }

    A(const A& otherA)
    {
        m_p = new int;

        // BOOM!
        *m_p = *otherA.m_p;
    }
};

A&& CreateA()
{
    A a;
    return std::move(a);
}

int _tmain(int argc, _TCHAR* argv[])
{
    A a2 = CreateA();
    return 0;
}

During the creation of a2 A's copy ctor is called - and crashes, since the source object created in CreateA() is already destroyed. Is this standard behaviour? Could this be a compiler bug??

Notice that if you change a2's type from 'A' to 'const A&' the crash doesn't occur - which reinforces the suspicion that it is indeed a bug. Can anyone shed some light on this?

Note: I'm fully aware this is not the intended usage for rvalue-refs, and this example is contrived. Just hoping to get a better grasp on the behaviour of this new type.

Ofek Shilon
  • 14,734
  • 5
  • 67
  • 101
  • 4
    A reference is a reference, no matter whether it's R or L. And you cannot refer to something that doesn't exist... – Kerrek SB Dec 21 '12 at 16:19
  • possible duplicate of [Can someone please explain move semantics to me?](http://stackoverflow.com/questions/3106110/can-someone-please-explain-move-semantics-to-me) – fredoverflow Dec 21 '12 at 19:11

2 Answers2

2

You cannot access a local variable outside its scope. Rvalue references don't change that: they are still references. The code presented has undefined behaviour because it returns a reference to a local variable and then accesses it.

Don't return rvalue references. That is silly the vast majority of time. Return values instead:

A CreateA()
{
    A a;
    return a; // a move here is automatic
              // unless you are using a compiler with outdated rules like MSVC
    //return std::move(a); // ok, poor MSVC
    // alternatively:
    //return A{}; //or 
    //return A();
}

When you write A const& a2 = CreateA(); nothing crashes, because you don't actually access any object. All you do is grab a dangling reference. However, this code is not even well-formed, it just happens to compile because MSVC has some outdate rules for reference binding.

So, basically, these behaviours are a mix of compiler bugs and undefined behaviour :)

Community
  • 1
  • 1
R. Martinho Fernandes
  • 228,013
  • 71
  • 433
  • 510
  • Note that a2's type is A - it is *not* a reference. I would expect my code to be semantically equivalent to your code: first a copy ctor should be invoked, and only then the dtor of the internal object. Doesn't this sound right? – Ofek Shilon Dec 21 '12 at 16:23
  • @OfekShilon The return type is a reference. And it is a reference to a local variable. Local variables don't survive beyond their scope. – R. Martinho Fernandes Dec 21 '12 at 16:24
  • And I deliberately put a preemptive comment saying I'm fully aware this is not the intended usage for rvalue refs. No need to point out this being silly. – Ofek Shilon Dec 21 '12 at 16:24
  • I originally put the preemptive comment because I thought it would be helpful too. Would you accept answers to your own questions about edge cases and compiler quirks that say 'just don't do that'? – Ofek Shilon Dec 21 '12 at 16:27
  • Yes, I *did*. FWIW, I have actually asked a similar question before, and that was the answer I accepted. – R. Martinho Fernandes Dec 21 '12 at 16:28
  • @OfekShilon: what do you mean? This answer isn't saying "just don't do that", it's explaining why it doesn't work, and saying that because it doesn't work, you shouldn't do that – jalf Dec 21 '12 at 16:28
2

Look at what happens in your code:

  1. CreateA() is called
  2. inside the function, a local variable of type A is created.
  3. you create a rvalue reference pointing to this local variable
  4. you return this rvalue reference
  5. as you return, the object of type A, which the rvalue reference points to, goes out of scope, and gets destroyed
  6. the reference now points to a destroyed object
  7. you try to initialize a2 as a copy of the object that once existed inside the function call

And... that doesn't work. The object you're trying to copy is dead and gone. Undefined behavior.

Don't do that. :)

In C++, references do not affect the lifetime of the referenced object. There is no "I'm pointing at this object, so you can't destroy it!".

Never return references to local objects. It doesn't work, so... just don't do it.

jalf
  • 243,077
  • 51
  • 345
  • 550
  • Well, in c++, references certainly *do* affect the lifetime of the referenced object - if they are const :). Reading about rvalue-references casting semantics (to lvalue-refs and to values) I got the - apparently wrong - impression that the rvalue ref returned from CreateA would be cast to a value type (since it is assigned to a value type). Oh well. thanks. – Ofek Shilon Dec 21 '12 at 16:37
  • @OfekShilon: no, const references affect the lifetime of an unnamed temporary. But not of a local object that has gone out of scope. If `a2` had been a const ref, *and* your function had returned an `A` instead of an `A&&`, then it would have worked. :) You're right that the reference returned from `CreateA` gets converted into a value type, but that happens after the object pointed to by the reference has been destroyed, so it's too late to do any good ;) – jalf Dec 21 '12 at 16:39
  • This is exactly the case where 'in c++, const references affect the lifetime of *the referenced object*'. As you point out, this object is a temporary one. – Ofek Shilon Dec 21 '12 at 16:41
  • Fair enough, but if you want to be pedantic, my claim was that *references* do not do this. I said nothing about *const* references. ;) But yes, I could have been a bit more precise. However this does not apply in your case anyway, because you return a rvalue reference, and not an object – jalf Dec 21 '12 at 16:44