1

I need a way to quickly access data in a container.

So I remember iterator of that data position. Container maybe modified (elements added and removed) after that, but if I use container type that does not invalidate my iterator (like std::map or std::list) I am fine.

Also my data may not be in the container (yet), so I set an iterator to container.end() to reflect that.

Which standard container guarantees that end() would not change when elements added and removed? So I can still compare my iterator to the value returned by container.end() and not get false negative.

Lightness Races in Orbit
  • 358,771
  • 68
  • 593
  • 989
Slava
  • 40,641
  • 1
  • 38
  • 81
  • 1
    That's undefined and should not be relied on. The normal iterator invalidation rules apply. (Are you trying to prematurely optimize?) – sehe Mar 14 '13 at 16:16
  • 1
    Related: [Iterator invalidation rules](http://stackoverflow.com/questions/6438086/iterator-invalidation-rules) – sehe Mar 14 '13 at 16:16
  • 4
    @sehe If normal iterator invalidation rules apply then it is not undefined. As i mentioned I use container types that does not invalidate my iterator. – Slava Mar 14 '13 at 16:17
  • I also think it's undefined, but if you apply normal invalidation rules, be careful: when you shrink a container, one can consider that the past-last or `end` element is deleted. – Antoine Mar 14 '13 at 16:24
  • 3
    See [this related question](http://stackoverflow.com/questions/11350454/past-the-end-iterator-invalidation-in-c11). Apparently in C++03 normal rules do not apply to `end` and past-last iterators may be invalidated. – Antoine Mar 14 '13 at 16:28
  • @Antoine so if I compile my program with -std=c++11 it will not be invalidated, but with -std=c++03 it may :) ? – Slava Mar 14 '13 at 16:48
  • Lol, well in theory it could but in practice I doubt any compiler/stl implementation would do the additional work just for the sake of confusing you ;) – Antoine Mar 14 '13 at 17:15

3 Answers3

6

23.2.4/9 says of Associative Containers:

The insert and emplace members shall not affect the validity of iterators and references to the container, and the erase members shall invalidate only iterators and references to the erased elements

Now, there are some places where the standard talks about not invalidating "iterators and references to elements of the container", thus excluding end(). I don't believe that this is one of them - I'm pretty sure that an end() iterator is an "iterator to the container".

23.3.5.4/1 says for std::list that insert "Does not affect the validity of iterators and references", and 23.3.5.4/3 says that erase "invalidates only the iterators and references to the erased elements". Again, end() iterators are iterators and so their validity isn't excluded.

One thing to watch out for is that for any container, swap can invalidate end() iterators (I assume this is because there are two "natural" behaviors, either that the end iterator points to the end of the same container or else to the end of the one it was swapped with, but the standard doesn't want to dictate which or rule out other possibilities). But you aren't swapping, just adding and removing elements.

Steve Jessop
  • 257,525
  • 32
  • 431
  • 672
  • I wish I had put these fancy standard quotes in my answer. Maybe I wouldn't have been downvoted to hell by Zadirion, hah! +1 for you, sir. –  Mar 14 '13 at 16:38
3

From my experience, iterators from std::vector and std::dequeue break upon resizing from erasure or addition (this applies to std::string's storage as well). std::list and std::map don't allocate blocks of memory: they usually allocate individual nodes (and most std::unordered_map implementations have buckets as a linked list of items (e.g., std::list)).

If you need to have a surviving end() iterator, choose a std::list (I do this for my Signal/Slots implementation, for their tokens) or do your own personal bookkeeping with std::vector/std::dequeue.

EDIT: So, std::list is a good way to have your iterators be always valid, provided your list itself never dies (which they aren't). From another answer, if you need standardese clarity:

23.3.5.4/1 says for std::list that insert "Does not affect the validity of iterators and references", and 23.3.5.4/3 says that erase "invalidates only the iterators and references to the erased elements". Again, end() iterators are iterators and so their validity isn't excluded. - Another Answer

Community
  • 1
  • 1
  • I suppose that's what I get for saying "From my experience". Oh well. I do say you can do your own personal bookkeeping. –  Mar 14 '13 at 16:23
  • Thanks, but I would like to know if it is guaranteed behavior rather than observed. – Slava Mar 14 '13 at 16:25
  • Everyone is following Zadirion's invalid commend and downvoting perfectly correct answer. Rediculous –  Mar 14 '13 at 16:35
  • @ThePhD I owe you an apology. Your answer is indeed valid. Not sure why I was so sure about all containers invalidating their iterators. You can edit your answer so I can undo my downvote. (Can't undo downvote after >14 minutes). And yes, it is ridiculous that people follow one without thinking for themselves. I'm sorry. – Ed Rowlett-Barbu Mar 14 '13 at 16:38
  • 1
    @aleguna: How does it answer the OPs question for a guarantee? It may be correct, but certainly for a different question. – PlasmaHH Mar 14 '13 at 16:40
  • @PlasmaHH well I believe it is a valid answer, the OP asked for a container type for which end() is not invalidated, and ThePhD rightfully provided one. – Ed Rowlett-Barbu Mar 14 '13 at 16:41
  • 1
    @Zadirion: I don't think that for standard guarantees it is enough to just claim something, you need standard quotes. – PlasmaHH Mar 14 '13 at 16:45
  • @PlasmaHH: The Standard either does or does not guarantee something, irrelevant of whether other people claim it does or doesn't or whether they back those claims up. – Puppy Mar 14 '13 at 17:00
0

Instead of iterators, use a vector and store index values. They'll survive any restructuring. Iterators are primarily intended for use in pairs that designate a range; hanging on to individual iterators gets messy, as you've seen.

Pete Becker
  • 69,019
  • 6
  • 64
  • 147
  • This might not work so well, as if resizes happen you'll have to either A) update all the indices or B) just 'empty' the slot and then have a list of free indices. –  Mar 14 '13 at 16:21
  • Unfortunately this is not a good idea. I may have std::list and keep iterators. After remove elements from it my iterators would be fine. Positions in vector will be broken, so this is not adequate replacement. – Slava Mar 14 '13 at 16:22
  • @ThePhD - vector indices still work after a resize. However, they don't work after removing an element. – Pete Becker Mar 14 '13 at 16:58
  • @Slava - sigh, you **did** say that you need to remove elements. Sorry about that. Still, I stand by (part of) what I said: don't use iterators. Use pointers, and a NULL pointer to indicate no element. – Pete Becker Mar 14 '13 at 17:07
  • @PeteBecker pointer would not work, as I need to remove elements in container, that relevant to my data. Another way would be to scan whole container and find element to remove, but that may be too expensive. – Slava Mar 14 '13 at 17:10