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> 第九條,較詳細的討論的各種容器的操作方法,有興趣的讀者可自行翻閱。

 

水平有限,錯誤難免,望及時指出。希望能對大家理解迭代器失效提供一些思路。

  

  

 

  

  

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM