1

I have a code just like this:

std::vector<int> v1 = { 1, 2, 3, 4 };  
std::vector<int> v2 = { 7, 8, 9, 10 };  
std::vector<int>::iterator it = std::next(v1.begin());  
v1 = v2;  
int test = *it;  
std::cout << test;   

The above code will throw an error: iterator not dereferencable.

However, if I replace vector with list as follows:

std::list<int> v1 = { 1, 2, 3, 4 };  
std::list<int> v2 = { 7, 8, 9, 10 };  
std::list<int>::iterator it = std::next(v1.begin());  
v1 = v2;  
int test = *it;  
std::cout << test;   

The code just ran as expected without error.
From Iterator invalidation rules, and the std::list::operator=, I was told that after the call to operator =, all iterators, references and pointers related to this container are invalidated, except the end iterators. But why the above code with std::list works? Did I misunderstand something essential?

Community
  • 1
  • 1

2 Answers2

4

When an iterator is invalidated, dereferencing it is undefined behavior. Therefore, whatever behavior you observed in doing so is irrelevant, as far as the spec is concerned. "Working", according to your expectations, is among the allowable behaviors.

FWIW (not much, tbh), I would expect the std::list assignment operator to be implemented equivalent to something like this:

list& operator=(list const& rhs) {
    if (this == &rhs)
        return *this;

    auto lhs_i = begin();
    auto rhs_i = rhs.begin();

    // write over the elements in any currently existing nodes, this
    // avoids any unnecessary allocations
    while (lhs_i != end() && rhs_i != rhs.end()) {
        *lhs_i++ = *rhs_i++;
    }

    // erase any extra elements if size() > rhs.size()
    erase(lhs_i, end());

    // push back additional elements if size() < rhs.size()
    while (rhs_i != rhs.end()) {
        push_back(*rhs_i++);
    }

    return *this;
}

And if it is, you can see that, for a case such as yours, where the lists both have the same number of elements, no elements are created or destroyed, and so you would very much expect iterators to go on working exactly as normal. This is, of course, entirely speculation, and definitely not behavior which you should rely on because even if it is the case for your implementation, they can change it on the next release without notice.

Benjamin Lindley
  • 95,516
  • 8
  • 172
  • 256
1

It is undefined behavior but GCC has debug containers to catch this kind of behavior.

Enable it with -D_GLIBCXX_DEBUG:

     int test = *it;
         ^~~~
/usr/local/include/c++/6.1.0/debug/safe_iterator.h:270:

Error: attempt to dereference a singular iterator.

Objects involved in the operation:

    iterator "this" @ 0x0x7fff5f561e90 {
      type = __gnu_debug::_Safe_iterator<std::__cxx1998::_List_iterator<int>, std::__debug::list<int, std::allocator<int> > > (mutable iterator);
      state = singular;
      references sequence with type 'std::__debug::list<int, std::allocator<int> >' @ 0x0x7fff5f561ef0
    }
bash: line 7: 16071 Aborted                 (core dumped) ./a.out