返回右值引用很少是一个好主意。极少数例外情况是您编写的函数在逻辑上等效于std::forward
.
这是一个坏主意的原因是引用生命周期扩展不具有传递性,并且您很容易意外地获得悬空引用。
const auto& foo = some_operation;
如果some_operation
是纯右值表达式,则临时的生命周期延长至foo
. 但是,如果some_operation
是:
foo( some_prvalue_expression )
wherefoo
接受一个右值引用并返回它,而不是你默默地得到一个悬空引用。
对于移动成本低的类型,解决方案很简单:
struct B{
template<class Ar> friend
Ar operator<<(Ar&& ar, A const& a){...; return std::forward<Archive>(ar);}
}
如果传递的是左值,则按引用返回,但如果传递的是右值,则按值返回。
现在参考寿命延长工作正常。
这要求您的类型移动成本低;所以
myarchive_type{} << B{} << B{} << other_unrelated_type{};
导致myarchive_type{}
被移动 3 次。
如果您的类型移动起来不便宜,请考虑简单地阻止右值版本;缺乏安全性使它不值得。
举个具体的例子,看看这个函数:
template<class C>
struct backwards {
C c;
auto begin() const {
using std::rbegin;
return rbegin(c);
}
auto end() const {
using std::rend;
return rend(c);
}
};
template<class C>
backwards(C&&) -> backwards<C>;
(如果我的扣除指南有误,请道歉)。
现在我们这样做:
for( auto x : backwards{ std::vector<int>{1,2,3} } ) {
std::cout << x << ",";
}
被vector
移入backwards
,但如果我们这样做:
auto vec = std::vector<int>{1,2,3};
for( auto x : backwards{ vec } ) {
std::cout << x << ",";
}
没有复制。
如果没有“如果我们传递了一个右值,则创建一个副本并返回它”,上面的第一个 for 循环反而有一个悬空引用。
我已经看到了一些建议或想法,建议尝试概括引用生命周期扩展,或者至少向编译器提供提示,即函数的返回值取决于传递给它的参数的生命周期,以便它可以生成错误/以这种方式使用悬空引用时的警告。据我所知,没有一个将它变成 C++ 标准。
在此之前,随便返回一个右值引用对我的血液来说太危险了。
假设您考虑搬家费用高昂。好吧,这行得通:
std::invoke([&](auto&& ar){
dosomething( ar << << B{} << B{} << other_unrelated_type{} );
}, myarchive_type{} );
它获取临时myarchive_type{}
值并将其存储在右值引用中。
然后它将它传递给一个 lambda。在 lambda 内部,名称ar
是一个左值。我们继续在 lambda 中安全地使用它。