1. 程式人生 > >c++之叠代器失效

c++之叠代器失效

個人 錯誤 自身 開始 崩潰 引用 重新 [0 但是

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++之叠代器失效