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

來源:互聯網
上載者:User

        對於以下代碼:

      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 關注互連網、分布式、高並發、自動化、軟體團隊

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.