容器迭代器失效問題

衆所周知當使用一個容器的insert或者erase函數通過迭代器插入或刪除元素"可能"會導致迭代器失效,因此很多建議都是讓我們獲取insert或者erase返回的迭代器,以便用重新獲取新的有效的迭代器進行正確的操作:

view plaincopy to clipboardprint?
iter=vec.insert(iter);   
iter=vec.erase(iter);      
  想想究竟爲什麼迭代器失效,原因也不難理解。以vector爲例,當我們插入一個元素時它的預分配空間不夠時,它會重新申請一段新空間,將原空間上的元素複製到新的空間上去,然後再把新加入的元素放到新空間的尾部,以滿足vector元素要求連續存儲的目的。而後原空間會被系統撤銷或徵做他用,於是指向原空間的迭代器就成了類似於“懸垂指針”一樣的東西,指向了一片非法區域。如果使用了這樣的迭代器會導致嚴重的運行時錯誤就變得很自然了。這也是許多書上敘述vector在insert操作後“可能導致所有迭代器實效”的原因。但是想到這裏我不禁想到vector的erase操作的敘述是“會導致指向刪除元素和刪除元素之後的迭代器失效”。但是明顯感覺erase帶來失效要比insert來得輕得多。似乎“此失效非彼失效”,想想似乎也是這樣的:erase操作是在原空間上進行的,假設有一個存有"12345"序列的vector<int>容器原本指向3的迭代器在我刪除2之後無非變成指向4了,我只要注意別用到超過end位置的迭代器不就行了嗎?

  說了這麼多似乎可以歸納一下迭代器失效的類型了:

  1.由於容器元素整體“遷移”導致存放原容器元素的空間不再有效,從而使得指向原空間的迭代器失效。

  2.由於刪除元素使得某些元素次序發生變化使得原本指向某元素的迭代器不再指向希望指向的元素。

  對於第一種類型沒什麼好就是的了,原因應該確定如此了。可對於第二種,我寫了如下的代碼

view plaincopy to clipboardprint?
vector<int> vec;   
for(int i=0;i<10;i++)   
vec.push_back(i);   
 
vector<int>::iterator iter =vec.begin()+2;   
vec.erase(iter);//注:這裏真的不建議這麼寫   
cout<<*iter<<endl;   
for(vector<int>::iterator it=vec.begin();it!=vec.end();it++)   
cout<<*it<<endl; 

   按照我的猜測儘管我在註釋的位置的寫法很“危險”,但是我並未涉及到上面總結第一種失效類型的範疇。程序應該還是會如預期的一樣刪除在vec[2]位置上的2然後輸出前移到vec[2]位置上的3,並輸出0到10不含2的所有數字,於是vs2008的c++環境下運行一下,竟然一個是個無情的“紅叉”。按道理說這種寫法是絕對不對出現嚴重的運行時錯誤的,難道是猜測得不對嗎?

   當時被這樣的疑問困擾了幾天,有一天突然想起一個關於容器迭代器作參數的例子,在vs2008下運行不了,但是在vc6.0下卻可以,有人說是因爲08採用了更爲嚴格的類型檢測機制。於是將上面的代碼放到6.0裏,果然得到了預期的效果,看來系統果然是這麼處理的。至於08爲什麼不可以,現在我只能認爲是採用了更加安全的檢查機制。使得第二種類型的失效後果同樣“不可饒恕”。

  這樣我就又想到假如insert元素時原空間夠用的話,是不是也不會產生第一類失效而產生第二類失效呢?

view plaincopy to clipboardprint?
vector<int> vec;   
for(int i=0;i<10;i++)   
    vec.push_back(i);   
       cout<<"capacity:"<<vec.capacity()<<endl;//查看預分配空間大小   
vector<int>::iterator iter =vec.begin()+2;   
vec.insert(iter,100);   
cout<<"capacity:"<<vec.capacity()<<endl;     
cout<<*iter<<endl;   
for(vector<int>::iterator it=vec.begin();it!=vec.end();it++)   
cout<<*it<<endl;  
   
  同樣在vc6.0下,證明了假設是對的。但是上面的種種做法只是爲了幫助我理解迭代器實效的原因,建議使用insert和erase操作時還是像許多書中介紹的如第一段代碼那樣的寫法,這是一種好的且安全的習慣。總之一句話去相信“insert和erase操作後所有的迭代器都會失效”。


迭代器(iterator)是一個可以對其執行類似指針的操作(如:解除引用(operator*())和遞增(operator++()))的對象,我們可以將它理解成爲一個指針。但它又不是我們所謂普通的指針,我們可以稱之爲廣義指針,你可以通過sizeof(vector::iterator)來查看,所佔內存並不是4個字節。

    首先對於vector而言,添加和刪除操作可能使容器的部分或者全部迭代器失效。那爲什麼迭代器會失效呢?vector元素在內存中是順序存儲,試想:如果當前容器中已經存在了10個元素,現在又要添加一個元素到容器中,但是內存中緊跟在這10個元素後面沒有一個空閒空間,而vector的元素必須順序存儲一邊索引訪問,所以我們不能在內存中隨便找個地方存儲這個元素。於是vector必須重新分配存儲空間,用來存放原來的元素以及新添加的元素:存放在舊存儲空間的元素被複制到新的存儲空間裏,接着插入新的元素,最後撤銷舊的存儲空間。這種情況發生,一定會導致vector容器的所有迭代器都失效。

我們看到實現上述所說的分配和撤銷內存空間的方式以實現vector的自增長性,效率是極其低下的。爲了使vector容器實現快速的內存分配,實際分配的容器會比當前所需的空間多一些,vector容器預留了這些額外的存儲區,用來存放新添加的元素,而不需要每次都重新分配新的存儲空間。你可以從vector裏實現capacity和reserve成員可以看出這種機制。

    capacity和size的區別:size是容器當前擁有的元素個數,而capacity則指容器在必須分配新存儲空間之前可以存儲的元素總數。

    vector迭代器的幾種失效的情況: 1.當插入(push_back)一個元素後,end操作返回的迭代器肯定失效。 2.當插入(push_back)一個元素後,capacity返回值與沒有插入元素之前相比有改變,則需要重新加載整個容器,此時first和end操作返回的迭代器都會失效。 3.當進行刪除操作(erase,pop_back)後,指向刪除點的迭代器全部失效;指向刪除點後面的元素的迭代器也將全部失效。

    deque迭代器的失效情況: 在C++Primer一書中是這樣限定的: 1.在deque容器首部或者尾部插入元素不會使得任何迭代器失效。 2.在其首部或尾部刪除元素則只會使指向被刪除元素的迭代器失效。 3.在deque容器的任何其他位置的插入和刪除操作將使指向該容器元素的所有迭代器失效。但是:我在vs2005測試發現第一條都不滿足,不知爲何?等以後深入STL以後慢慢的領會吧!

    只有list的迭代器好像很少情況下會失效。也許就只是在刪除的時候,指向被刪除節點的迭代器會失效吧,其他的還沒有發現。

發佈了50 篇原創文章 · 獲贊 1 · 訪問量 5萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章