Home > Software design >  c using range based to access the vector wrapped in a shared_ptr gives unexpected result
c using range based to access the vector wrapped in a shared_ptr gives unexpected result

Time:10-21

I have the following code, why is the range based output is not what is stored in the vector?

#include <iostream>
#include <memory>
#include <vector>

using namespace std;

shared_ptr<vector<double>> get_ptr() {
    vector<double> v{1, 2, 3, 4, 5};
    return make_shared<vector<double>>(v);
}

int main() {
    auto vec = get_ptr();
    for (int i = 0; i<get_ptr()->size();   i) {
        std::cout << (*get_ptr())[i] << std::endl;
    }
    for (auto v: (*get_ptr())) {
        std::cout << v << std::endl;
    }
}

The output on my ubuntu is something like below,

1
2
3
4
5
4.67913e-310
4.67913e-310
3
4
5

CodePudding user response:

Your code has undefined behavior, range-based for loop is equivalent as

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

For auto && __range = range-expression ;, in your code it'll be auto && __range = (*get_ptr()));. get_ptr() returns by-value, what it returns is a tempraory which will be destroyed after the full expression. After that __range is dangling. And dereference on it leads to UB.

Even for the 1st code snippet, get_ptr() creates new vector every time when it's called in if condition and every iteration. It works just because get_ptr() always creates vector containing same elements and accessing them by index is fine.

Since C 20 we can use temporary range expression for such temporary lifetime issue.

CodePudding user response:

Every time you call get_ptr() you create a new and unique copy of the vector. And that object will be destructed as soon as the full expression involving the call is finished.

So in the case of

for (auto v: (*get_ptr()))

As soon as *get_ptr() is finished, the object will be destructed.

And due to how ranged for loops works, you will iterate using a non-existing vector, leading to undefined behavior.


CodePudding user response:

A range-for loop behaves as if initializing the range to iterate over by a declaration of the form

auto&& range = range-expression;

where range-expression is the expression after the :. In your case:

auto&& range = (*get_ptr());

get_ptr returns by-value and therefore this creates a temporary std::shared_ptr holding the vector. You then dereference it and range will reference the managed vector. However, a temporary lives only until the end of the full-expression in which it was created and therefore the std::shared_ptr returned from the function is immediately after this line destroyed and it will take the managed vector with it since there is no other std::shared_ptr referring to it.

So the actual loop then uses the range reference which is now however dangling, causing undefined behavior.

Since C 20 there is an alternative syntax for range-for loops which allows you to store the temporary return value for the purpose of the loop, avoiding this common pitfall.

for (auto r = get_ptr(); auto v : *r) /*...*/
  • Related