Сцена первая: Код (Text): std::vector<int> vec; vec.reserve(10); vec.push_back(1); vec.push_back(2); std::vector<int>::const_iterator vend = vec.end(); vec.push_back(3); std::cout << *vend << std::endl; // Вывод '3' Сцена вторая: Код (Text): std::list<int> ls; ls.reserve(10); ls.push_back(1); ls.push_back(2); std::list<int>::const_iterator lend = ls.end(); ls.push_back(3); std::cout << *lend << std::endl; // Ошибка, т.к. lend после вставки всеравно остался end-ом Отсюда вопрос: откуда именно вытекает подобное поведение сохраненного end-итератора? Это: 1. Unspecified behavour? 2. Implementation-defined behavior? Если да то для кого? 3. Вытекает из iterator_category? Почему это происходит с точки зрения реализации я понимаю, это опустим. Интересует в чем состоят требования к поведению с точки зрения Стандарта и в чем они выражаются?
для списка, iterator::end()==null все это для того чтобы обход контейнера итератором выглядел так for(; it != container.end() ;++it)
GoldFinch Бугага. Во-первых, резве я говорил, что реализация не соответствует стандарту? Во-вторых, форум наверно для того и существует, чтобы облегчить поиск информации? Стандарт это 1000-страничный документ на английском языке. Как искать-то предлагаешь? Ctrl-F "Stored end iterator behavior"?
_DEN_ что значит Stored end iterator? это просто итератор указывающий на конец контейнера в данный момент времени если поменять конец контейнера, это уже не будет итератор указывающий на конец контейнера то что он const говорит только о том что ты не будешь менять состояние контейнера
GoldFinch Для вектора - да. Для листа - нет. Мой вопрос в том и состоит, чтобы понять, чем диктуется и откуда вытекает это поведение. Это-то тут причем? PS. Меня _НЕ_ интересует реализация - я прекрасно понимаю как оно устроено и откуда берется такое поведение на примере конкретной платформы, модели памяти и компилятора. Не надо меня учить основам языка и его стандартной библиотеки - я знаю как примеряются итераторы, почему была выбрана концепция end-итераторов и какие она дает удобства, каким образом принято итерировать контейнеры, какие гарантии есть относительно времени жизни итераторов после модификации контейнера, и многое другое. Не надо предлагать смотреть сорцы stl или дизасмить exe-шники. Не нужно на все это тратить время. У меня есть конкретный вопрос: поведение сохраненного end-итератора после push_back зависит от типа контейнера. Чем с точки зрения Стандарта это поведение продиктовано?
_DEN_ Это вытекает из свойств контейнеров: end() возвращает итератор, который по Стандарту может НЕ быть разыменовываемым (dereferenceable). Поэтому *vend и *lend недопустимы.
green Но ведь после .push_back() ситуация несколько меняется? По-твоему работающий *vend - implementation-defined behavior?
green Кстати, мотивационный пример тут не в разыменовании итератора. Код (Text): template <class container> void foo(container& cnt) { typename container::const_iterator beg = cnt.begin(); typename container::const_iterator end = cnt.end(); // Вставка в контейнер. // ... std::copy(beg, end std::ostream_iterator<typename container::value_type>(std::cout << "Было: ")); std::copy(cnt.begin(), cnt.end(), std::ostream_iterator<typename container::value_type>(std::cout << "Стало: ")); } Так что ничего противозаконного мы не делаем, но вопрос поведения сохраненного end-а остается.
_DEN_ Да. Стандарт оговаривает только случаи, когда операция на контейнером может invalidate итераторы этого контейнера. В частности, для вектора и листа гарантируется сохранение валидности всех итераторов при push_back. То, что невалидные итераторы могут случайно стать валидными - обычное дело: например, можно снести и повторно создать контейтер - если менеджер памяти решит расположить его по старому адресу, все итераторы, полученные от старого контейнера, останутся валидными. Но это уже чисто chance-defined behavior.
green Для вектора как раз таки не гарантируется. Векторные итераторы не поломаются только если не произойдет реаллокации памяти, выделенной под вектор. На 100% это можно гарантировать только воспользовавшись функцией .reserve(); И все-таки, мотивационный пример. Разыменований не происходит, однако результат зависит от поведения сохраненного .end().
_DEN_ это и есть ответ на вопрос. в случае с вектором - итератор мог остаться end, а мог начать указывать на валидное значение. и рассчитывать на такое поведение нельзя. в cлучае со списком - все нормально. итератор конца - всегда будет валиден и соответственно указываеть на end
RedLord После push_back остаться end-ом в существующих реализациях он никак не может - либо следующее значение, либо итератор с bad pointer-ом внутри, но никак не end. Можно - reserve();
_DEN_ Да, точно, для вектора Стандарт гарантирует валидность итераторов при отсутствии релокации (size() < capacity()). Дело в том, нет никакого специального end-итератора, который обязательно сохраняет свой end-status независимо от состояния контейнера.
есть два типа контейнеров: последовательные и ассоциативные, для последовательных(в частности вектор) при вставке-удалении все итераторы становятся невалидными, для ассоциативных(в частности лист) -- нет
Velheart Лист и вектор относятся к последовательным контейнерам. Для vector::insert Стандарт гарантирует валидность итераторов при условии size() + numItemsToInsert < capacity() (т.е. при отсутствии релокации). list::insert сохраняет итераторы всегда.
green упс, сорри, облажался, не проверил, т.к. казалось интуитивно понятным, что лист должен быть ассоциативным..