本文有更新,请移步我的个人博客:https://blog.andyqiao.top/article/17/

  

之前看《C++ Primier》的时候,也解到在顺序型窗口里insert/erase会涉及到迭代器失效的问题,并没有深究。今天写程序的时候遇到了这个问题。

1 莫名其妙的Erase  

  最初我的程序是酱紫的,别说话,我知道这样是有问题的,可这样是最直观的想法

   int arr[]={0,1,2,3,4,5,6,7,8,9,10}; 
  vector<int> a(arr,arr+sizeof(arr)/sizeof(*arr));for (auto it = a.begin(); it != a.end();++it ){
if ((*it)&1){
a.erase(it);
}
}

  没错,程序崩溃!删除了迭代器it之后,it迭代器失效了,无法再进行++it操作了。

  可是,当我觉得erase做的只是把it之后的元素向前移动一个位置而已,为什么迭代器失效了呢?我翻开《STL源码剖析》,SGI STL的vector<T,Alloc>::erase的源码是这样的:

iterator vector<T, Alloc>::erase(iterator position)
{
if (position + != end())
copy(position + , finish, position);
--finish;
destroy(finish);
return position;
}

  正如我所想,erase函数并没有对输入的position迭代器进行改写!我打印出调试信息,发现erase之后,迭代器的_Ptr成员,也就是指针的值并没有发生变化,而此指针所指的元素的确是下一个元素。那么为什么失效了呢?

  我又查了《C++ Primier》,发现此书上的标准写法是这样的:

int arr[]={,,,,,,,,,,};
  vector<int> a(arr,arr+sizeof(arr)/sizeof(*arr));
for (auto it = a.begin(); it != a.end();){
if ((*it)&){
it=a.erase(it);
}
else
++it;
}

  运行了一下,这样是没错的。我打印了调试信息,发现与之前一样,erase之后把结果赋给it,it里的成员_Ptr并没有发生变化。唯一的可能就是迭代器里还有别的标志,如果当前元素被删除之后,该迭代器也就“失效”了。《C++ Primier》并未对此作出过多解释,只是说,erase函数返回被删除元素的下一个元素的迭代器。

  结论:在STL里,我们不能以指针来看待迭代器,指针是与内存绑定的,而迭代器是与容器里的元素绑定的,删除了之后,该迭代器就失效了,在对其重新赋值之前,不能再访问此迭代器。

2 更加小心冀冀地Insert

  机智如我,自然会去探索一下insert之后,迭代器会怎样。于是:  

    vector<int> a;
for (int i = ; i < ; ++i)
{
a.push_back(i);
} for (auto it = a.begin(); it != a.end(); ++it){
if (*it == ){
a.insert(it, );
       ++it;
}
}

  你猜怎么着??

  啥事儿没有!你可能会问,插入之后为什么要++it。插入之前,it指向5,在5之前插入100后,it指向100。这样下一次循环,it依然会指向5。相信我,你的程序会爆炸的!

  我作了个++it之后,it又指向5,下一次循环就直接指向5之后的元素了,顺利完成插入工作。

  世界和平~世界和平~我还真不确定。

  突然想到,当插入元素过多,vector的capacity会增加,这时会不会问题呢?说干就干:  

    vector<int> a;
for (int i = ; i < ; ++i)
{
a.push_back(i);
} for (auto it = a.begin(); it != a.end(); ++it){
if (*it == ){
a.insert(it, );
       ++it;
}
}

  BOOM!果然崩溃了!也就是说插入之后的迭代器失效了。那之前的呢?

  我决定粗暴地测试一下:

    vector<int> a;
for (int i = ; i < ; ++i)
{
a.push_back(i);
}
auto it1=a.begin();
for (auto it = it1; it != a.end(); ++it){
if (*it == ){
a.insert(it, );
       it=it1;
}
}

  我插入之后,直接让it指向begin(),然后单步调试。执行完it=it1还好好的,可再去执行++it还是崩溃了。

  也就是说,capacity变化之后,所有的迭代器都失效了!这是当然了呀!capacity发生变化,容器内部做的不仅仅是增加capacity这么简单,因为容器所在内存后面可能没有足够的内存让我们使用,所以,容器要重新开辟一段足够大的内存来存储容器里的元素,当前内存会被释放。这样一来,迭代器自然失效了。

3 C++ Primier的总结

  关于容器的迭代器失效的问题,C++ Primier用了一小节作了总结,我翻译成中文如下:

  (1)增加元素到容器后

  对于vector和string,如果容器内存被重新分配,iterators,pointers,references失效;如果没有重新分配,那么插入点之前的iterator有效,插入点之后的iterator失效;

  对于deque,如果插入点位于除front和back的其它位置,iterators,pointers,references失效;当我们插入元素到front和back时,deque的迭代器失效,但reference和pointers有效;

  对于list和forward_list,所有的iterator,pointer和refercnce有效。

  (2)从容器中移除元素后

  对于vector和string,插入点之前的iterators,pointers,references有效;off-the-end迭代器总是失效的;

  对于deque,如果插入点位于除front和back的其它位置,iterators,pointers,references失效;当我们插入元素到front和back时,off-the-end失效,其他的iterators,pointers,references有效;

  对于list和forward_list,所有的iterator,pointer和refercnce有效。

  (3)在循环中refresh迭代器

  当处理vector,string,deque时,当在一个循环中可能增加或移除元素时,要考虑到迭代器可能会失效的问题。我们一定要refresh迭代器。

