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

  

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

1 莫名其妙的Erase  

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

  1. 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 ){
  2. if ((*it)&1){
  3. a.erase(it);
  4. }
  5. }

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

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

  1. iterator vector<T, Alloc>::erase(iterator position)
  2. {
  3. if (position + != end())
  4. copy(position + , finish, position);
  5. --finish;
  6. destroy(finish);
  7. return position;
  8. }

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

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

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

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

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

2 更加小心冀冀地Insert

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

  1. vector<int> a;
  2. for (int i = ; i < ; ++i)
  3. {
  4. a.push_back(i);
  5. }
  6.  
  7. for (auto it = a.begin(); it != a.end(); ++it){
  8. if (*it == ){
  9. a.insert(it, );
           ++it;
  10. }
  11. }

  你猜怎么着??

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

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

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

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

  1. vector<int> a;
  2. for (int i = ; i < ; ++i)
  3. {
  4. a.push_back(i);
  5. }
  6.  
  7. for (auto it = a.begin(); it != a.end(); ++it){
  8. if (*it == ){
  9. a.insert(it, );
  10.        ++it;
  11. }
  12. }

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

  我决定粗暴地测试一下:

  1. vector<int> a;
  2. for (int i = ; i < ; ++i)
  3. {
  4. a.push_back(i);
  5. }
  6. auto it1=a.begin();
  7. for (auto it = it1; it != a.end(); ++it){
  8. if (*it == ){
  9. a.insert(it, );
  10.        it=it1;
  11. }
  12. }

  我插入之后,直接让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迭代器。

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

  至于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. Java异步回调

      作者:禅楼望月(http://www.cnblogs.com/yaoyinglong) 1.开始讲故事: 午饭的时候到了,可是天气太冷,根本不想出办公室的门,于是你拨通了某饭店的订餐电话“喂!你好 ...

  2. mysql如何更改数据库名(一键实现mysql改数据库名)

    由于某种原因,有时我们有可能需要数据库的名称,但是不像官方有rename可以去更改表名,并没有一个命令可以去更新数据库的名字. 思路:借助rename这个命令 基本操作:rename olddb.ta ...

  3. [C#高级编程]基础知识摘要一

    核心C#: 值类型存储在堆栈中,而引用类型存储在托管堆上. object类型可以用于两个目的: 可以使用object引用绑定任何子类型的对象 object类型执行许多一般用途的基本方法,包括Equal ...

  4. Scrum团队成立3.0

    博客园 首页 新随笔 联系 订阅 管理 随笔 - 23  文章 - 0  评论 - 26 0428-Scrum团队成立3.0 ------------------------------3.0---- ...

  5. STL or Force --- CSU 1553: Good subsequence

    Good subsequence Problem's Link:   http://acm.csu.edu.cn/OnlineJudge/problem.php?id=1553 Mean: 给你一个长 ...

  6. C++ - 多线程的实现

    支持多线程可谓是C++语言最大的变化之一. 此前,C++只能利用操作系统的功能(Unix族系统使用pthreads库),或是例如OpenMP和MPI这些代码库,来实现多核计算的目标. C++本身并没有 ...

  7. jQuery的事件change

    人生还在继续,只有不断补充以前所不懂的知识,今天练习一个jQuery的事件change.这个事件是在对象失去focus并且原本值有所变化时就产生此事件.如select时,用户所选择的选项有变时,或是t ...

  8. Js中各类型数据到bool的转换

    在返回Json字符串给前台时遇到的问题,返回的bool数据总是为TRUE 特意查了一下,发现了Js中各类数据转换到bool型是的结果. 希望能给遇到同样问题的人一点帮助.  数据类型  转换为bool ...

  9. window10 mysql5.7 解压版 安装

    1. 解压mysql-5.7.11-winx64.zip 到某文件夹, 如C:\DevelopCommon\mysql-5.7.11-winx64. 2. 配置环境变量 变量名 : MYSQL_HOM ...

  10. 设置让ASP.NET管道接收所有类型的请求

    在web.config文件添加如下一段配置: <configuration> <system.webServer> <modules runAllManagedModul ...