作者:Eliza | 来源:互联网 | 2022-12-02 17:35
背景和以前的搜索
我正在寻找一种优雅的方法来在C++ 14中使用基于范围的for循环对容器(例如std :: vector)进行反向迭代.寻找一个解决方案,我发现这个Q/A.它基本上告诉我,这不是标准库的一部分,我必须自己使用boost或实现一个适配器.我不想使用boost,所以我现在正在寻找最好的实现.
除了前面提到的Q/A中提出的建议外,我还发现了这个实现以及关于这个主题的博客.大多数实现非常相似,看起来相当不错.然而,它们都有一个陷阱:正如在这个注释中所指出的,如果你用一个临时对象调用反向适配器,你可能会得到一个悬空引用:
for (const auto& v : reverse_iterate(getContainer()))
关于基于范围的for循环中的临时对象的问题,这个答案确实帮助了我的理解.但是,我们可以做些什么来防止悬空参考呢?
我的解决方案
基于这个背景,我正在寻找一种能够摆脱这种陷阱的实现.在下面的实现中,我使用额外的rvalue-reference rx_
来延长输入参数的生命周期iff reverse_iterate
是用rvalue引用调用的.
编辑:不要使用此解决方案.如公认的解决方案所指出的那样是错误的.
template
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
reverse_range reverse_iterate(T &x)
{
return reverse_range(x);
}
template
reverse_range reverse_iterate(T &&rx)
{
return reverse_range(std::move(rx));
}
显然,我们在lvalue构造函数中生成了一些构造未使用的空容器对象的开销,但我认为这并不算太糟糕.另外一个很可能获得通过提供两类摆脱这种reverse_range_lvalue
和reverse_range_rvalue
,其中每个提供的参数类型之一的执行...
问题
以上扩展是否会解决悬空引用问题或者我是否会遗漏某些内容?
您对我的代码的其他问题有任何提示吗?
在C++ 14或任何其他(未来)版本中是否有更好的想法来解决这个问题?
1> Yakk - Adam ..:
这不起作用.生命周期扩展在(部分)构造函数中不起作用.(它在构造函数的主体中工作,而不是在成员初始化列表中).
template
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>.
// This code is using forwarding references in a clever way.
template
constexpr backwards_t backwards( R&& r ) { return {std::forward(r)}; }
传递rvalue时会执行此操作,并在传递左值时保留引用.
诀窍在于,对于转发引用T&&
,如果T&&
是左值,那么T
是引用,如果T&&
是右值则则T
是值.因此,在将rvalues转换为值(并将rvalue移动到所述值)时,我们将左值转换为引用(并且不进行复制).
for (const auto& v : backwards(getContainer()))
这样才行.
在c ++ 17中你可以做得更好一些; 如果进行聚合初始化,则引用生存期扩展可以应用于结构的内容.但我建议不要这样做; 参考寿命延长在断裂时是脆弱且危险的.
在c ++ 20或更高版本中有讨论允许编译器将移动转换为到期对象成为精简.但我不打赌在特定情况下工作.我还认为我看到一篇关于使用其生命依赖性信息标记ctors和函数的文章(即,返回值取决于参数的生命周期),允许警告/错误以及可能更广泛的生命周期扩展.
所以这是一个已知的问题.但这是今天解决这个问题的最佳安全方法.
这是一个非常聪明和优雅的解决方案.+1