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

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


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?

Спасибо ...

Ответы [ 4 ]

21 голосов
/ 11 января 2009

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

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.

4 голосов
/ 11 января 2009

После вызова 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 в локальном пространстве имен?]

0 голосов
/ 11 января 2009

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

0 голосов
/ 11 января 2009

Смотрите это :

for (i = v.begin(); i != v.end(); ) {
  //...
  if (erase_required) {
      i = v.erase(i);
  } else {
      ++i;
  }
}
...