Що станеться, якщо ви викликаєте стирання () на елементі карти під час ітерації від початку до кінця?


133

У наступному коді я переглядаю карту і перевіряю, чи потрібно стирати елемент. Чи безпечно стирати елемент і зберігати ітерацію чи мені потрібно збирати ключі в інший контейнер і робити другий цикл для виклику стирання ()?

map<string, SerialdMsg::SerialFunction_t>::iterator pm_it;
for (pm_it = port_map.begin(); pm_it != port_map.end(); pm_it++)
{
    if (pm_it->second == delete_this_id) {
        port_map.erase(pm_it->first);
    }
}

ОНОВЛЕННЯ: Звичайно, я прочитав це запитання, яке не вважав, що воно буде пов'язане, але відповідає на моє запитання.


Зверніть увагу на питання, що std::remove_ifне працюєstd:map
socketpair

Відповіді:


183

C ++ 11

Це було зафіксовано в C ++ 11 (або стирання було вдосконалено / стало узгодженим для всіх типів контейнерів).
Тепер метод стирання повертає наступний ітератор.

auto pm_it = port_map.begin();
while(pm_it != port_map.end())
{
    if (pm_it->second == delete_this_id)
    {
        pm_it = port_map.erase(pm_it);
    }
    else
    {
        ++pm_it;
    }
}

C ++ 03

Стирання елементів на карті не приводить до недійсності ітераторів.
(крім ітераторів на елементі, який було видалено)

Фактично вставлення чи видалення не приводить до недійсності жодного з ітераторів:

Також дивіться цю відповідь:
Техніка Марка Рансома

Але вам потрібно оновити код:
у своєму коді ви збільшуєте pm_it після виклику стирання. На даний момент вже пізно і вже визнано недійсним.

map<string, SerialdMsg::SerialFunction_t>::iterator pm_it = port_map.begin();
while(pm_it != port_map.end())
{
    if (pm_it->second == delete_this_id)
    {
        port_map.erase(pm_it++);  // Use iterator.
                                  // Note the post increment.
                                  // Increments the iterator but returns the
                                  // original value for use by erase 
    }
    else
    {
        ++pm_it;           // Can use pre-increment in this case
                           // To make sure you have the efficient version
    }
}

Чи pm_it++гарантовано порядок оцінки приросту в виразі постфікса, який буде виконаний до введення функції?
Девід Родрігес - dribeas

4
@David Rodríguez - dribeas: Так. Стандарт гарантує, що всі вирази аргументів будуть повністю оцінені до виклику функції. Це результат збільшення посту, який передається функції стирання (). Так що так, збільшення порту pm_it буде виконано до виклику стирання ().
Мартін Йорк

Примітка: Майже лінія для лінії відповідає асоціативному наприклад контейнера в Скотт Майєр «Ефективне СТЛ» Пункт 9.
Ogre Psalm33

for (auto pm_t = port_map.begin (); pm_it! = port_map.end ();) {...}
Андрій Сирокомський

4
@iboisver: На вектор. Використання стирання () приводить до недійсності всіх ітераторів масиву після точки стирання (не лише кінця), це властивість Sequenceконтейнерів. Особливістю властивостей Associativeконтейнерів є те, що ітератори не можуть бути визнані недійсними стиранням чи вставкою (якщо вони не вказують на стираний елемент). Ітератори векторного та стираного стилю детально висвітлюються у відповідному питанні stackoverflow.com/a/3938847/14065
Мартін Йорк

12

Ось як я це роблю ...

typedef map<string, string>   StringsMap;
typedef StringsMap::iterator  StrinsMapIterator;

StringsMap m_TheMap; // Your map, fill it up with data    

bool IsTheOneToDelete(string str)
{
     return true; // Add your deletion criteria logic here
}

void SelectiveDelete()
{
     StringsMapIter itBegin = m_TheMap.begin();
     StringsMapIter itEnd   = m_TheMap.end();
     StringsMapIter itTemp;

     while (itBegin != itEnd)
     {
          if (IsTheOneToDelete(itBegin->second)) // Criteria checking here
          {
               itTemp = itBegin;          // Keep a reference to the iter
               ++itBegin;                 // Advance in the map
               m_TheMap.erase(itTemp);    // Erase it !!!
          }
          else
               ++itBegin;                 // Just move on ...
     }
}

Якщо ви видалите також кінець вектора (itEnd), то остання перевірка (умова в той час) буде проти недійсного ітератора (itEnd). Не добре.
Агостіно

1

Ось як я це зробив, приблизно:

bool is_remove( pair<string, SerialdMsg::SerialFunction_t> val )
{
    return val.second == delete_this_id;
}

map<string, SerialdMsg::SerialFunction_t>::iterator new_end = 
    remove_if (port_map.begin( ), port_map.end( ), is_remove );

port_map.erase (new_end, port_map.end( ) );

Є щось дивне

val.second == delete_this_id

але я просто скопіював це з вашого прикладу коду.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.