问题
In the following C++11+ code which return statement construction should be preferred?
#include <utility>
struct Bar
{
};
struct Foo
{
Bar bar;
Bar get() &&
{
return std::move(bar); // 1
return bar; // 2
}
};
回答1:
Well, since it's a r-value ref qualified member function, this
is presumably about to expire. So it makes sense to move bar
out, assuming Bar
actually gains something from being moved.
Since bar
is a member, and not a local object/function parameter, the usual criteria for copy elision in a return statement don't apply. It would always copy unless you explicitly std::move
it.
So my answer is to go with option number one.
回答2:
I prefer option 3:
Bar&& get() &&
// ^^
{
return std::move(bar);
}
and, while we're at it:
Bar& get() & { return bar; }
Bar const& get() const& { return bar; }
Bar const&& get() const&& { return std::move(bar); }
We're an rvalue, so it should be free to cannibilize our resources, so move
-ing bar
is correct. But just because we're open to moving bar
doesn't mean we have to mandate such a move and incur extra operations, so we should just return an rvalue reference to it.
This is how the standard library do - e.g. std::optional<T>::value.
回答3:
I would like to clarify my point (from comments). Even though moving result should in general be considerably more efficient than copying, it is not my primary concern here. The core issue arises from false assumption that by calling this method on r-value reference to Foo
instance caller's intentions include creation of a new Bar
value. For example:
Foo Produce_Foo(void);
// Alright, caller wanted to make a new `Bar` value, and by using `move`
// we've avoided a heavy copy operation.
auto bar{Produce_Foo().get()};
// Oops! No one asked us to make a useless temporary...
cout << Produce_Foo().get().value() << endl;
The solution would be to add a dedicated functions to be used just to take a peek at stored bar and to take control over content of stored bar object.
Bar const & get_bar() const noexcept
{
return bar;
}
// no particular need to this method to be r-value reference qualified
// because there is no direct correlation between Foo instance being moved / temp
// and intention to take control over content of stored bar object.
Bar give_bar() noexcept
{
return ::std::move(bar);
}
Now that user has a choice there will be no more problems:
// Alright, caller wanted to make a new `Bar` value, and by using `move`
// we've avoided a heavy copy operation.
// There is also no need to figure out whether Produce_Foo returned an rvalue or not.
auto bar{Produce_Foo().give_bar()};
// Alright, no extra temporaries.
cout << Produce_Foo().get_bar().value() << endl;
As for use cases for r-value reference qualified methods, I think they are mostly useful when dealing with temporaries of the same type as this object. e.g. string class implementing such concatenation operator can reduce amount of reallocations, essentially performing like a dedicated string builder.
来源:https://stackoverflow.com/questions/48485885/to-move-or-not-to-move-from-r-value-ref-qualified-method