Friday, 30 September 2016

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

今天调试代码,定位一个compiler 死循环问题。 最终定位出来的原因给作者在下面这篇文章中总结的基本一样。需要指出的是这是程序员及其容易犯的一个错误。忘记将std::list的成员函数erase 返回给iter 本身。这样导致iter 成为类似野指针(虽然iter 不是指针,但是本质上是包裹起来的指针),行为将变得undefined。 如果在某些平台上,比如Linux,操作系统没有立即回收内存,有可能该问题本隐藏起来。但是在其他立即回收内存(或硬删除而不是移动到缓存)的平台上,则会造成死循环。

下面是问题代码,及修复代码:


//这里假设上下文已经设置好了myList (有了元素)
// str 是作为函数入参数传进来, const char * 类型

原代码 (错误使用方法3,相对于下面链接中的两种错误方法) :
 std::list<const char *>::iterator myList ;
  for(std::list<const char *>::iterator I = myList.begin(), E = myList.end(); I != E; ++I) {
         if (!strcmp(*I, str)) {
            _myList.erase(I);
            --I;
          }
 }

修复代码 (正确使用方法3,相对于下面链接中的两种正确方法):
 std::list<const char *>::iterator myList ;
  for(std::list<const char *>::iterator I = myList.begin(), E = myList.end(); I != E; ++I) {
         if (!strcmp(*I, str)) {
            I = _myList.erase(I);
            --I;
            }
  }
// comment : erase 返回指向删除元素下一个位置元素的指针,先-- 在++,仍正确指向
// 删除元素的下一个位置,可以正常工作。


另外两种错误及正确方法,见下面的链接。 个人比较喜欢正确方法三,虽然需要强制--,再++,但是毕竟避免了把++操作从for () 语句中移动到句外的情况。
总而言之,凡是让程序员记着干某些事情的设计都不是好设计。因为他们总是忘记。 比如这里的让他们记得把erase()返回值付给iter 本身,以及记得new 了以后一定要delete。正如用auto_ptr 或Java 自动垃圾回收方法可以解决到处泛滥的内存泄露问题。或许我们也应该设计一种方法,让erase() 接口不用强求程序员记着赋值给iter。 强制给出void,并让iter 自动指向下个元素也不是一个好方法。程序员使用不当的话,有可能导致跳过元素。笔者没有什么好的idea。如果各位路过的大神,有什么好的想法,欢迎留言讨论。


http://blog.csdn.net/lanbing510/article/details/8796048

下面材料整理自Internet&著作。
STL中的容器按存储方式分为两类,一类是按以数组形式存储的容器(如:vector 、deque);另一类是以不连续的节点形式存储的容器(如:list、set、map)。在使用erase方法来删除元素时,需要注意一些问题。

      在使用 list、set 或 map遍历删除某些元素时可以这样使用:

正确使用方法1       std::list< int> List;
      std::list< int>::iterator itList;
      for( itList = List.begin(); itList != List.end(); )
      {
            if( WillDelete( *itList) )
            {
               itList = List.erase( itList);
            }
            else
               itList++;
      }

       或

正确使用方法2       std::list< int> List;
      std::list< int>::iterator itList;
      for( itList = List.begin(); itList != List.end(); )
      {
            if( WillDelete( *itList) )
            {
               List.erase( itList++);
            }
            else
               itList++;
      }

      
      下面是两个错误的使用方法:

错误使用方法1       std::list< int> List;
      std::list< int>::iterator itList;
      for( itList = List.begin(); itList != List.end(); itList++)
      {
            if( WillDelete( *itList) )
            {
               List.erase( itList);
            }
      }

         或
错误使用方法2       std::list< int> List;
      std::list< int>::iterator itList;
      for( itList = List.begin(); itList != List.end(); )
      {
            if( WillDelete( *itList) )
            {
               itList = List.erase( ++itList);
            }
            else
               itList++;
      }

      正确使用方法1:通过erase方法的返回值来获取下一个元素的位置
      正确使用方法2:在调用erase方法之前先使用 “++”来获取下一个元素的位置
      错误使用方法1:在调用erase方法之后使用“++”来获取下一个元素的位置,由于在调用erase方法以后,该元素的位置已经被删除,如果在根据这个旧的位置来获取下一个位置,则会出现异常。
      错误使用方法2:同上。

No comments:

Post a Comment