Подтвердить что ты не робот

Итерация на языке С++ с удалением

Я не мог найти пример, как это сделать, поэтому я надеялся, что кто-то сможет мне помочь. У меня есть карта, определенная в классе следующим образом:

std::map<std::string, TranslationFinished> translationEvents;

TranslationFinished - это функция boost::. У меня есть метод как часть моего класса, который выполняет итерацию через эту карту, вызывая каждую из следующих функций:

void BaseSprite::DispatchTranslationEvents()
{
    for(auto it = translationEvents.begin(); it != translationEvents.end(); ++it)
    {
        it->second(this);
    }
}

Однако возможно, чтобы функция, называемая it->second(this);, удаляла элемент из карты translationEvents (обычно сама по себе), используя следующую функцию:

bool BaseSprite::RemoveTranslationEvent(const std::string &index)
{
    bool removed = false;
    auto it = translationEvents.find(index);
    if (it != translationEvents.end())
    {
        translationEvents.erase(it);
        removed = true;
    }
    return removed;
}

это приводит к сбою утверждения отладки, когда DispatchTranslationEvents() пытается увеличить итератор. Есть ли способ безопасно перебирать карту с возможностью того, что вызов функции во время итерации может удалить элемент с карты?

Заранее спасибо

ИЗМЕНИТЬ: Неправильно C/Pd неправильно удалить код события. Исправлено.

4b9b3361

Ответ 1

Прочитав все остальные ответы, я нахожусь здесь в выигрыше... Но здесь все идет.

Однако это возможно для функции, называемой it- > second (this); удалить элемент из карты translationEvents (обычно сам)

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

Удаление текущего обратного вызова

В более простом случае, когда обратный вызов может удалять сам, вы можете использовать разные подходы:

// [1] Let the callback actually remove itself
for ( iterator it = next = m.begin(); it != m.end(); it = next ) {
   ++next;
   it->second(this);
}
// [2] Have the callback tell us whether we should remove it
for ( iterator it = m.begin(); it != m.end(); ) {
   if ( !it->second(this) ) {                   // false means "remove me"
      m.erase( it++ );
   } else {
      ++it;
   }
}

Среди этих двух вариантов я бы явно предпочел [2], поскольку вы отключаете обратный вызов от реализации обработчика. То есть обратный вызов в [2] ничего не знает о контейнере, в котором он хранится. [1] имеет более высокую связь (обратный вызов знает о контейнере) и сложнее рассуждать о том, что контейнер изменен из нескольких мест в коде. Некоторое время спустя вы даже можете оглянуться назад на код, подумайте, что это странный цикл (не помня, что обратный вызов удаляет себя) и реорганизуйте его в нечто более разумное, как for ( auto it = m.begin(), end = m.end(); it != end; ++it ) it->second(this);

Удаление других обратных вызовов

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

void removeElement( std::string const & name ) {
   to_remove.push_back(name);
}
...
for ( iterator it = m.begin(); it != m.end(); ++it ) {
   it->second( this );       // callback will possibly add the element to remove
}
// actually remove
for ( auto it = to_remove.begin(); it != to_begin.end(); ++it ) {
   m.erase( *it );
}

Если удаление элементов должно быть немедленным (т.е. они не должны вызываться даже в этой итерации, если они еще не были вызваны), вы можете изменить этот подход, проверив, был ли он отмечен для удаления перед выполнением вызова, Знак может быть выполнен двумя способами, общий из которых будет изменять тип значения в контейнере как pair<bool,T>, где bool указывает, жив ли он или нет. Если, как в этом случае, содержащийся объект может быть изменен, вы можете просто сделать это:

void removeElement( std::string const & name ) {
   auto it = m.find( name );           // add error checking...
   it->second = TranslationFinished(); // empty functor
}
...
for ( auto it = m.begin(); it != m.end(); ++it ) {
   if ( !it->second.empty() )
      it->second(this);
}
for ( auto it = m.begin(); it != m.end(); ) { // [3]
   if ( it->second.empty() )
      m.erase( it++ );
   else
      ++it;
}

Обратите внимание, что поскольку обратный вызов может удалить любой элемент в контейнере, вы не сможете удалить его, когда текущий обратный вызов может удалить уже посещенный итератор. Опять же, вы можете не беспокоиться о том, чтобы оставить пустые функторы какое-то время, так что было бы нормально просто игнорировать его и выполнять erase по мере того, как вы идете. Уже пройденные элементы, отмеченные для удаления, будут очищены в следующем проходе.

