C++ 调用erase时STL迭代器失效的问题
STL标准定义,当在容器(如std::deque、std::list等)上发生擦除时,迭代器将失效 我的问题如下,假设std::deque中包含的整数列表,以及指示std::deque中元素范围的一对标记,那么删除所有偶数元素的正确方法是什么 到目前为止,我有以下几点,但这里的问题是,假定的结束在擦除后无效: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;
#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是到目前为止删除/移除的元素数)
如果删除元素,则执行的复制不会比从容器中间删除元素的成本高。它甚至可能更有效。请记住,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中未擦除元素的迭代器/指针。请参阅此列表:了解完整的失效规则。