避免在基于反向范围的for循环实现中悬挂参考

Avoid dangling reference for reverse range-based for-loop implementation

本文关键字:实现 循环 参考 for 于反 范围      更新时间:2023-10-16

背景和以前的搜索

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

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

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

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

我的解决方案

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

编辑:不要使用此解决方案。正如公认的解决方案所指出的那样,这是错误的。

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

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

问题

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

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

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

这行不通。 生存期延长在(该部分(构造函数中不起作用。 (它在构造函数的主体中工作,只是不在成员初始值设定项列表中(。

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()))

这样就行了。

在 c++17 中,你可以做得"更好"一点;如果你进行聚合初始化,引用生存期延长可以应用于结构的内容。 但我建议不要这样做;参考寿命延长在断裂时是脆弱和危险的。

在 c++20 或更高版本中,有讨论允许编译器将即将过期的对象转换为省略函数。 但我不会打赌它在特定情况下有效。 我还记得我看过一篇关于用它们的生命周期依赖信息标记 ctor 和函数的论文(即,返回值取决于参数的生命周期(,允许警告/错误,也许还有更通用的生命周期延长。

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