c++之叠代器失效
1.首先從一到題目開始談說起叠代器失效。有時我們很自然並且自信地 用下面方法刪除vector元素:
#include <iostream>
#include <stdio.h>
#include <vector>
#include <algorithm>
#include <string>
void del_elem(vector<string> &vec, const char * elem) { vector<string>::iterator itor = vec.begin(); for(; itor != vec.end(); itor++) { if (*itor == elem) { vec.erase(itor); } } } template <class InputIterator> void show_vec(InputIterator first, InputIterator last) { while(first != last) { std::cout << *first << " "; first++; } std::cout << " " << std::endl; } int main(void) { string arr[] = {"php", "c#", "java", "js", "lua"}; vector<string> vec(arr, arr+(sizeof(arr)/sizeof(arr[0]))); std::cout << "before del: " << std::endl; show_vec(vec.begin(), vec.end()); del_elem(vec,"php"); std::cout << "after del: " << std::endl; show_vec(vec.begin(), vec.end()); return 0; }
當 string arr[] = {"php", "c#", "java", "js", "lua"}; 時,運行上邊程序,得到如下輸出:
運行結果是正確的啊。 找到 "php" ,然後刪除,剩下四個元素。
但是實際上 del_elem 的過程是和我們想象的不一樣的,在 del_elem中打印下每一步的 itor 的值,就會發現蛛絲馬跡。
將 del_elem加上log:
void del_elem(vector<string> &vec, const char * elem) { std::cout << "----------------------------" << std::endl; vector<string>::iterator itor = vec.begin(); for (; itor != vec.end(); itor++) { std::cout << *itor << std::endl; if (*itor == elem) { vec.erase(itor); } } std::cout << "----------------------------" << std::endl; }
我們在做刪除操作前,打印每個元素的值, 繼續編譯運行得到如下結果:
在做 del_elem操作時,少打印了一個 "c#", 也就是在打印完"php",然後刪除php以後,接下來打印的不是 "c#", 而直接打印了 "java" 。
那麽我們可以將 vec.erase(itor) 註釋掉,然後 可以得到 del_elem 會打印所有的元素值,
如此看來 c# 是因為執行了erase 操作以後,“變沒了”。
弄清這個問題,我們要看看一組vector操作的定義:
iterator erase(iterator position) { if(position + 1 != end()) copy(position + 1, finish, position); --finish; destroy(finish); return position; }
iterator begin() { return start; }
iterator end() { return finish; }
我們經常使用 vec.begin(), vec.end(), 想必也能知道start和finish 為何物。
首先看erase函數: 先判斷 待刪除的叠代器是否為 finish 的前一個元素,也就是vector中的最後一個元素,
如果不是最後一個元素,就將待刪除叠代器的後邊所有元素往前移動一個slot, 然後 --finish 重新定位finish指針。
此時finish指針指向的是沒刪除之前的最後一個元素地址,本例中就是 lua的地址, 但是當前的finish指針處的值已經沒用了,於是調用destroy。
如果待刪除叠代器是finish的前一個元素的話,那麽就直接移動finish指針,調用destroy銷毀該位置處的元素對象。
與此同時,我們看到erase函數傳進來的叠代器,只起到了一個位置定位判斷的作用,erase本身沒有對該叠代器有任何操作,該叠代器的值所在地址仍然有效,但是由於進行了copy操作,position處的值已經變成了"c#".
再回過頭來看一下我們的 del_elem 函數:
當刪除第一個元素php之後,在執行 itor++之前,php之後的所有元素都前移了一個slot導致此時 itor存放的元素已經是 c#,
於是繼續執行itor++後,此時itor又向後移動,itor中的值已經是java,c#就是這樣被漏掉的。
1-2 由此,又可以得出另一個結論,當arr中有n(n>=2)個 連續的php元素,我們用 del_elem函數 是不能刪除掉所有的php元素的,於是這樣就會導致bug。
我們將 string arr[] = {"php", "c#", "java", "js", "lua"}; 改為 string arr[] = {"php", "php", "php", "php", "c#", "java", "js", "lua"}; 後,觀察運行結果:
固然不出所料,php沒有被刪除幹凈,因為當刪除第一個php以後,用當前 del_elem 方法,總是會漏掉刪除的php之後的元素,如果這個元素恰好是 "php",便會出現bug。
1-3 用當前 del_elem刪除一個元素,會導致 finish 前移一個slot,如果將php放到最後slot,即finish之前的slot中,當刪除最後一個php後,finish會指向刪除的php的地址(已經非法了),
然後php的地方會被銷毀,然後又執行 itor++,於是此時的itor指向到finish的後邊,當 判斷 itor != vec.end() 時,這個式子是成立的,於是繼續執行,但是當對叠代器解引用時,最終會由於是非法
引用地址,程序崩掉。我們來看一下是否是這樣, 將最後一個元素改為 "php"; string arr[] = {"php", "php", "php", "php", "c#", "java", "js", "php"};
編譯運行,結果如下:
gdb調試,發現是因為 *itor 導致程序崩潰。
2 以上例子指出了vector 刪除元素時選擇的方法不當導致的一些問題;
1 是刪除多個相同元素時,因為vector自身的特性導致 刪除不凈,出現bug
2 是當刪除的元素時最後一個元素,可能導致程序崩潰。
3 我們很多時候都知道vector 叠代器失效會出問題,但是很多時候不知道會導致什麽問題。
以上例子列舉了 叠代器失效的 結果, 那麽反過來 ,我們再研究 “什麽是vector刪除元素會導致叠代器失效” 的問題。
我的結論是,在對vector進行刪除元素的時候, 刪除元素之前,假設我們定義了一些叠代器分別指向,
1 叠代器的位置位於 待刪除叠代器之前,
2 待刪除的叠代器
3 叠代器的位置位於 待刪除的叠代器之後
那麽當對待刪除的叠代器調用erase(itor)以後,之前定義在itor之前的叠代器依舊有用, 之前定義的 itor 以及 itor之後的叠代器 已經失效了,這裏的失效是指,這些叠代器所指的元素內容已經和刪除之前的不一樣了,甚至可能是指向了非法地址。
於是在對這些失效的叠代器進行操作的時候 可能導致程序出bug ,或者直接崩潰。
4. 那麽該如何刪除vector元素呢?
可以參考:
void del_elem(vector<string> &vec, const char *elem) { vector<string>::iterator itor = vec.begin(); for (; itor != vec.end();) { std::cout << *itor << " " << &(*itor) << std::endl; if (*itor == elem) { itor = vec.erase(itor); //個人覺得這句賦值是多余的,因為erase本身沒有對itor進行任何操作,erase操作之前和操作之後的itor所指向的位置是不變的,變的只是裏邊的值。 如有理解錯誤,還望及時指出 } else { itor++; } } }
5. 想必讀者已經對vector刪除元素引起的叠代器失效有了一些理解,那麽再來理解插入元素導致的叠代器失效會更容易一些。
1 如果插入操作引起了空間重新配置,(申請新空間,賦舊空間的值到新空間,釋放舊空間),那麽在插入操作執行後,之前聲明的所有叠代器都將失效
2 如果沒有引起空間配置,那麽會導致插入位置之後的叠代器失效。
6. 我們 假如我們聲明了一些叠代器,對vector進行了插入或刪除操作以後,要註意這些叠代器可能已經失效。
7. 由vector的叠代器失效,可以引出,其他序列式容器的叠代器失效,其他關聯式容器的叠代器失效。內容太多,本篇只是先給出vector的叠代器失效的一些理解,後續繼續補充其它的。
<effective stl> 第九條,較詳細的討論的各種容器的操作方法,有興趣的讀者可自行翻閱。
水平有限,錯誤難免,望及時指出。希望能對大家理解叠代器失效提供一些思路。
c++之叠代器失效