在基于范围的 for 循环中使用 shared_ptr 到 std::vector

using shared_ptr to std::vector in range-based for loop

我写了一个 c++ 函数,它组装了一些数据,然后 returns 一个 std::shared_ptr 到一个新分配的 std::vector 包含数据。与此类似的东西:

std::shared_ptr<std::vector<int>> shared_ptr_to_std_vector_of_ints()
{
    auto v = std::make_shared<std::vector<int>>();
    for (int i = 0; i < 3; i++) v->push_back(i);
    return v;
}

我尝试使用基于范围的 for 循环遍历向量的内容,但它表现得好像向量是空的。在摆弄之后,我发现我可以通过将从函数返回的值分配给局部变量,然后在循环中引用它来让它按照我的预期运行:

// Executes loop zero times:
std::cout << "First loop:" << std::endl;
for (int i : *shared_ptr_to_std_vector_of_ints()) std::cout << i << std::endl;

// Prints three lines, as expected
std::cout << "Second loop:" << std::endl;
auto temp = shared_ptr_to_std_vector_of_ints();
for (int i : *temp) std::cout << i << std::endl;

剪下的打印如下:

First loop:
Second loop:
1
2
3

为什么第一个版本不起作用?

我在 macOS Sierra 10.12.6 上使用 Xcode。我相信它正在使用 LLVM 9.0 来编译 c++ 代码。

注意shared_ptr_to_std_vector_of_intsreturns是按值的,所以returns是临时的

range-based for loop相当于

{
  init-statement
  auto && __range = range_expression ; 
  auto __begin = begin_expr ;
  auto __end = end_expr ;
  for ( ; __begin != __end; ++__begin) { 
    range_declaration = *__begin; 
    loop_statement 
  } 
} 

部分 auto && __range = range_expression ;,对于您的示例,它将是 auto && __range = *shared_ptr_to_std_vector_of_ints() ;shared_ptr_to_std_vector_of_ints returns 一个临时的 std::shared_ptr<std::vector<int>>,然后解引用它以获得 std::vector<int>,然后将它绑定到右值引用 __range。临时 std::shared_ptr<std::vector<int>> 将在完整表达式后被销毁,并且 use_count 减少到 0 因此被管理的 std::vector<int> 也被销毁。然后 __range 变成悬挂引用。在那之后,例如auto __begin = begin_expr ; 会尝试从 __range 获取迭代器,这会导致 UB。

(强调我的)

If range_expression returns a temporary, its lifetime is extended until the end of the loop, as indicated by binding to the rvalue reference __range, but beware that the lifetime of any temporary within range_expression is not extended.

如您的第二个版本所示,可以通过改用命名变量来解决该问题;或者您也可以使用初始化语句(来自 C++20):

for (auto temp = shared_ptr_to_std_vector_of_ints(); int i : *temp) std::cout << i << std::endl;