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

1

9

I wrote a c++ function that assembles some data then then returns a std::shared_ptr to a newly allocated std::vector containing the data. Something analogous to this:

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

I tried to iterate over the contents of the vector using a range-based for loop, but it acted as if the vector was empty. After fiddling around, I found I could get it to behave as I expected by assigning the value returned from the function to a local variable, then referencing that in the loop:

// 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;

That snipped prints this:

First loop:
Second loop:
1
2
3

Why does the first version not work?

I’m using Xcode on macOS Sierra 10.12.6. I believe it is using LLVM 9.0 to compile the c++ code.

Scrawly answered 30/4, 2018 at 3:46 Comment(1)
Do you really need shared_ptr around std::vector (as std::vector already manage memory) ? (especially true with provided usage).Decorative
C
9

Note that shared_ptr_to_std_vector_of_ints returns by-value, so what it returns is a temporary.

The range-based for loop is equivalent to

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

The part auto && __range = range_expression ;, for your example it will be auto && __range = *shared_ptr_to_std_vector_of_ints() ;. shared_ptr_to_std_vector_of_ints returns a temporary std::shared_ptr<std::vector<int>>, then dereference on it to get the std::vector<int>, then bind it to the rvalue-reference __range. The temporary std::shared_ptr<std::vector<int>> will be destroyed after the full expression, and use_count decreases to 0 so the std::vector<int> being managed is destroyed too. Then __range becomes dangled reference. After that e.g. auto __begin = begin_expr ; would try to get the iterator from __range, which leads to UB.

(emphasis mine)

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.

As your 2nd version showed, the issue could be solved via using a named variable instead; or you can also use init-statement (from C++20):

for (auto temp = shared_ptr_to_std_vector_of_ints(); int i : *temp) std::cout << i << std::endl;
Cockayne answered 30/4, 2018 at 3:55 Comment(1)
Or without extra name, create a temporary: for (int i : std::vector<int>(*shared_ptr_to_std_vector_of_ints())).Decorative

© 2022 - 2024 — McMap. All rights reserved.