我有如下代码:

for (std::list<item*>::iterator i=items.begin();i!=items.end();i++)
{
    bool isActive = (*i)->update();
    //if (!isActive) 
    //  items.remove(*i); 
    //else
       other_code_involving(*i);
}
items.remove_if(CheckItemNotActive);


我想在更新不活动的项目后立即删除它们,以避免再次浏览列表。但是,如果我添加注释行,则在进入i++时出现错误:“列表迭代器不可递增”。我尝试了一些替代方法,这些替代方法在for语句中没有增加,但是我什么也没用。

在移动std :: list时删除项目的最佳方法是什么? br />

评论

我还没有看到任何基于向后迭代的解决方案。我贴了一个这样的。

#1 楼

您必须先增加迭代器(使用i ++),然后再删除前一个元素(例如,使用i ++的返回值)。您可以将代码更改为while循环,如下所示:

std::list<item*>::iterator i = items.begin();
while (i != items.end())
{
    bool isActive = (*i)->update();
    if (!isActive)
    {
        items.erase(i++);  // alternatively, i = items.erase(i);
    }
    else
    {
        other_code_involving(*i);
        ++i;
    }
}


评论


实际上,这不能保证能正常工作。使用“ erase(i ++);”,我们只知道将预增值传递给了aser(),并且i在分号之前而不是在调用ease()之前进行了递增。 “迭代器上一个= i ++;擦除(上一个);”一定可以使用返回值

–詹姆斯·柯伦(James Curran)
09年2月27日在20:03

不,詹姆斯,在调用擦除之前,i会递增,并且先前的值会传递给函数。在调用函数之前,必须对函数的参数进行全面评估。

–布赖恩·尼尔(Brian Neal)
09年2月27日在20:07

// @詹姆斯·柯伦:那是不对的。在调用函数之前,将对所有参数进行完全求值。

–马丁·约克
09年2月28日在0:59

马丁·约克是正确的。调用函数之前,对函数调用的所有参数进行全面评估,没有例外。这就是功能的工作原理。它与您的foo.b(i ++)。c(i ++)示例无关(无论如何都未定义)

–杰夫
09年2月28日在15:59

替代用法i = items.erase(i)更安全,因为它等效于列表,但是如果有人将容器更改为向量,则仍然可以使用。使用向量,delete()将所有内容向左移动以填充孔。如果您尝试使用在擦除后递增迭代器的代码删除最后一个项目,则末端移到左侧,而迭代器移到右侧(越过末端)。然后你崩溃了。

–埃里克·塞帕宁(Eric Seppanen)
2012年5月2日23:55

#2 楼

您想执行以下操作:

i= items.erase(i);


这将正确更新迭代器,使其指向删除迭代器后的位置。

评论


请注意,您不能只是将该代码放入for循环中。否则,每次删除元素时都会跳过一个元素。

– Michael Kristofik
09年2月27日在19:39

他能不能我-每次遵循他的一段代码来避免跳过?

–热情的极客
2012年3月19日15:58

@enthusiasticgeek,如果i == items.begin()会发生什么?

– MSN
2012年3月19日在18:36

@enthusiasticgeek,此时您应该只执行i = items.erase(i);。这是规范形式,已经处理了所有这些细节。

– MSN
2012年3月20日4:18在

迈克尔指出了一个巨大的“陷阱”,我现在不得不处理同样的事情。我发现避免这种情况的最简单方法是将for()循环分解为while()并小心增加

–安妮·奎因(Anne Quinn)
2014年1月14日11:49



#3 楼

您需要结合使用Kristo的答案和MSN的方法:

// Note: Using the pre-increment operator is preferred for iterators because
//       there can be a performance gain.
//
// Note: As long as you are iterating from beginning to end, without inserting
//       along the way you can safely save end once; otherwise get it at the
//       top of each loop.

std::list< item * >::iterator iter = items.begin();
std::list< item * >::iterator end  = items.end();

while (iter != end)
{
    item * pItem = *iter;

    if (pItem->update() == true)
    {
        other_code_involving(pItem);
        ++iter;
    }
    else
    {
        // BTW, who is deleting pItem, a.k.a. (*iter)?
        iter = items.erase(iter);
    }
}


当然,最高效和SuperCool®STL精明的东西应该是这样的:

// This implementation of update executes other_code_involving(Item *) if
// this instance needs updating.
//
// This method returns true if this still needs future updates.
//
bool Item::update(void)
{
    if (m_needsUpdates == true)
    {
        m_needsUpdates = other_code_involving(this);
    }

    return (m_needsUpdates);
}

