2016-05-29 8 views
1

У меня есть код так же, как это:Назначения (оператор =) аннулирует итераторы для контейнеров

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; 

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

ответ

4

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

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

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

+0

И даже если это, кажется, «работает правильно» он может сломаться в любой момент без какого-либо уведомления. –

1

Это неопределенное поведение, но 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