Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

To move, or not to move from r-value ref-qualified method?

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
    }
};
like image 774
Constructor Avatar asked Jan 28 '18 11:01

Constructor


3 Answers

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.

like image 193
StoryTeller - Unslander Monica Avatar answered Nov 20 '22 07:11

StoryTeller - Unslander Monica


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.

like image 4
Barry Avatar answered Nov 20 '22 06:11

Barry


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.

like image 1
user7860670 Avatar answered Nov 20 '22 08:11

user7860670