// This call does everything the previous loop did!!! (Including the fact
// that it isn't deleting the items that are erased!)
items.remove_if(std::not1(std::mem_fun(&Item::update)));


评论


我确实考虑过您的SuperCool方法,但犹豫的是对remove_if的调用并未明确表明目标是处理项目,而不是将它们从活动项目列表中删除。 (这些项不会被删除,因为它们只是变为非活动状态,而不是不必要的)

– AShelly
09年2月27日在22:40

我想你是对的。一方面,我倾向于建议更改“更新”的名称以消除不明确的地方,但事实是,此代码与函子很相似,但也并非毫无意义。

–迈克
09年3月2日,0:35

公平的评论,可以修复while循环以使用end或删除未使用的定义。

–迈克
17年4月12日在21:49

#4 楼

使用std :: remove_if算法。

编辑:
使用集合应类似于:
1。准备收集。
2。流程收集。

如果不混合这些步骤,生活会更轻松。


std :: remove_if。或list :: remove_if(如果您知道使用list而不使用TCollection)
std :: for_each


评论


std :: list具有remove_if成员函数,该函数比remove_if算法更有效(并且不需要“ remove-erase”习惯用法)。

–布赖恩·尼尔(Brian Neal)
09年2月27日在20:03

#5 楼

我已经总结一下,这是带有示例的三种方法:

1。使用while循环

list<int> lst{4, 1, 2, 3, 5};

auto it = lst.begin();
while (it != lst.end()){
    if((*it % 2) == 1){
        it = lst.erase(it);// erase and go to next
    } else{
        ++it;  // go to next
    }
}

for(auto it:lst)cout<<it<<" ";
cout<<endl;  //4 2


2。在列表中使用remove_if成员函数:

list<int> lst{4, 1, 2, 3, 5};

lst.remove_if([](int a){return a % 2 == 1;});

for(auto it:lst)cout<<it<<" ";
cout<<endl;  //4 2


3.。结合使用std::remove_if函数和erase成员函数:

list<int> lst{4, 1, 2, 3, 5};

lst.erase(std::remove_if(lst.begin(), lst.end(), [](int a){
    return a % 2 == 1;
}), lst.end());

for(auto it:lst)cout<<it<<" ";
cout<<endl;  //4 2


4。使用for循环,应注意更新迭代器:

list<int> lst{4, 1, 2, 3, 5};

for(auto it = lst.begin(); it != lst.end();++it){
    if ((*it % 2) == 1){
        it = lst.erase(it);  erase and go to next(erase will return the next iterator)
        --it;  // as it will be add again in for, so we go back one step
    }
}

for(auto it:lst)cout<<it<<" ";
cout<<endl;  //4 2 


#6 楼

这是一个使用for循环的示例,该循环在列表遍历期间被删除的情况下对列表进行迭代并递增或重新验证迭代器。

for(auto i = items.begin(); i != items.end();)
{
    if(bool isActive = (*i)->update())
    {
        other_code_involving(*i);
        ++i;

    }
    else
    {
        i = items.erase(i);

    }

}

items.remove_if(CheckItemNotActive);


#7 楼

Kristo的答案的循环版本的替代者。

您会失去一些效率,在删除时会先后退然后再前进,但是要换取额外的迭代器增量,您可以在循环范围内声明迭代器,并使代码看起来更整洁。选择什么取决于当前的优先级。

答案完全没有时间了,我知道...

typedef std::list<item*>::iterator item_iterator;

for(item_iterator i = items.begin(); i != items.end(); ++i)
{
    bool isActive = (*i)->update();

    if (!isActive)
    {
        items.erase(i--); 
    }
    else
    {
        other_code_involving(*i);
    }
}


评论


这也是我所使用的。但是我不确定如果要删除的元素是容器中的第一个元素是否可以保证正常工作。我认为,对于我来说,它是可行的,但是我不确定它是否可以跨平台移植。

–trololo
13年2月10日在18:12



我没有做“ -1”,但是列表迭代器不能递减吗?至少我有Visual Studio 2008的断言。

– Milesma
13年7月26日在3:52

只要将链表实现为具有头/存根节点的圆形双链表(用作end()rbegin(),并且在将empty用作begin()和rend()时),它将起作用。我不记得我在哪个平台上使用它,但是它也对我有用,因为上面提到的实现是std :: list的最常见实现。但是无论如何,几乎可以肯定这是在利用一些未定义的(按照C ++标准)行为,因此最好不要使用它。

–拉斐尔·加戈(Rafael Gago)
2014年3月26日15:36



关于:迭代器不能递减擦除方法需要一个随机访问迭代器。一些集合实现提供了导致断言的仅前向迭代器。