Ответ 2

map::erase аннулирует удаление итератора (очевидно), но не остальную часть карты. Это означает, что:

  • Если вы удалите любой элемент, отличный от текущего, вы будете в безопасности и
  • Если вы удаляете текущий элемент, вы должны сначала получить следующий итератор, чтобы вы могли продолжить его повторение (поэтому функция erase для большинства контейнеров возвращает следующий итератор). std::map нет, поэтому вам нужно сделать это вручную)

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

for(auto it = translationEvents.begin(); it != translationEvents.end();)
{
    auto next = it;
    ++next; // get the next element
    it->second(this); // process (and maybe delete) the current element
    it = next; // skip to the next element
}

В противном случае (если функция может удалить любой элемент), это может немного усложниться.

Ответ 3

Вообще говоря, он неодобрительно модифицирует коллекцию во время итерации. Многие коллекции аннулируют итератор при изменении коллекции, включая многие из контейнеров в С# (я знаю, что вы на С++). Вы можете создать вектор событий, которые хотите удалить во время итерации, а затем удалить их впоследствии.

Ответ 4

Мое решение состоит в том, чтобы сначала создать временный контейнер и заменить его оригинальным. Затем вы можете выполнить итератор через временный контейнер и вставить те, которые хотите сохранить в исходном контейнере.

void BaseSprite::DispatchTranslationEvents()
{
    typedef std::map<std::string, TranslationFinished> container_t;

    container_t tempEvents;
    tempEvents.swap(translationEvents);

    for(auto it = tempEvents.begin(); it != tempEvents.end(); ++it)
    {
        if (true == it->second(this))
            translationEvents.insert(it);
    }
}

И функции TranslationFinished должны возвращать true, если они хотят быть сохранены и возвращать false для удаления.

bool BaseSprite::RemoveTranslationEvent(const std::string &index)
{
    bool keep = false;
    return keep;
}

Ответ 5

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

for(auto it = translationEvents.begin(); it != translationEvents.end();)
{
    //remove the "erase" logic from second call
    it->second(this); 
    //do erase and increase the iterator here, NOTE: ++ action is very important
    translationEvents.erase(it++);         
}

Итератор будет недействителен после удаления элемента, поэтому вы не сможете использовать этот итератор для увеличения действия после его удаления. Однако удаление элемента не повлияет на другой элемент в реализации карты, IIRC. Таким образом, суффикc++ скопирует итератор сначала и увеличит итератор сразу после этого, а затем вернет значение копии, что означает, что итератор увеличен до действия стирания, это должно быть безопасным для вас.

Ответ 6

Вы можете отложить удаление до цикла отправки:

typedef boost::function< some stuff > TranslationFunc;

bool BaseSprite::RemoveTranslationEvent(const std::string &index)
{
    bool removed = false;
    auto it = translationEvents.find(index);
    if (it != translationEvents.end())
    {
        it->second = TranslationFunc(); // a null function indicates invalid event for later
        removed = true;
    }
    return removed;
}

защищать от вызова недопустимого события в самом цикле и очищать любые "удаленные" события:

void BaseSprite::DispatchTranslationEvents()
{
    for(auto it = translationEvents.begin(); it != translationEvents.end();)
    {
        // here we invoke the event if it exists
        if(!it->second.empty())
        {
            it->second(this);
        }

        // if the event reset itself in the map, then we can cleanup
        if(it->second.empty())
        {
            translationEvents.erase(it++); // post increment saves hassles
        }
        else
        {
            ++it;
        }
    }
}

одно очевидное предостережение: если событие повторяется, а затем удалено, он не получит возможность повторить повторение, которое будет удалено во время текущего цикла отправки.

это означает, что фактическое удаление этого события будет отложено до следующего запуска цикла отправки.

Ответ 7

Проблема ++it следует за возможным стиранием. Будет ли это работать для вас?

void BaseSprite::DispatchTranslationEvents()
{
    for(auto it = translationEvents.begin(), next = it;
        it != translationEvents.end(); it = next)
    {
        next=it;
        ++next;
        it->second(this);
    }
}