迭代器失效(1)-小心使用STL容器的erase()

原创 2012年12月16日 17:26:48



        对于以下代码:

      my_container.erase(iter);

                其中my_container是STL的某种容器,iter是指向这个容器中某个元素的迭代器。如果不是在for,while循环中,

        这种方式删除元素没有问题,如果是在for,while中对m_container迭代,删除其中符合条件的所有元素,就可能出现问题。

         如果是在for,while中对m_container迭代,删除其中符合条件的所有元素,就可能出现问题。


        问题是:

                在迭代容器的时候删除元素,可能导致迭代器失效(invalidation of iterators),产生未定义行为

         (undefined behavior);

                例如,对某个迭代器解引用所获得的值并不是执行erase()前这个迭代器指向的值,还有可能对未指向任何

         元素的迭代器的解引用赋值而引发程序crash。

         类似的问题代码像这样:

  std::vector<int>  my_container;
  for (int i = 0; i < 100; ++i) {
       my_container.push_back(i);
    }

  std::vector<int>::iterator it = my_container.begin();
  for (it != my_container.end(); it++) {
    	if (*it % 2 == 1) {
           my_container.erase(it);
       }
  }
                my_container.erase(it)之后,it及其后面的迭代器已经失效,不应该再使用这些迭代器。再执行it++,其行为是未定义的。

       其他容器也会遇到迭代器失效的问题:

                对于vector 被删除元素的迭代器以及指向后面元素的迭代器全部失效。  

                对于deque  在首部或尾部删除元素则只会使指向被删除元素的迭代器失效,任何其他位置的插入和删除操作将使指向该容器元素的

          所有迭代器失效。  

                对于list 仅有指向被删除元素的迭代器失效。    

                对于(mulit)map ,(multi)set 仅有指向被删除元素的迭代器失效。


              所以Golden Rule是:尽量不要使用容器的插入删除操作之前的迭代器。

              为什么不同容器迭代器失效情况有差别?这与实现各容器的数据结构有关。  

              如何在迭代容器时删除其中的元素?各容器通用的做法如下:

            std::vector<int>::iterator it = my_container.begin();
            for (it != my_container.end();/**blank*/ ) {
    	          if (*it % 2 == 1) {
                      my_container.erase(it++);
                 }
                 else{
                      it++;
                 }
            }

                     my_container.erase(it++) 巧妙得在执行erase()之前,it 先自增,指向被删除元素后面的元素,而给erase()传递的是未自增的it迭代器,

            以定位要删除的元素。如果元素的值为奇数,则删除此元素,it指向下一个元素,如果元素的值为偶数,则检查下一个元素的值。整个迭代过程中

            迭代器就不会失效了。

                    上段代码中两个不同分支出现了i++操作,下面的代码示例显示了如何防止遗忘其中任何一个分支的i++操作。          

            MyContainer::iterator it = myContainer.begin();
            While(it != myContainer.end()){
               MyContainer::iterator curIt = it;
               if (*curIt == matchingValue)    {
                       myContainer.erase(curIt);
               }
            }
                    对于vector ,deque, list, 另一种可行的方式是:
                           
           std::vector<int>::iterator it = my_container.begin();
           for (it != my_container.end();/**blank*/ ) {
    	         if (*it % 2 == 1) {
                    it = my_container.erase(it);
                }
                else{
                     it++;
                }
           }
                   上面代码可行的原因是vector::erase() 返回一个新的迭代器,指向被删除元素的后面的元素。可以继续使用新的迭代器。

                   而出于某种未知的原因(multi)map::erase(), (multi)set::erase()没有返回这样的迭代器。(从C++11开始也支持返回迭代器了).

                  

                   但是对于vector,诸如在0到99个数中删除所有奇数的问题,可以使用STL的remove(),remove_if()优化性能。代码如下:    

          bool isOdd(int value)
          {
               return (value % 2) == 1;
          }

          my_container.erase( std::remove_if(m_container.begin(), m_container.end(), isOdd), m_container.end());
                   让我们再看看不使用remove_if()的版本:                 
          for (it != my_container.end();/**blank*/ ) {
    	        if (*it % 2 == 1) {
                   it = my_container.erase(it);
               }
               else{
                    it++;
               }
           }

                   如果你阅读过erase()源码或了解它是如何工作的,性能问题就显而易见:erase()删除一个元素的操作是被删除元素后面的所有元素依次

           向前移动一个元素的位置,然后删除最后一个元素,时间复杂度为O(n^2)。

                   remove(),remove_if()的时间复杂度为O(n),删除元素的操作如下所示:

                 

            template<class ForwardIt, class UnaryPredicate>
            ForwardIt remove_if(ForwardIt first, ForwardIt last, UnaryPredicate p)
            {
                  ForwardIt result = first;
                  for (; first != last; ++first) {
                       if (!p(*first)) {
                            *result++ = *first;
                       }
                  }
                  return result;
             }
                     从前向后遍历容器所有元素,将待保留的元素向前移动,占据待删除的元素的位置,remove_if()返回新的元素范围(begin,end)中的end,

             记为new_end_of_range ,再调用erase()删除从new_end_of_range到my_container.end()之间的所有元素。

                     实际上,remove_if() 没有删除容器中的任何元素,它没有改变my_container.end(), 调用remove_if()后容器元素个数不会改变!!删除元素的工作

              交给了erase().

                    


                       Scott Meyers在他的”Effective STL”中关于此问题的讨论中也使用了remove_if(),由此看来,他的确是提出了一些让STL effective的建议。


              深入学习STL迭代器失效问题:

                        在google中搜索 stl iterator invalidation rules 可以获得很多有关STL迭代器失效的有关内容。

           

              References:

              1. STL remove_if()       http://en.cppreference.com/w/cpp/algorithm/remove

              2.More C++ Idioms/Erase-Remove   http://en.wikibooks.org/wiki/More_C%2B%2B_Idioms/Erase-Remove

              3.Effective STL, Item 32 - Scott Meyers

              4.Cpp Invalid Iterators [对各种迭代器失效的情况进行了讲解分类]

                   http://www.angelikalanger.com/Conferences/Slides/CppInvalidIterators-DevConnections-2002.pdf                

              5.以下是stackoverflow上关于在迭代时删除容器中元素的讨论:

                  http://stackoverflow.com/questions/1604588/iterate-vector-remove-certain-items-as-i-go

                  http://stackoverflow.com/questions/3747691/stdvector-iterator-invalidation?rq=1

                  http://stackoverflow.com/questions/2874441/deleting-elements-from-stl-set-while-iterating?rq=1

                  http://stackoverflow.com/questions/1038708/erase-remove-contents-from-the-map-or-any-other-stl

                        -container-while-iterating/1038761#1038761

                  http://stackoverflow.com/questions/799314/difference-between-erase-and-remove?rq=1


