Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Return rvalue reference or temporary object in C++11 [duplicate]

In Effective Modern C++ item 12, there is a sample code about the C++11 functions' reference qualifiers:

class Widget {
public:
    using DataType = std::vector<double>;
    …
    DataType& data() &            // for lvalue Widgets
    { return values; }            // return lvalue

    DataType data() &&            // for rvalue Widgets
    { return std::move(values); } // return rvalue
    …
private:
    DataType values;
};

So why does the second data() rvalue reference overload function return a temporary object DataType but not an rvalue reference DataType&&?

like image 262
sfz Avatar asked Jan 26 '19 13:01

sfz


People also ask

What are Rvalue references in C++11?

In C++11, however, there's a new kind of reference, an "rvalue reference", that will let you bind a mutable reference to an rvalue, but not an lvalue. In other words, rvalue references are perfect for detecting if a value is temporary object or not. Rvalue references use the && syntax instead of just &, and can be const and non-const, ...

Why is my widget object returning an rvalue reference?

In this case, the && override would be called because the Widget object return by buildWidget does not have a name, and is this an rvalue reference. In concrete terms, as soon as the call to data () is done, the underlying Widget object (unnamed here) is going to immediately die (it's lifetime will end).

What is an rvalue in C++?

However once the const keyword was added to the C++, lvalues were split into — rvalue — The expression that refers to a disposable temporary object so they can’t be manipulated at the place they are created and are soon to be destroyed. An address can not be taken of rvalues.

Why can't I return a return value from a function?

Therefore your return value is a reference to an object that no longer exists. It'd be the same as if you did an l-value reference. You can only return a reference (r-value or l-value) if the object you're referring to has lifetime longer than the function. In both your examples, abc has a lifetime limited by the function -- so they're both bad.


Video Answer


2 Answers

The only reason I see would be to avoid creation of dangling reference when the object is the result of a prvalue:

Widget foo();

auto&& x = foo().data();

If foo().data() returned an rvalue reference to values member, x would be a dangling reference, because the result object of foo() is destroyed at the end of the initialization of x (the end of the full-expression).

On the other hand, with data()&& returning by value, x is bound to a temporary materialization that will have the same life time as x. So the dangling reference is avoided.

This return type for data() && is not idiomatic in C++. Usually, accessor functions return a reference and such use-case as the one above will probably raises the "dangling reference alarm" of any code reviewer.

This definition of data()&& is smart but it breaks a common convention.

like image 84
Oliv Avatar answered Oct 19 '22 05:10

Oliv


The & or && after the method serve a special purpose: they serve as a ref value qualifier.

In a sense, all of the things you can put after a function's argument declarations (i.e. after the ) of the parameters) means "only use this method when *this has these qualities".

Most typically usage here is const qualifying: only calling an override in a const context:

class Widget {
void foo() {
cout << "Calling mutable foo\n";
}

void foo() const {
cout << "Calling const foo\n";
}
};

The same thing can be done with && or &, to tell the compiler to select those overrides when *this has matching qualifiers.

But why would you want to do this?

This is useful when you want to differentiate between one of the following:

  • Give out a reference to some internal data in the normal lvalue case
  • Copy out the data in the case where *this is an rvalue reference and will not outlive the calling code's reference.

A common example that bites a lot of people is the use in for loops.

Consider the following code (building on your Widget example):

Widget buildWidget() { return Widget(); }

int main() {
   for (auto i : buildWidget().data()) {
      cout << i << '\n';
   }
   return 0;
}

In this case, the && override would be called because the Widget object return by buildWidget does not have a name, and is this an rvalue reference.

In concrete terms, as soon as the call to data() is done, the underlying Widget object (unnamed here) is going to immediately die (it's lifetime will end). If this && override didn't exist, the lvalue reference into its data member would point to a destructed object. This would be undefined behavior.

In general, this rvalue ref qualifying thing is not a common thing. It really only needs to be used when you run into a situation like this (often when you want to return a value by copy when *this may be destructed after the call).

like image 44
Sam Avatar answered Oct 19 '22 06:10

Sam