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) /*...*/