Разве стирание std::list::iterator не делает итератор недействительным и не уничтожает объект?

Почему следующее печатает 2?

list<int> l;
l.push_back( 1 );
l.push_back( 2 );
l.push_back( 3 );
list<int>::iterator i = l.begin();
i++;
l.erase( i );
cout << *i;

Я знаю, что возвращает erase, но мне интересно, почему это нормально? Или он не определен, или зависит от компилятора?


person Kiril Kirov    schedule 11.03.2011    source источник


Ответы (5)


Да, это неопределенное поведение. Вы разыменовываете своего рода дикий указатель. Вы не должны использовать значение i после erase.

И да, erase уничтожает объект, на который указывает. Однако для типов POD уничтожение ничего не дает.

erase не присваивает стираемому итератору никакого специального "нулевого" значения, итератор просто больше недействителен.

person Vlad    schedule 11.03.2011
comment
Уничтожает ли стирание действительно объект, на который указывает? Как, например, список указателей, уничтожает ли стирание указатель? Насколько я понимаю, это не так. - person Vite Falcon; 11.03.2011
comment
@Vite: что на самом деле в этом контексте? erase вызывает деструктор цели итератора. Он не обнуляет место в памяти, где находится цель итератора. Имейте в виду, что если ваш список содержит указатели, deleteвведение указателя не равнозначно deleteвведению объекта, на который указывает указатель! Уничтожение указателя фактически не является операцией. - person Vlad; 11.03.2011
comment
@Vite Falcon Он уничтожает указатель... НЕ то, НА ЧТО указывает указатель. - person Mark B; 11.03.2011
comment
@Vite - зависит от того, что вы понимаете под уничтожением. Деструктор вызывается обязательно, всегда (при стирании). Но кажется, что память просто помечена как свободная для простых объектов/переменных. - person Kiril Kirov; 11.03.2011
comment
@Kiril: да, обычно память просто помечается как свободная, как всегда после delete. - person Vlad; 11.03.2011
comment
@Vlad - конечно, похоже на HDD.. 10x - person Kiril Kirov; 11.03.2011
comment
@Kiril: Я думаю, стирание памяти считается пустой тратой времени - точно так же, как и для файловых систем. - person Vlad; 11.03.2011

«уничтожение» объекта означает, что его память освобождается, а его содержимое может быть изменено (в основном, если это делает написанный от руки деструктор и, возможно, в результате сохранения на месте вещей, связанных со свободной памятью). list::erase возвращает вам новый итератор, который вы должны использовать вместо того, который был передан в качестве аргумента (у меня возникнет соблазн сделать i = l.erase(i); привычкой).

Разрушение никоим образом не означает, что память выметается, стирается. Ранее допустимое местоположение по-прежнему допустимо в большинстве случаев с точки зрения ЦП (т. е. он может извлекать значения), но на него нельзя полагаться, поскольку другая операция может повторно использовать это местоположение для любой цели в любое время. время.

Вы вряд ли увидите, что *i вызывает segfault, имхо, хотя это может произойти с более сложными типами, использующими указатели, но вы можете увидеть, что он имеет новые значения.

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

person PypeBros    schedule 11.03.2011
comment
Да, я знаю, как им пользоваться (i = l.erase(i);), мне просто интересно (: - person Kiril Kirov; 11.03.2011
comment
@kiril kirov: Я понял это из вашего вопроса, но подумал, что ответ будет более полным и с этой информацией. Надеюсь, что остальное поможет демистифицировать поведение. - person PypeBros; 11.03.2011

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

извините за домыслы, хотя

person bua    schedule 11.03.2011

Поскольку вы имеете дело со связанным списком, элементы списка не должны располагаться в памяти прямо «позади» друг друга. Если бы вы попытались сделать то же самое с вектором, вы, вероятно (поскольку поведение не определено), испытали бы

cout << *i

печатать как 2.

Однако это не очень безопасный способ программирования. Поэтому, как только вы удалите итератор, не используйте его снова, если только вы не инициализируете его снова с помощью begin() или end() и т. д.

person Woltan    schedule 11.03.2011

Попробуйте скомпилировать свой код с правильными параметрами с помощью хорошего компилятора, а затем запустить его. (С VC++ /D_DEBUG /EHs /MDd кажется достаточным. С g++, по крайней мере, -D_GLIBCXX_CONCEPT_CHECKS -D_GLIBCXX_DEBUG -D_GLIBCXX_DEBUG_PEDANTIC. Оба компилятора в целом нуждаются в еще большем количестве опций.) Это должно привести к сбою. (Это происходит, когда я пробовал.)

person James Kanze    schedule 11.03.2011