使用C++11的范围for循环和rvalue范围初始化是否安全?

38

假如我有一个返回 std::vector 的函数:

std::vector<int> buildVector();

使用基于范围的 for 循环迭代结果似乎是很自然的:

for (int i : buildVector()) {
  // ...
}

问题:这样做安全吗?

我阅读的标准(实际上是草案n4431)表明,可能不安全,虽然我很难相信委员会没有考虑到这种用法。我希望我的理解是错误的。

第6.5.4节定义了基于范围的for

for ( for-range-declaration : expression ) statement

使用以下的语法糖:

{
  auto && __range = range-init;
  for ( auto __begin = begin-expr,
             __end = end-expr;
        __begin != __end;
        ++__begin ) {
    for-range-declaration = *__begin;
    statement
  }
}

range-init是一个表达式,至少对于类类型,begin-expr可以是__range.begin()begin(__range)

在我的buildVector示例中,我认为range-init会产生一个临时对象,实现允许立即销毁该对象,这意味着在绑定__range引用之后,__range引用可能已经失效。这意味着在评估begin-expr之前,__range引用可能已经失效。

当然,写成这样应该总是安全的:

std::vector<int> notATemporary = buildVector();
for (int i : notATemporary) {
  // ...
}

但我希望我不必把这个问题添加到我的注意事项清单中。

1个回答

28

是的,它是完全安全的。

根据[class.temporary]/4-5:

有两种上下文情况会导致临时变量在不同于完整表达式结束点处被销毁。第一种情况是调用默认构造函数[...]

第二种情况是将引用绑定到临时对象。引用所绑定的临时对象或作为引用绑定的子对象的完整对象在引用的生命周期内持续存在,但以下情况除外:

  • 临时变量绑定到构造函数的引用成员ctor-initializer中[...]
  • 临时变量绑定到函数调用中的引用参数[...]
  • 绑定到函数返回语句中返回值的临时变量的生命周期[...]
  • 临时变量绑定到new-initializer中的一个引用[...]

以上例外都不适用。因此,这个临时变量将持续存在于引用__range的生命周期内,即整个循环。


6
如果您开始使用范围时,一个重要的陷阱可能会让您犯错:如果您编写一个范围适配器,它是从一个右值构造的,并且您想能够在for(:)表达式中链式调用它,那么您需要通过值来存储输入范围,因为引用的寿命延长不是可传递的。使用R&&,存储R - Yakk - Adam Nevraumont
1
@Yakk 是的,尽管我试图改变。事实上,你甚至回答了那个问题 :) - Barry
@yakk 确实,那正是让我开始思考这个问题的情况。 - mbrcknl
1
如果你喜欢使用boost范围适配器,请小心,因为以下代码不安全:`for (int i : buildVector() | boost::adaptors::reversed)` - Llopeth
根据这个答案,我想知道这里给出的理由是否正确?基于范围的for循环似乎在涉及引用生命周期延长时有其自己的规则。特别是:“它似乎是标准中唯一一个隐式绑定引用以扩展表达式结果生命周期而不扩展嵌套临时对象生命周期的部分。” - DancingIceCream

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接