1

返回前向(通用)引用的最自然方式是什么?

struct B{
         template<class Ar> friend
/*   1*/ Ar&& operator<<(Ar&& ar, A const& a){...; return std::forward<Archive>(ar);}
         template<class Ar> friend 
/*OR 2*/ Ar& operator<<(Ar&& ar, A const& a){...; return ar;}
         template<class Ar> friend 
/*OR 3*/ Ar  operator<<(Ar&& ar, A const& a){...; return std::forward<Ar>(ar);}
         template<class Ar> friend 
/*OR 4*/ Ar  operator<<(Ar&& ar, A const& a){...; return ar;}
/*OR 5*/ other??
}

我想到的情况是构造并立即使用的类似流的对象。例如:

dosomethign( myarchive_type{} << B{} << B{} << other_unrelated_type{} );
4

1 回答 1

6

返回右值引用很少是一个好主意。极少数例外情况是您编写的函数在逻辑上等效于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 中安全地使用它。

于 2018-09-14T14:08:21.903 回答