C++ reverse_iterator 遍历删除问题源码解析

文章目录

  • 问题代码

      std::map test;
      test[1] = 10;
      test[2] = 20;
      map ::iterator it = test.begin();
      map ::reverse_iterator rit = test.rbegin();
      for (;rit != test.rend();) {
         cout << "t1 "<< rit->first << endl;
         test.erase(--(rit++).base()); // code 1
        //test.erase(--rit.base()); // code 2
         cout << "t2 " << rit->first << endl;
    }
    // 输出
    t1 2
    t2 1
    t1 1
    t2 0
    t1 0 // 这里还能访问到t1已经是不对了
    
  • 问题复现

    • 从正常使用的逻辑来讲,应当是code 1的使用方式,但是正确的却是code 2
    • https://stackoverflow.com/questions/404258/how-do-i-erase-a-reverse-iterator-from-an-stl-data-structure
  • 查看stl源码

 /**
   *  Bidirectional and random access iterators have corresponding reverse 
   *  %iterator adaptors that iterate through the data structure in the
   *  opposite direction.  They have the same signatures as the corresponding
   *  iterators.  The fundamental relation between a reverse %iterator and its
   *  corresponding %iterator @c i is established by the identity:
     双向和随机访问的迭代器拥有相对应的反向迭代器从数据结构的反方向开始迭代。它们具有与相应迭代器相同的签名。他们之间的关系如下;
   *  @code
   *      &*(reverse_iterator(i)) == &*(i - 1)
   *  @endcode
   *  意思就是:i的reverse_iterator代表的是i-1
   *  This mapping is dictated by the fact that while there is always a
   *  pointer past the end of an array, there might not be a valid pointer
   *  before the beginning of an array. [24.4.1]/1,2
   *  iterator是左闭右开,reverse_iterator是右开左闭
   							[begin(), rend())          (rend(), rbegin]
   *  Reverse iterators can be tricky and surprising at first.  Their
   *  semantics make sense, however, and the trickiness is a side effect of
   *  the requirement that the iterators must be safe.
   *  反向迭代器的实现搓爆了。但是,它们的语义是有意义的,而且棘手是迭代器必须安全的要求的副作用(都翻译不通)。
  */
// 成员仅有一个迭代器,我没想到居然不是继承了同一个迭代器的类,这种设计为后来发生的问题埋下了伏笔
protected:
      _Iterator current;
// 这里直接记录了一个bug,这可是最关键的*操作啊,因为还关联了->操作
/**
       *  @return  A reference to the value at @c --current
       *
       *  This requires that @c --current is dereferenceable.
       *
       *  @warning This implementation requires that for an iterator of the
       *           underlying iterator type, @c x, a reference obtained by
       *           @c *x remains valid after @c x has been modified or
       *           destroyed. This is a bug: http://gcc.gnu.org/PR51823
      */
      reference
      operator*() const
      {
				_Iterator __tmp = current;
				return *--__tmp;
      }
      /**
       *  @return  A pointer to the value at @c --current
       *
       *  This requires that @c --current is dereferenceable.
      */
      pointer
      operator->() const
      { return &(operator*()); }
// base 这东西返回的是非常难理解的current
iterator_type
      base() const
      { return current; }
// ++操作也是不走心的直接--current。
reverse_iterator&
operator++()
      {
				--current;
				return *this;
      }
  • 看清问题:

    • erase只能根据iterator来,因此我们转化一下,这个转化也是醉了,–rit.base(),这个base函数形同虚设。

    • 那么这样一来,通过前面埋的伏笔我们发现–current被移出了。

    • ok,深吸一口气,现在你的rit变成了什么呢?

    • 你所依赖的current倒是没什么问题,但是–current已经变天了。

    • 现在回头来看我们觉得没问题的code 1 我们先做了rit++操作即–current,之后我们删除了–current,这在没有遍历删除到最后一个的时候还是work fine的,但是遍历到最后一个时,–current就出问题了,没有正确找到rend,如果还需要进一步深入,需要看tree的erase源码。

    •   vector  test;
        test.push_back(10);
        test.push_back(20);
        vector ::iterator it = test.begin();
        vector ::reverse_iterator rit = test.rbegin();
        for (;rit != test.rend();) {
           cout << "t1 "<< *rit << endl;
           test.erase(--(rit++).base());
           cout << "t2 " << *rit << endl;
        }
      // output
      t1 20
      t2 10
      t1 10
      t2 0
      // vector是正确的
      
  • trick 绕过:

    • test.erase(–rit.base()) 这样就能绕过了,即在erase之后依靠–current来判断是不是rend
    • 暂时没找到rend的定义。问题也可能在这里
  • 优雅解决:

    • 要么C++ 11 erase之后返回next;
    • 要么reverse_it拿来迭代就好,最好别用他去删除,太搓了

你可能感兴趣的:(C++)