–杰西·奇斯霍尔姆(Jesse Chisholm)
19年4月24日在21:13

@Jesse Chisholm的问题是关于std :: list的,而不是一个任意的容器。 std :: list提供擦除和双向迭代器。

–拉斐尔·加戈(Rafael Gago)
19/12/29在20:52



#8 楼

删除仅使指向被删除元素的迭代器无效。

因此,在这种情况下,删除* i后,i无效,并且您无法对其进行增量。

您可以做的是先保存要删除的元素的迭代器,然后递增迭代器,然后删除保存的元素。

评论


使用后增量要优雅得多。

–布赖恩·尼尔(Brian Neal)
09年2月27日在20:05

#9 楼

如果您将std::list视为一个队列,则可以将要保留的所有项目出队并入队,而仅出队(而不是入队)要删除的项目。这是一个示例,我想从包含数字1-10的列表中删除5 ...

std::list<int> myList;

int size = myList.size(); // The size needs to be saved to iterate through the whole thing

for (int i = 0; i < size; ++i)
{
    int val = myList.back()
    myList.pop_back() // dequeue
    if (val != 5)
    {
         myList.push_front(val) // enqueue if not 5
    }
}


myList现在将只有数字1-4和6- 10.

评论


有趣的方法,但恐怕它可能会很慢。

– sg7
18年4月9日在1:49

#10 楼

向后迭代避免了擦除要遍历的其余元素上的元素的影响:

typedef list<item*> list_t;
for ( list_t::iterator it = items.end() ; it != items.begin() ; ) {
    --it;
    bool remove = <determine whether to remove>
    if ( remove ) {
        items.erase( it );
    }
}


PS:请参见此,例如,关于向后迭代。 br /> PS2:我没有彻底测试它在末端是否能很好地擦除元件。

评论


re:避免擦除列表中其余元素上的元素的效果,可能是这样。对于矢量来说可能不是。在任意集合上不能保证这一点。例如,地图可能决定重新平衡自己。

–杰西·奇斯霍尔姆(Jesse Chisholm)
19年4月24日在21:20

#11 楼

您可以编写

std::list<item*>::iterator i = items.begin();
while (i != items.end())
{
    bool isActive = (*i)->update();
    if (!isActive) {
        i = items.erase(i); 
    } else {
        other_code_involving(*i);
        i++;
    }
}


可以使用std::list::remove_if编写等效的代码,它不那么冗长,更明确
items.remove_if([] (item*i) {
    bool isActive = (*i)->update();
    if (!isActive) 
        return true;

    other_code_involving(*i);
    return false;
});


如果项是向量而不是列表以保持O(n)的兼容性,则应使用std::vector::erase std::remove_if惯用法-否则如果您编写通用代码并且项可能是没有有效方法擦除单个项的容器(例如向量)

items.erase(std::remove_if(begin(items), end(items), [] (item*i) {
    bool isActive = (*i)->update();
    if (!isActive) 
        return true;

    other_code_involving(*i);
    return false;
}));


#12 楼

执行while循环时,它灵活,快速且易于读写。
auto textRegion = m_pdfTextRegions.begin();
    while(textRegion != m_pdfTextRegions.end())
    {
        if ((*textRegion)->glyphs.empty())
        {
            m_pdfTextRegions.erase(textRegion);
            textRegion = m_pdfTextRegions.begin();
        }
        else
            textRegion++;
    } 


评论


这是低效的-每次删除列表时都会从头开始重新启动列表。

– AShelly
7月13日23:08

#13 楼

我认为您在这里有一个错误,我这样编写:

for (std::list<CAudioChannel *>::iterator itAudioChannel = audioChannels.begin();
             itAudioChannel != audioChannels.end(); )
{
    CAudioChannel *audioChannel = *itAudioChannel;
    std::list<CAudioChannel *>::iterator itCurrentAudioChannel = itAudioChannel;
    itAudioChannel++;

    if (audioChannel->destroyMe)
    {
        audioChannels.erase(itCurrentAudioChannel);
        delete audioChannel;
        continue;
    }
    audioChannel->Mix(outBuffer, numSamples);
}


评论


我猜这对样式首选项是不受欢迎的,因为它似乎可以正常工作。是的,可以肯定,(1)使用额外的迭代器,(2)迭代器增量在循环中处于一个奇怪的位置,没有充分的理由将其放置在其中,(3)在决定删除后,它可以使通道工作像在OP中一样。但这不是一个错误的答案。

–杰西·奇斯霍尔姆(Jesse Chisholm)
19年4月24日在21:26