避免基于反向范围的 for 循环实现的悬空引用
Avoid dangling reference for reverse range-based for-loop implementation
背景和以前的搜索
我正在寻找一种在 C++14 中使用基于范围的 for 循环对容器(例如 std::vector)进行反向迭代的优雅方法。在搜索解决方案时,我找到了 this Q/A。它基本上告诉我,这不是标准库的一部分,我必须自己使用 boost 或实现适配器。我不想使用 boost,所以我现在正在寻找最好的自己的实现。
除了 previously mentioned Q/A, I also found this implementation and this blog regarding this topic. Most of the implementations are quite similar and seem quite decent. However they all have a pitfall: As pointed out in this comment 中给出的建议之外,如果您使用临时对象调用反向适配器,您可能最终会得到悬空引用:
for (const auto& v : reverse_iterate(getContainer()))
关于基于范围的 for 循环中临时对象的问题,this answer 确实帮助了我的理解。但是我们可以做些什么来防止悬空引用呢?
我的解决方案
基于这个背景,我正在寻找一个可以摆脱这个陷阱的实现。在下面的实现中,我使用了一个额外的右值引用 rx_
来延长我的输入参数的生命周期 iff reverse_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 或任何其他(未来)版本中是否有更好的想法来解决这个问题?
那是行不通的。生命周期延长在(那部分)构造函数中不起作用。 (它在构造函数的主体中工作,而不是在成员初始化列表中)。
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中你可以做一点"better";如果您进行聚合初始化,引用生命周期扩展可以应用于结构的内容。但我建议不要这样做;参考寿命延长在中断时是脆弱和危险的。
在 c++20 或更高版本中有讨论允许编译器将过期对象的移动转换为省略。但我不会打赌它在特定情况下会起作用。我还认为我看到了一篇关于用它们的生命周期依赖信息标记 ctors 和函数的论文(即 return 值取决于参数的生命周期),允许 warnings/errors 并且可能更普遍的生命周期延长.
所以这是一个已知问题。但这是今天解决这个问题的最好的一般安全方法。
背景和以前的搜索
我正在寻找一种在 C++14 中使用基于范围的 for 循环对容器(例如 std::vector)进行反向迭代的优雅方法。在搜索解决方案时,我找到了 this Q/A。它基本上告诉我,这不是标准库的一部分,我必须自己使用 boost 或实现适配器。我不想使用 boost,所以我现在正在寻找最好的自己的实现。
除了 previously mentioned Q/A, I also found this implementation and this blog regarding this topic. Most of the implementations are quite similar and seem quite decent. However they all have a pitfall: As pointed out in this comment 中给出的建议之外,如果您使用临时对象调用反向适配器,您可能最终会得到悬空引用:
for (const auto& v : reverse_iterate(getContainer()))
关于基于范围的 for 循环中临时对象的问题,this answer 确实帮助了我的理解。但是我们可以做些什么来防止悬空引用呢?
我的解决方案
基于这个背景,我正在寻找一个可以摆脱这个陷阱的实现。在下面的实现中,我使用了一个额外的右值引用 rx_
来延长我的输入参数的生命周期 iff reverse_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 或任何其他(未来)版本中是否有更好的想法来解决这个问题?
那是行不通的。生命周期延长在(那部分)构造函数中不起作用。 (它在构造函数的主体中工作,而不是在成员初始化列表中)。
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中你可以做一点"better";如果您进行聚合初始化,引用生命周期扩展可以应用于结构的内容。但我建议不要这样做;参考寿命延长在中断时是脆弱和危险的。
在 c++20 或更高版本中有讨论允许编译器将过期对象的移动转换为省略。但我不会打赌它在特定情况下会起作用。我还认为我看到了一篇关于用它们的生命周期依赖信息标记 ctors 和函数的论文(即 return 值取决于参数的生命周期),允许 warnings/errors 并且可能更普遍的生命周期延长.
所以这是一个已知问题。但这是今天解决这个问题的最好的一般安全方法。