21

What happens when you add elements to a data structure such as a vector while iterating over it. Can I not do this?

I tried this and it breaks:

int main() {
    vector<int> x = { 1, 2, 3 };

    int j = 0;
    for (auto it = x.begin(); it != x.end(); ++it) {
        x.push_back(j);
        j++;
        cout << j << " .. ";
    }
}
das-g
  • 8,581
  • 3
  • 33
  • 72
user620189
  • 2,445
  • 7
  • 18
  • 19

6 Answers6

17

Iterators are invalidated by some operations that modify a std::vector.

Other containers have various rules about when iterators are and are not invalidated. This is a post (by yours truly) with details.

By the way, the entrypoint function main() MUST return int:

int main() { ... }
Lightness Races in Orbit
  • 358,771
  • 68
  • 593
  • 989
  • 3
    This isn't really correct. Iterators are invalidated by certain operations which modify `std::vector`. Modifying the contents of an `std::vector`, without modifying its topology, has no effect on the iterators, nor does deleting something after the place where the iterator points, nor does inserting something after the place where the iterator points, **if** the capacity is sufficient. – James Kanze Apr 12 '11 at 18:17
  • 1
    @James: It's correct up to a certain level of abstraction. I provided links to the proper details. – Lightness Races in Orbit Apr 12 '11 at 18:19
  • 7
    return from "main" may be skipped, even in case it return int. please refer the Standard – Oleg Jan 05 '16 at 12:45
  • Wait so isnt this statement `main() MUST return int` wrong. I know you are trying to make a point, but you can still **bold** *italic* and UPPERCASE the word "should" ... – Fantastic Mr Fox Sep 08 '16 at 19:58
  • 1
    @Ben: No, it's wrong. The function always returns `int`, even if you let the `return` statement be implicit. – Lightness Races in Orbit Sep 09 '16 at 00:54
  • 1
    @Ben: I don't understand what you're asking. My comment was wholly factual and its purpose was to respond to _your_ comment in which you questioned the accuracy of my answer, by providing you with the clarification you appeared to be seeking. – Lightness Races in Orbit Sep 09 '16 at 07:57
  • 1
    @Ben: Oh, mind you, I meant to write "No, it's not wrong". Maybe that's the source of the confusion? – Lightness Races in Orbit Sep 09 '16 at 07:59
  • I meant the comment in the answer about main returning int. If you are not suggesting that the op write return int in their code then it is not pertinent to the answer and is unnecessary, there is nothing in the question about this? – Fantastic Mr Fox Sep 09 '16 at 14:04
  • 2
    @Ben: As is appropriate on Stack Overflow, I was pointing out a fact. The question originally (five and a half years ago, when all this was written) had `void main()` in it, which was wrong ([though this was corrected in an edit by someone else last year](https://stackoverflow.com/posts/5638323/revisions)). Satisfied? – Lightness Races in Orbit Sep 09 '16 at 15:00
  • Both links in this answer have gone stale. – Barmar Feb 01 '18 at 22:00
  • @Barmar: Thanks; fixed – Lightness Races in Orbit Feb 05 '18 at 22:38
13

What happens when you add elements to a data structure such as a vector while iterating over it. Can I not to this?

The iterator would become invalid IF the vector resizes itself. So you're safe as long as the vector doesn't resize itself.

I would suggest you to avoid this.

The short explanation why resizing invalidates iterator:

Initially the vector has some capacity (which you can know by calling vector::capacity().), and you add elements to it, and when it becomes full, it allocates larger size of memory, copying the elements from the old memory to the newly allocated memory, and then deletes the old memory, and the problem is that iterator still points to the old memory, which has been deallocated. That is how resizing invalidates iterator.

Here is simple demonstration. Just see when the capacity changes:

std::vector<int> v;
for(int i = 0 ; i < 100 ; i++ )
{
  std::cout <<"size = "<<v.size()<<", capacity = "<<v.capacity()<<std::endl;
  v.push_back(i);
}       

Partial Output:

size = 0, capacity = 0
size = 1, capacity = 1
size = 2, capacity = 2
size = 3, capacity = 4
size = 4, capacity = 4
size = 5, capacity = 8
size = 6, capacity = 8
size = 7, capacity = 8
size = 8, capacity = 8
size = 9, capacity = 16
size = 10, capacity = 16

See the complete output here : http://ideone.com/rQfWe

Note: capacity() tells the maximum number of elements the vector can contain without allocating new memory, and size() tells the number of elements the vector currently containing.

Nawaz
  • 327,095
  • 105
  • 629
  • 812
3

It's not a good idea to do it.

You could think about the case where your vector would need to be resized after a push_back. It would then need to be moved to a bigger memory spot and your iterators would now be invalid.

Drahakar
  • 5,668
  • 6
  • 39
  • 57
3

It's a bad idea in general, because if the vector is resized, the iterator will become invalid (it's wrapping a pointer into the vector's memory).

It's also not clear what your code is really trying to do. If the iterator somehow didn't become invalid (suppose it was implemented as an index), I'd expect you to have an infinite loop there - the end would never be reached because you're always adding elements.

Assuming you want to loop over the original elements, and add one for each, one solution would be to add the new elements to a second vector, and then concatenate that at the end:

vector<int> temp;

// ...

// Inside loop, do this:
temp.push_back(j);

// ...

// After loop, do this to insert all new elements onto end of x
x.insert(x.end(), temp.begin(), temp.end());
Luke Halliwell
  • 7,199
  • 6
  • 27
  • 30
2

While you used vector as an example, there are other stl containers which are able to have elements pushed-back without invalidating iterators. Pushing back an element into a std::list doesn't require any re-allocation of existing elements as they aren't stored contiguously (lists instead comprise of nodes linked together by pointers to the next node), therefore iterators remain valid as the node they internally point to still resides at the same address.

Xoanon
  • 23
  • 3
1

if you need to do it this way, you can reserve the maximum number of records you could add. this will stop the vector from needing to resize, and this should prevent crashes

dmjalund
  • 235
  • 2
  • 8