1. 程式人生 > >(轉)C++ STL中map.erase(it++)用法原理解析

(轉)C++ STL中map.erase(it++)用法原理解析

之前在程式碼中使用map::erase函式時,誤搬了vector::erase的用法,導致Server down掉了,好在在測試環境就及時發現了問題,在上線前進行了補救==。  以下總結一下map::erase的正確用法。  首先看一下在迴圈中使用vector::erase時我習慣的用法:

for(vector<int>::iterator it = vecInt.begin(); it != vecInt.end();)
{
    if(*it == 0)
    {
        it = vecInt.erase(it);
    }
    else
    {
        it++;
    }
}
C++98
iterator erase (iterator position);
  • 這一種用法是沒有問題的。
C++98
(1) 
     void erase (iterator position);
(2) 
size_type erase (const key_type& k);
(3) 
     void erase (iterator first, iterator last);

如上所示,C++98中map::erase並沒有返回值為iterator的原型函式。  那麼問題來了it=map.erase(it),然後對it進行操作會發生什麼呢?會發生傳說中的“未定義的行為”!包括但不限於程式掛掉、機器宕機、地球地震、宇宙毀滅等–原因是什麼呢?在執行map.erase(it)之後,it這個iterator已經失效了,考慮C語言中一個失效釋放了的指標,再次引用它會導致什麼問題呢?

在迴圈中正確使用map::erase的方法是什麼呢?如下:

for(map<int,int>::iterator it = mapInt.begin(); it != mapInt.end();)
{
    if(it->second == 0)
    {
        mapInt.erase(it++);
    }
    else
    {
        it++;
    }
}

在網上找mapInt.erase(it++)的說明,比較詳細的一種解釋為: http://blog.csdn.net/lmh12506/article/details/9167653  該方法中利用了字尾++的特點,這個時候執行mapInt.erase(it++);這條語句分為三個過程  1、先把it的值賦值給一個臨時變數做為傳遞給erase的引數變數

2、因為引數處理優先於函式呼叫,所以接下來執行了it++操作,也就是it現在已經指向了下一個地址。

3、再呼叫erase函式,釋放掉第一步中儲存的要刪除的it的值的臨時變數所指的位置。  然而個人感覺比較費解,意思是第一步先把it的值傳給了函式呼叫的形參,然後又回去執行i+1的操作嗎?這樣總感覺it++的執行被硬生生的切成了兩部分,只能硬記住這一結論。  直到後來看了《STL原始碼剖析》中的++i和i++實現方式的區別,然後某一天,再看到《More Effective C++》裡的說明,突然開竅了,mapInt.erase(it++)的機理終於不再神祕。  其實在mapInt.erase(it++)中,it++確實是作為一個完整的執行過程,it++的具體實現程式碼其實類似以下:

// postfix form: fetch and increment
map<int, int>::iterator operator++(int)//通過一個多餘的int引數與prefix++區分
{
    map<int, int>::iterator tmp = *this; // fetch
    increment(); // increment,map內部由紅黑樹實現,此函式負責指向下一個有序元素的iterator
    return tmp; // return what was
}

上面程式碼的最終返回的值其實是tmp,tmp儲存的是*this的舊值,this後來通過increment函式自增了,但是tmp的依然保持原值,最後將tmp返回賦值作為erase的引數,所以在mapInt.erase(it++)中,其實it++是作為一個整體執行完成了的,在傳值給erase函式之前,it其自身其實已經+1了,不過後綴++返回的卻是一個未執行+1操作的舊值,所以後面erase函式依然刪除的是原it位置的值,同時該迭代器失效,然而之前it已經+1自增過了,所以不受其影響噢。  關於上面程式碼中呼叫的字首++程式碼類似如下:

// prefix form: increment and fetch
map<int, int>::iterator& operator++()
{
    increment(); // increment
    return *this; // fetch
}

也正因為字尾++會比字首++的操作多一個臨時變數,並且其是以傳值複製的方式返回給呼叫方,所以一般而言字尾++的效率會比字首++效率低一些。

值得一提的是,在最新的C++11標準中,已經新增了一個map::erase函式執行後會返回下一個元素的iterator,然而不知道啥時候C++11才能達到現在C++98的覆蓋程度,謹慎一點還是使用map.erase(it++)比較保險。 http://www.cplusplus.com/reference/map/map/erase/

C++11
(1) 
iterator  erase (const_iterator position);
(2) 
size_type erase (const key_type& k);
(3) 
iterator  erase (const_iterator first, const_iterator last);
  • 最後,有的小夥伴可能會問為啥字首++和字尾++的返回值一個是迭代器引用,一個卻是迭代器傳值?簡單來說,字首++返回的便是傳參進來的迭代器,自然可以返回迭代器本身的引用,然而後綴++返回的是一個函式內部的臨時變數,在函式執行完後便析構了,必然不能傳引用。注意既然是通過傳值的方式返回,對其返回值的修改對於原it是沒有影響的,舉例來說(it++)++的結果其實it只自增了一次,第二次++只是對其(it++)的返回值執行了++,對原it沒有任何效果。