Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why does std::vector::insert invalidate all iterators after the insertion point

Tags:

c++

stdvector

When insert-ing into a std::vector the C++ standard assures that all iterators before the insertion point remain valid as long as the capacity is not exhausted (see [23.2.4.3/1] or std::vector iterator invalidation).

What is the rationale behind not allowing iterators after the insertion point to remain valid (if the capacity is not exhausted)? Of course, they would then point to a different element but (from the presumed implementation of std::vector) it should still be possible to use such an iterator (for example dereference it or increment it).

like image 407
Toby Brull Avatar asked Jul 27 '13 13:07

Toby Brull


1 Answers

You seem to be thinking of an "invalid" iterator as only one that would provoke a crash if used, but the standard's definition is broader. It includes the possibility that the iterator can still safely be dereferenced, but no longer points to the element it is expected to point to. (This is a special case of the observation that "undefined behavior" does not mean "your program will immediately crash"; it can also mean "your program will silently compute the wrong result" or even "nothing observably wrong will occur on this implementation.")

It is easier to demonstrate why this is an issue with erase:

#include <vector>
#include <iostream>
int main(void)
{
    std::vector<int> a { 0, 1, 2, 3, 4, 4, 6 };

    for (auto p = a.begin(); p != a.end(); p++) // THIS IS WRONG
        if (*p == 4)
            a.erase(p);

    for (auto p = a.begin(); p != a.end(); p++)
        std::cout << ' ' << *p;

    std::cout << '\n';
}

On typical implementations of C++ this program will not crash, but it will print 0 1 2 3 4 6, rather than 0 1 2 3 6 as probably intended, because erasing the first 4 invalidated p -- by advancing it over the second 4.

Your C++ implementation may have a special "debugging" mode in which this program does crash when run. For instance, with GCC 4.8:

$ g++ -std=c++11 -W -Wall test.cc && ./a.out
 0 1 2 3 4 6

but

$ g++ -std=c++11 -W -Wall -D_GLIBCXX_DEBUG test.cc && ./a.out
/usr/include/c++/4.8/debug/safe_iterator.h:307:error: attempt to increment 
    a singular iterator.

Objects involved in the operation:
iterator "this" @ 0x0x7fff5d659470 {
type = N11__gnu_debug14_Safe_iteratorIN9__gnu_cxx17__normal_iteratorIPiNSt9__cxx19986vectorIiSaIiEEEEENSt7__debug6vectorIiS6_EEEE (mutable iterator);
  state = singular;
  references sequence with type `NSt7__debug6vectorIiSaIiEEE' @ 0x0x7fff5d659470
}
Aborted

Do understand that the program provokes undefined behavior either way. It is just that the consequences of the undefined behavior are more dramatic in the debugging mode.

like image 109
zwol Avatar answered Nov 15 '22 07:11

zwol