从右值引用限定成员函数按值或按右值引用返回?

Returning by value or by rvalue reference from rvalue reference qualified member function?

Effective Modern C++ 的第 12 项中,Scott Meyers 写了以下内容 class 以展示在引用限定符上重载成员函数的好处:

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;
};

这似乎很清楚:现在 non_temp_obj.data() 将调用第一个重载和 return 对之后仍然存在的对象成员的引用,而 make_temp_obj().data() returns 按值是一个对象的成员,该成员在该表达式完成后立即死亡。

这是我的第一个问题:关于 && 过载,为什么 return std::move(values); 而不仅仅是 return values;,考虑到我们 return价值?

然而,迈耶斯在 errata 中写道

A better way to have the rvalue reference overload of the data member function return an rvalue is to have it return an rvalue reference. That would avoid the creation of a temporary object for the return value, and it would be consistent with the by-reference return of the original data interface near the top of page 84.

我将其解释为建议更改

    DataType data() &&
    { return std::move(values); }

    DataType&& data() &&
    { return std::move(values); }

但我不明白其中的原因,尤其是 this answer 这几乎让我相信书的版本是正确的,勘误表是错误的。

所以我的第二个问题是:谁是对的?

values是一个对象成员,也是一个左值,所以如果你直接return values,它会被复制到return值,不会移动。 && ref-qualified 重载的要点是避免制作不必要的副本。 return std::move(values) 通过将 values 转换为右值来实现这一点,这样它就被移动而不是被复制。

对于你问题的第二部分:两者各有优缺点。作为您链接注释的答案,returning 按 && 重载的值避免生命周期问题,因为如果引用立即绑定到 returned 对象,其生命周期将延长。另一方面,按值 returning 可能会意外地破坏 values 的值。例如:

DataType Widget::data() &&
{ return std::move(values); }

void func() {
    Widget foo;
    std::move(foo).data(); // Moves-constructs a temporary from
                           // foo::value, which is then immediately
                           // destroyed.
    auto bar = foo.data(); // Oops, foo::value was already moved from
                           // above and its value is likely gone.
}