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

 ̄綄美尐妖づ 提交于 2020-06-27 06:48:06

问题


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

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!