C++ 调用erase时STL迭代器失效的问题

C++ 调用erase时STL迭代器失效的问题,c++,iterator,deque,erase,invalidation,C++,Iterator,Deque,Erase,Invalidation,STL标准定义,当在容器(如std::deque、std::list等)上发生擦除时,迭代器将失效 我的问题如下,假设std::deque中包含的整数列表,以及指示std::deque中元素范围的一对标记,那么删除所有偶数元素的正确方法是什么 到目前为止,我有以下几点,但这里的问题是,假定的结束在擦除后无效: #include <cstddef> #include <deque> int main() { std::deque<int> deq;

STL标准定义,当在容器(如std::deque、std::list等)上发生擦除时,迭代器将失效

我的问题如下,假设std::deque中包含的整数列表,以及指示std::deque中元素范围的一对标记,那么删除所有偶数元素的正确方法是什么

到目前为止,我有以下几点,但这里的问题是,假定的结束在擦除后无效:

#include <cstddef>
#include <deque>

int main()
{
   std::deque<int> deq;
   for (int i = 0; i < 100; deq.push_back(i++));

   // range, 11th to 51st element
   std::pair<std::size_t,std::size_t> r(10,50);

   std::deque<int>::iterator it = deq.begin() + r.first;
   std::deque<int>::iterator end = deq.begin() + r.second;

   while (it != end)
   {
      if (*it % 2 == 0)
      {
         it = deq.erase(it);
      }
      else
        ++it;
   }

   return 0;
}
#包括
#包括
int main()
{
std::deque deq;
对于(inti=0;i<100;deq.push_-back(i++));
//范围,第11至51个元素
标准:对r(10,50);
std::deque::iterator it=deq.begin()+r.first;
std::deque::迭代器end=deq.begin()+r.second;
while(it!=结束)
{
如果(*it%2==0)
{
它=删除(它);
}
其他的
++它;
}
返回0;
}
检查std::remove_是如何实现的,似乎有一个非常昂贵的复制/下移过程正在进行

  • 是否有一种更有效的方法来实现上述目标,而无需所有拷贝/轮班

  • 一般来说,删除/擦除一个元素比将其与序列中的下一个第n个值交换更昂贵(其中n是到目前为止删除/移除的元素数)

注意:答案应该假设序列大小相当大,+1mil元素,平均有1/3的元素需要擦除。

我会使用。我认为维基百科的链接文章甚至显示了你在做什么——删除一些奇怪的元素


如果删除元素,则执行的复制不会比从容器中间删除元素的成本高。它甚至可能更有效。

请记住,deque是一个连续的内存容器(类似于vector,可能是共享实现),因此在容器中删除元素必然意味着在孔上复制后续元素。您只需要确保正在进行一次迭代,并将每个不被删除的对象直接复制到其最终位置,而不是在每次删除过程中逐个移动所有对象
remove\u如果
在这方面是有效和适当的,那么你的
擦除
循环就不是:它会进行大量不必要的复制

FWIW-备选方案:

  • 向对象添加一个“已删除”状态,并将其标记为已删除,但每次对容器进行操作时,都需要检查自己
  • 使用一个列表,该列表是使用指向上一个和下一个元素的指针实现的,这样删除一个列表元素会改变相邻的点以绕过该元素:无复制、高效迭代、无随机访问、更小(即低效)的堆分配和指针开销
选择什么取决于特定操作的性质、相对频率和性能要求(例如,如果在非关键时间执行缓慢的删除,您可能负担得起,但需要尽可能快的迭代-无论是什么,请确保您了解您的需要和各种数据结构的含义)。当您从容器的中间删除一个元素时,该点之后的其他元素必须向下移动一个位置到可用空间中。如果删除多个元素,则每个删除的元素都会产生相应的成本。一些未擦除的元素将移动几个点,但被迫一次移动一个点,而不是一次移动所有点。这是非常低效的

标准库算法
std::remove
std::remove\u if
优化了这项工作。他们使用了一个聪明的技巧来确保每个移动的元素只移动一次。与你的直觉相反,这比你自己做的要快得多

伪代码如下所示:

read_location <- beginning of range.
write_location <- beginning of range.
while read_location != end of range:
    if the element at read_location should be kept in the container:
        copy the element at the read_location to the write_location.
        increment the write_location.
    increment the read_location.

read\u location一个没有提到的替代方法是创建一个新的
deque
,复制要保留在其中的元素,然后用旧的
deque
交换它

void filter(std::deque<int>& in, std::pair<std::size_t,std::size_t> range) {
    std::deque<int> out;
    std::deque<int>::const_iterator first = in.begin();
    std::deque<int>::const_iterator curr = first + range.first;
    std::deque<int>::const_iterator last = first + range.second;
    out.reserve(in.size() - (range.second-range.first));
    std::copy(first, curr, std::back_inserter(out));
    while (curr != last) {
        if (*curr & 1) {
            out.push_back(*curr);
        }
        ++curr;
    }
    std::copy(last, in.end(), std::back_inserter(out));
    in.swap(out);
}
void过滤器(std::deque&in,std::pair range){
std::deque out;
std::deque::const_iterator first=in.begin();
std::deque::const_迭代器curr=first+range.first;
std::deque::const_迭代器last=first+range.second;
out.reserve(in.size()-(range.second range.first));
标准::复制(第一,当前,标准::返回插入器(输出));
while(curr!=上次){
如果(*当前和1){
向外推。向后推(*curr);
}
++咖喱;
}
std::copy(最后一个,in.end(),std::back_inserter(out));
输入、交换(输出);
}

我不确定您是否有足够的内存来创建副本,但创建副本通常比尝试内联擦除大型集合中的元素更快、更容易。如果您仍然看到内存波动,那么通过调用
std::count_If
来确定要保留多少元素,并保留这些元素。这样你就可以有一个单一的内存分配。

如何以一种高效和敏感的方式将一个已删除状态添加到一个包含10mil元素的deque中?@Oxsnarder:但正如所指出的,你可能会有更多的副本在使用单独的
擦除
调用。事实上,从
向量
deque
中间删除元素本质上是低效的。如果您需要经常这样做,最好使用
列表
。在这种情况下,可以使用成员函数
remove\u if
或指定的擦除循环类型。
结束
不会对
列表
无效。我相信
deque::erase
会使所有迭代器无效。擦除不会影响std::list中未擦除元素的迭代器/指针。请参阅此列表:了解完整的失效规则。