int arr[] = { , , , , , , , , , ,  };
deque<int> v(arr,arr+sizeof(arr)/sizeof(*arr));
for (auto it = v.begin(); it != v.end(); )
{
if ((*it) & )
{
it = v.insert(it, *it);
it += ;
}
else
it = v.erase(it);
}

  至于it+=2,很容易解释,insert之后,it指向新增加的元素,+2之后,it指向下一个要处理的元素。

  (4)在循环不变式中不要store off-the-end迭代器

  这个很容易理解了,增加或移除元素之后,off-the-end失效了,不store的话,每次从end()函数中取的都是最新的off-the-end,自然不会失效。

  最后:《C++ Primier》是本好书

  

  

C++ STL 迭代器失效问题的更多相关文章

  1. stl 迭代器失效

    1.对于基于连续内存的容器,容器元素的增删,有可能会导致迭代器的失效.考虑: int main(int argc, char* argv[]) { vector<int> intVec; ...

  2. 转:STL迭代器失效问题

    . 对于关联容器(如map, set, multimap,multiset),删除当前的iterator,仅仅会使当前的iterator失效,只要在erase时,递增当前iterator即可.这是因为 ...

  3. C++ STL中迭代器失效的问题

    my_container.erase(iter); 其中my_container是STL的某种容器,iter是指向这个容器中某个元素的迭代器.如果不是在for,while循环中,这种方式删除元素没有问 ...

  4. STL的erase()陷阱-迭代器失效总结

    下面材料整理自Internet&著作. STL中的容器按存储方式分为两类,一类是按以数组形式存储的容器(如:vector .deque):另一类是以不连续的节点形式存储的容器(如:list.s ...

  5. STL源代码分析--迭代摘要、迭代器失效汇总

    Vector 1.内部数据结构:连续存储,比如数组. 2.随机訪问每一个元素,所须要的时间为常量. 3.在末尾添加或删除元素所需时间与元素数目无关,在中间或开头添加或删除元素所需时间随元素数目呈线性变 ...

  6. vector源码(参考STL源码--侯捷):空间分配导致迭代器失效

    vector源码1(参考STL源码--侯捷) vector源码2(参考STL源码--侯捷) vector源码(参考STL源码--侯捷)-----空间分配导致迭代器失效 vector源码3(参考STL源 ...

  7. stl vector、红黑树、set、multiset、map、multimap、迭代器失效、哈希表(hash_table)、hashset、hashmap、unordered_map、list

    stl:即标准模板库,该库包含了诸多在计算机科学领域里所常用的基本数据结构和基本算法 六大组件: 容器.迭代器.算法.仿函数.空间配置器.迭代适配器 迭代器:迭代器(iterator)是一种抽象的设计 ...

  8. STL迭代器及迭代器失效问题

    迭代器失效: 典型的迭代器失效. 首先对于vector而言,添加和删除操作可能使容器的部分或者全部迭代器失效.那为什么迭代器会失效呢?vector元素在内存中是顺序存储,试想:如果当前容器中已经存在了 ...

  9. STL容器迭代器失效问题讨论

    STL源码剖析---迭代器失效小结 vector迭代器的几种失效的情况: .当插入(push_back)一个元素后,end操作返回的迭代器肯定失效. .当插入(push_back)一个元素后,capa ...

随机推荐

  1. HMM 自学教程(二)生成模型

    本系列文章摘自 52nlp(我爱自然语言处理: http://www.52nlp.cn/),原文链接在 HMM 学习最佳范例,这是针对 国外网站上一个 HMM 教程 的翻译,作者功底很深,翻译得很精彩 ...

  2. Android 学习笔记之ContentProvider实现数据共享....

    PS:最近听老师说打算让我参与企业的app制作,让我加快学习的进度...好吧,貌似下周还有考试...貌似实验室这个app也要做...暂时不管了...那就只能加快进度了,感觉略微的有点激动和紧张,总算是 ...

  3. Hekaton的神话与误解

    最近这段时间,我花了很多时间来更好的理解Hekaton——SQL Sever 2014里的全新内存表技术.我看了很多文章,了解了Haktaon的各种内部数据存储结构(主要是哈希索引和Bw-tree). ...

  4. Python内置函数解析

    我们知道,为了方便使用,python内置了一系列常用及关键的函数,如type().下面将对这些函数进行逐一分析.解释. Python内置函数表: 1. abs():返回绝对值.如abs(-1)= 1. ...

  5. KeyValue Config

    public class ConfigHelper { public static ScriptsHelper Scripts { get { return new ScriptsHelper(); ...

  6. 怎么快速了解自己的MySQL服务器

      1.查看数据库服务器状态:status Linux 下的MySQL服务器状态 该列表中主要包括MySQL的版本(为version 5.1.61).运行平台(debian-linux-gnu(i68 ...

  7. c3p0配置

    <!DOCTYPE hibernate-configuration PUBLIC "-//Hibernate/Hibernate Configuration DTD 3.0//EN&q ...

  8. MyEclipse搭建SSM框架(Spring+MyBatis+SpringMVC)

    待写. 参考网址 http://www.cnblogs.com/Joetao/articles/4544572.html http://www.cnblogs.com/verlen11/p/53497 ...

  9. struts2 s:if标签以及 #,%{},%{#}的使用方法

    <s:if>判断字符串的问题: 1.判断单个字符:<s:if test="#session.user.username=='c'"> 这样是从session ...

  10. ok6410 android driver(4)

    Install busybox for goldfish/phone 1. Download busybox source code http://www.busybox.net/ 2. Decomp ...