Назначение (operator =) делает недействительными итераторы для контейнеров

У меня есть код просто так:

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;   

Приведенный выше код выдаст ошибку: итератор не разыменовывается.

Однако, если я заменим вектор списком следующим образом:

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;   

Код просто запустился, как и ожидалось, без ошибок.
Из правил аннулирования итераторов и std::list::operator= мне сказали, что после вызова operator = все итераторы, ссылки и указатели, связанные с этим контейнером, становятся недействительными, кроме конечных итераторов. Но почему вышеуказанный код с std:: list работает? Я неправильно понял что-то важное?

2 ответа

Решение

Когда итератор признан недействительным, разыменование - это неопределенное поведение. Следовательно, любое поведение, которое вы наблюдали при этом, не имеет отношения к спецификации. "Работа", согласно вашим ожиданиям, относится к числу допустимых форм поведения.

FWIW (не так много, тбч), я бы ожидал std::list Оператор присваивания должен быть реализован эквивалентно что-то вроде этого:

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;
}

И если это так, вы можете видеть, что для такого случая, как ваш, когда списки имеют одинаковое количество элементов, элементы не создаются и не уничтожаются, и поэтому вы очень ожидаете, что итераторы будут работать точно так же, как обычно, Это, конечно, полностью спекуляция, и определенно не поведение, на которое вы должны полагаться, потому что даже если это имеет место для вашей реализации, они могут изменить его в следующем выпуске без предварительного уведомления.

Это неопределенное поведение, но в GCC есть контейнеры для отладки, чтобы уловить такое поведение.

Включить с -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
Другие вопросы по тегам