首页 > 解决方案 > 避免基于反向范围的 for 循环实现的悬空引用

问题描述

背景和以前的搜索

我正在寻找一种优雅的方法来使用 C++14 中基于范围的 for 循环对容器(例如 std::vector)进行反向迭代。寻找解决方案我发现了这个 Q/A。它基本上告诉我,这不是标准库的一部分,我必须自己使用 boost 或实现一个适配器。我不想使用 boost,所以我现在正在寻找最好的自己的实现。

除了前面提到的 Q/A 中给出的建议外,我还找到了这个实现和关于这个主题的博客。大多数实现都非常相似,看起来相当不错。但是它们都有一个陷阱:正如在此评论中指出的那样,如果您使用临时对象调用反向适配器,您最终可能会得到一个悬空引用:

for (const auto& v : reverse_iterate(getContainer()))

关于基于范围的for循环中临时对象的问题,这个答案确实帮助了我的理解。但是我们能做些什么来防止悬空引用呢?

我的解决方案

基于这个背景,我正在寻找一个摆脱这个陷阱的实现。在下面的实现中,我使用了一个额外的右值引用rx_来延长我的输入参数的生命周期,iffreverse_iterate是用右值引用调用的。

编辑:不要使用这个解决方案。正如已接受的解决方案中指出的那样,这是错误的。

template <typename T>
class reverse_range
{
  T &&rx_; // rvalue-reference to prolong livetime of temporary object
  T &x_; // reference to container

public:
  explicit reverse_range(T &x) : rx_(T{}), x_(x) {}
  explicit reverse_range(T &&rx) : rx_(std::move(rx)), x_(rx_) {}

  auto begin() const -> decltype(this->x_.rbegin())
  {
    return x_.rbegin();
  }  
  auto end() const -> decltype(this->x_.rend())
  {
    return x_.rend();
  }
};

template <typename T>
reverse_range<T> reverse_iterate(T &x)
{
  return reverse_range<T>(x);
}
template <typename T>
reverse_range<T> reverse_iterate(T &&rx)
{
  return reverse_range<T>(std::move(rx));
}

显然,我们在左值构造函数中构造一个未使用的空容器对象会产生一些开销,但我认为这还不错。reverse_range_lvalue此外,可以通过提供两个类和来摆脱这种reverse_range_rvalue情况,每个类都提供一种参数类型的实现......

问题

上述扩展会解决悬空引用问题还是我错过了什么?

您对我的代码的进一步问题有任何提示吗?

在 C++14 或任何其他(未来)版本中是否有更好的想法来解决这个问题?

标签: c++for-loopc++14rvalue-reference

解决方案


那是行不通的。终身扩展在(那部分)构造函数中不起作用。(它在构造函数的主体中起作用,而不是在成员初始化列表中)。

template<class R>
struct backwards_t {
  R r;
  constexpr auto begin() const { using std::rbegin; return rbegin(r); }
  constexpr auto begin() { using std::rbegin; return rbegin(r); }
  constexpr auto end() const { using std::rend; return rend(r); }
  constexpr auto end() { using std::rend; return rend(r); }
};
// Do NOT, I repeat do NOT change this to `backwards_t<std::decay_t<R>>.
// This code is using forwarding references in a clever way.
template<class R>
constexpr backwards_t<R> backwards( R&& r ) { return {std::forward<R>(r)}; }

这在传递右值时会移动,并在传递左值时保持引用。

诀窍在于,对于转发引用T&&,如果T&&是左值T则为引用,如果T&&为右值T则为值。因此,我们将左值转换为引用(并且不复制),同时将右值转换为值(并将右值移动到所述值中)。

for (const auto& v : backwards(getContainer()))

这样就可以了。

中,你可以做得“更好”一些;如果您进行聚合初始化,引用生命周期扩展可以应用于结构的内容。但我建议不要这样做;参考寿命延长在中断时是脆弱和危险的。

或更高版本中有讨论允许编译器将移动到过期对象转换为省略。但我不会打赌它在特定情况下会起作用。我还认为我看到了一篇关于使用它们的生命周期依赖信息标记 ctor 和函数(即,返回值取决于参数的生命周期)、允许警告/错误以及可能更广义的生命周期扩展的论文。

所以这是一个已知问题。但这是当今解决此问题的最佳一般安全方法。


推荐阅读