转载本文请注明作者和出处[Gary的影响力]http://garyelephant.me,请勿用于任何商业用途!

Author: Gary Gao 关注互联网、分布式、高并发、自动化、软件团队

支持我的工作:  https://me.alipay.com/garygao









版权声明:本文为博主(微博@Gary的影响力)原创文章,未经博主允许不得转载。博客地址:http://garyelephant.me

c++ STL中容器迭代器失效

参考:http://www.cnblogs.com/yuanshuang/p/5777905.html http://blog.csdn.net/aa838260772/article/deta...
  • petersmart123
  • petersmart123
  • 2016年11月23日 14:07
  • 1016

关于STL中map的erase迭代器是否失效的讨论

近来,在阅读一份开源代码的时候,看到了类似如下的代码:typedef std::map id_names_t; id_names_t id_names; void EraseName(std::st...
  • haust_wang
  • haust_wang
  • 2015年10月21日 10:16
  • 2080

C++ STL容器迭代器失效

迭代器是封装了指针、重载了 -> 、* 、++等操作符的类模板。迭代器失效,指迭代器指向错误的元素或无效的内存地址。理解迭代器失效,首先要了解容器的内部数据结构、以及该容器存储新元素和删除旧元素时的内...
  • u010599509
  • u010599509
  • 2016年07月21日 15:57
  • 762

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

下面材料整理自Internet&著作。   STL中的容器按存储方式分为两类,一类是按以数组形式存储的容器(如:vector 、deque);另一类是以不连续的节点形式存储的容器(如:list、se...
  • liujianfei526
  • liujianfei526
  • 2016年08月12日 00:04
  • 527

C++ STL 迭代器失效

c++ STL迭代器失效总结
  • zxx910509
  • zxx910509
  • 2015年12月18日 09:43
  • 427

迭代器失效(1)-小心使用STL容器的erase()

对于以下代码: [cpp] view plain copy print?    my_container.erase(iter);     my_container.eras...
  • du_bingbing
  • du_bingbing
  • 2016年06月03日 10:49
  • 443

C++ STL 迭代器失效问题

之前看《C++ Primier》的时候,也解到在顺序型窗口里insert/erase会涉及到迭代器失效的问题,并没有深究。今天写程序的时候遇到了这个问题。 1 莫名其妙的Erase     最初我...
  • coderCong
  • coderCong
  • 2016年07月29日 15:03
  • 2489

C++ STL 容器、迭代器、适配器

1、容器vector,list,deuqe是C++STL中三种基本容器实现,它们不可能互为实现同时又不损失效率 2、stack和queue则都可以在这三种基本容器序列基础上实现,所以没有定义为独立...
  • richrdbird
  • richrdbird
  • 2016年03月31日 15:36
  • 1766

【C++】vector容器和list容器中使用单个迭代器的erase函数学习笔记

例题为:假设有如下ia的定义,ia复制到vector和list中,把list容器中奇数值元素删除掉,把vector容器偶数值元素删除掉。 数组复制采用push_back操作。删除操作采用了erase函...
  • qazcxh
  • qazcxh
  • 2015年04月11日 16:07
  • 1235

C++关于迭代器删除(erase)插入(insert)失效问题

初学者的我在学习迭代器的时候(今天这里主要说的是vector的迭代器)碰到了一些问题,纠结了好些时候,总算弄明白了一点。 迭代器会在删除插入等操作后失效,即在其删除插入位置后的迭代器会失效,那所...
  • a327369238
  • a327369238
  • 2014年05月23日 16:06
  • 1310
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:迭代器失效(1)-小心使用STL容器的erase()
举报原因:
原因补充:

(最多只允许输入30个字)