Что происходит с итератором STL после его удаления в VS, UNIX/Linux?

Пожалуйста, рассмотрите следующий сценарий:


map(T,S*) & GetMap(); //Forward decleration

map(T, S*) T2pS = GetMap();

for(map(T, S*)::iterator it = T2pS.begin(); it != T2pS.end(); ++it)
{
    if(it->second != NULL)
    {
        delete it->second;
        it->second = NULL;
    }
    T2pS.erase(it);
    //In VS2005, after the erase, we will crash on the ++it of the for loop.
    //In UNIX, Linux, this doesn't crash.
}//for

Мне кажется, что в VS2005 после "стирания" итератор будет равен end(), отсюда и сбой при попытке его инкрементировать. Действительно ли существуют различия между компиляторами в представленном здесь поведении? Если да, то чему будет равен итератор после "стирания" в UNIX/Linux?

Спасибо...


person Gal Goldman    schedule 11.01.2009    source источник
comment
Спросили и ответили: -e" title="что произойдет, если вы вызовете стирание элемента карты при итерации от начала до e"> stackoverflow.com/questions/263945/   -  person Martin York    schedule 11.01.2009


Ответы (4)


Да, если вы удалите итератор, этот итератор получит так называемое единственное значение, что означает, что он больше не принадлежит ни одному контейнеру. Вы больше не можете увеличивать, уменьшать или читать/записывать его. Правильный способ сделать этот цикл:

for(map<T, S*>::iterator it = T2pS.begin(); it != T2pS.end(); T2pS.erase(it++)) {
    // wilhelmtell in the comments is right: no need to check for NULL. 
    // delete of a NULL pointer is a no-op.
    if(it->second != NULL) {
        delete it->second;
        it->second = NULL;
    }
}

Для контейнеров, которые могут сделать недействительными другие итераторы при удалении одного итератора, erase возвращает следующий допустимый итератор. Затем вы делаете это с

it = T2pS.erase(it)

Вот как это работает для std::vector и std::deque, но не для std::map или std::set.

person Johannes Schaub - litb    schedule 11.01.2009
comment
Поправьте меня, если я ошибаюсь, но между ними нет никакой разницы: it = T2pS.erase(it++); так, как это написано в цикле for в вопросе, потому что в обоих случаях он будет недействителен к тому времени, когда вы ++. - person Gal Goldman; 12.01.2009
comment
Хорошо, я поправлю вас, потому что вы ошибаетесь ;-) в erase(it++) аргумент оценивается до вызова стирания, он будет (правильно) увеличен, его предыдущее (не увеличенное) значение возвращается в стирание и тот становится недействительным, но сам уже указывает на следующий элемент. - person Pieter; 12.01.2009
comment
Удаление нулевого указателя - это noop. Нет необходимости проверять значение null. - person wilhelmtell; 12.01.2009
comment
вы могли бы упомянуть, что std::list не делает недействительными какие-либо итераторы, кроме итераторов, указывающих на стертые элементы, при вызове функции Erase() члена. - person wilhelmtell; 12.01.2009
comment
wilhelmtell, я думал, что если я упомяну больше контейнеров, в игре будет только больше путаницы. ваша точка зрения об удалении нулевого указателя хороша, имхо. я добавлю это - person Johannes Schaub - litb; 12.01.2009
comment
В удалить его-›второй; Разве вы не удаляете итератор с приращением сообщения, указывающий на секунду, и я думаю, вы хотите удалить «это» до того, как значение с приращением к сообщению. - person vkaul11; 04.04.2013
comment
для deque, как насчет deque.erase(itor++)? - person Wallace; 07.07.2014

После того, как вы вызовете erase на итераторе в std::map, он становится недействительным. Это означает, что вы не можете использовать его. Попытка использовать его (например, путем увеличения) недействительна и может привести к чему угодно (включая сбой). Для std::map вызов erase на итераторе не делает недействительным любой другой итератор, поэтому (например) после этого вызова (пока it не был T2pS.end()) он будет действительным:

T2pS.erase( it++ );

Конечно, если вы используете этот подход, вы не захотите безоговорочно увеличивать it в цикле for.

Однако в этом примере зачем стирать в цикле for? Почему бы просто не вызвать T2pS.clear() в конце цикла.

С другой стороны, похоже, что у вас есть необработанный указатель «справа» от карты, но карта, похоже, владеет объектом, на который указывает. В таком случае, почему бы не сделать то, что справа от карты, чем-то вроде интеллектуального указателя, например std::tr1::shared_ptr?

[Кстати, я не вижу никаких параметров шаблона для map. Вы определили конкретное воплощение std::map как map в локальном пространстве имен?]

person CB Bailey    schedule 11.01.2009

см. это:

for (i = v.begin(); i != v.end(); ) {
  //...
  if (erase_required) {
      i = v.erase(i);
  } else {
      ++i;
  }
}
person orip    schedule 11.01.2009
comment
Разве сигнатура типа не void erase(iterator pos) ? - person A. Rex; 11.01.2009
comment
Ага! vector::erase(iterator) возвращает другой итератор, а map::erase(iterator) — нет. ОП обсуждал карты, но это тоже полезная информация. - person A. Rex; 11.01.2009
comment
В Visual C++ map::erase(iterator) возвращает итератор, но это нестандартно. - person ChrisN; 11.01.2009

Я думаю, что если вы измените коллекцию, вы аннулируете свой итератор. Вы не можете полагаться на поведение, как вы узнали.

person Jonathan Adelson    schedule 11.01.2009
comment
Контейнеры никогда не делают итераторы недействительными при изменении значения элемента. Когда элемент вставляется или удаляется, некоторые типы контейнеров (например, вектор) делают недействительными итераторы, указывающие на другие элементы, в то время как большинство (например, набор, карта, список) не делают этого. - person j_random_hacker; 11.01.2009