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

Почему следующий отпечаток 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 возвращает, но мне интересно, почему это нормально?Или оно не определено или зависит от компилятора?

Ответы [ 5 ]

15 голосов
/ 11 марта 2011

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

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

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

2 голосов
/ 11 марта 2011

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

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

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

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

1 голос
/ 11 марта 2011

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

1 голос
/ 11 марта 2011

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

cout << *i

для печати как 2.

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

1 голос
/ 11 марта 2011

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

извините за предположениехотя

...