C++ reverse_iterator 遍歷刪除問題源碼解析

文章目錄

  • 問題代碼

      std::map<int, int> test;
      test[1] = 10;
      test[2] = 20;
      map <int, int>::iterator it = test.begin();
      map <int, int>::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
   *  <em>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.</em> [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 <int> test;
        test.push_back(10);
        test.push_back(20);
        vector <int>::iterator it = test.begin();
        vector <int>::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拿來迭代就好,最好別用他去刪除,太搓了
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章