删除后的C ++映射迭代

| 我找不到执行此操作的实例,所以我希望有人可以帮助我。我在一个类中定义了一个地图,如下所示:
std::map<std::string, TranslationFinished> translationEvents;
TranslationFinished是boost :: function。我的类中有一个方法会遍历此映射,并像下面这样调用每个函数:
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错误的删除事件代码。立即修复。     
已邀请:
阅读完所有其他答案后,我在这里占有优势……但是在这里。   但是,它可能会被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;
}
请注意,由于回调可以删除容器中的任何元素,因此您不能随便擦除,因为当前的回调可能会删除已访问的迭代器。再说一遍,您可能不在乎将空函子保留一段时间,因此可以忽略它并随即执行go10ѭ。标记为要删除的已访问元素将在下一遍清除。     
map::erase
(显然)使要删除的迭代器无效,但不会使其余映射无效。 这意味着: 如果您删除当前元素之外的任何其他元素,那么您是安全的,并且 如果删除当前元素,则必须首先获取下一个迭代器,以便您可以继续进行迭代(这就是大多数容器的why10 the函数返回下一个迭代器的原因)。
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
}
否则(如果函数可以删除任何元素),可能会变得更加复杂。     
一般来说,在迭代过程中修改集合是不受欢迎的。修改集合时,许多集合会使迭代器无效,包括C#中的许多容器(我知道您在C ++中)。您可以创建要在迭代过程中删除的事件向量,然后再删除它们。     
我的解决方案是先创建一个临时容器,然后将其与原始容器交换。然后,您可以迭代临时容器,然后将要保留的容器插入原始容器。
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;
}
    
您应该有一种在迭代过程中擦除元素的方法,可能有些棘手。
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中的其他元素。因此,后缀++将首先复制迭代器,然后立即增加迭代器,然后返回复制值,这意味着在擦除操作之前增加迭代器,这对于您的要求应该是安全的。     
您可以将删除操作推迟到调度循环之前:
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;
        }
    }
}
一个明显的警告是,如果一个事件被迭代,然后又被删除,则在当前的分发循环中,它没有机会再次被迭代以被删除。 这意味着该事件的实际删除将推迟到下一次运行分发循环时进行。     
问题是ѭ21跟随可能的擦除操作。这对您有用吗?
void BaseSprite::DispatchTranslationEvents()
{
    for(auto it = translationEvents.begin(), next = it;
        it != translationEvents.end(); it = next)
    {
        next=it;
        ++next;
        it->second(this);
    }
}
    

要回复问题请先登录注册