Some differences between xvalue and prvalue

254 Views Asked by At

I've been carefully studying C++ catogories recently. The difference between lvalue and rvalue seems to be clear, but I got confused when it comes to prvalue and xvalue.
Given the example below:

#include <iostream>
using std::cout;
using std::endl;
using std::move;
class Type {
public:
    int value;
    Type(const int &value=0) :value(value) {}
    Type(const Type &type) :value(type.value){}
    Type(Type &&type) noexcept :value(type.value) {}
    Type &operator= (const Type &type) {
        value = type.value;
        return *this;
    }
    Type &operator=(Type &&type) noexcept{
        value = type.value;
        return *this;
    }
};
Type foo1(const Type &value) {
    return Type(value);
}
Type &&foo2(const Type &value) {
    return Type(value);
}
int main() {
    Type bar1(123);
    cout << foo1(bar1).value << endl;
    cout << foo2(bar1).value << endl;
    Type bar2;
    bar2 = foo1(bar1);
    cout << bar2.value << endl;
    bar2 = foo2(bar1);
    cout << bar2.value << endl;
    return 0;
}

Running the example, the console puts:
123
123
123
-858993460
Can anyone explan why it gives an unexpected value in the last output?
What feature does this example show about xvalue?

2

There are 2 best solutions below

3
On BEST ANSWER

foo2 is returning reference bound to temporary which is destroyed immediately; it always returns a dangled reference.

a temporary bound to a return value of a function in a return statement is not extended: it is destroyed immediately at the end of the return expression. Such function always returns a dangling reference.

Dereference on the returned reference like foo2(bar1).value and bar2 = foo2(bar1); leads to UB; anything is possible.

On the other hand, foo1 doesn't have such issue. The return value is moved from the temporary object.

0
On

Here's a very simple explanation. Consider this textbook example from pre-C++0x times:

T& f()
{
    T t;
    return t;
}

// ...

T& val = f();
cout << val; // <--- SIGSEGV here

You are not surprised that this snippet crashed, right? You returned a reference (which is, under the hood, nothing more than a glorified pointer) to a local object which was destroyed before the function even returned, thus dooming val to become a dangling reference.

Now consider:

T&& f()
{
    T t;
    return static_cast<T&&>(t);
}

// ...

T&& val = f();
cout << val; // <--- SIGSEGV here

In terms of object lifetimes, this is exactly the same as before. Rvalue reference is not some brand new tool for magically moving objects around. It's the same old reference (read: glorified pointer). Nothing changed - you still returned an address of a destroyed object.

And I assume everyone is on board with that std::move is nothing more than a glorified static_cast<T&&>, so in case it's used the result is the same:

T&& f()
{
    T t;
    return std::move(t);
}

// ...

T&& val = f();
cout << val; // <--- SIGSEGV here

This example is 99.9% identical to the previous one.

(The 0.1% difference is, e.g., in that in the first two examples GCC knows that the code is screwed up and actually returns a null reference that's guaranteed to crash on first use; while in the last example, since std::move is a function, it can't be sure so it obediently returns the bad address).