STL關聯式容器中刪除元素的方法和陷阱 四 (轉)

worldblog發表於2007-12-14
STL關聯式容器中刪除元素的方法和陷阱 四 (轉)[@more@]

在STL(標準模板庫)中經常會碰到要刪除容器中部分元素的情況,本人在中就經常編寫這方面的程式碼,在編碼和測試過程中發現在STL中刪除容器有很多陷阱,網上也有不少網友提到如何在STL中刪除元素這些問題。

上一篇文章主要討論序列式容器vector、list中安全刪除元素的方法和可能會遇到的陷阱,這一次討論在map(multimap)容器中如何安全的刪除和插入元素。

map(multimap)容器為關聯式容器,是程式設計中經常使用的容器,有鍵值(key)和實值(value),又稱字典、對映表。

你能看出以下程式碼有什麼問題?

例1:

#pragma warning (disable : 4786)

#include

#include

using namespace std;

void main() {

  map< int, int* > mnt;

  for ( int i = 0; i < 5; i++ ) {

    mapInt[ i ] = new int( i );

  }

  //  再插入鍵值為2的元素

  mapInt[ 2  ] = new int( 2 );

 

  //  做一些操作

  //  刪除。

  map< int, int* >::iterator itMap = mapInt.begin();

  for ( ; itMap != mapInt.end();  ++itMap ) {

  delete itMap->second;

  }

}

 :namespace prefix = o ns = "urn:schemas--com::office" />

例2:

void main() {

 

  map< int, int* > mapInt;

  for ( int i = 0; i < 5; i++ ) {

    mapInt.insert( make_pair( i, new int( i ) ) );

  }

  //  再插入鍵值為2的元素

  mapInt.insert( make_pair( 2, new int( 2 ) ) );

 

  //  做一些操作

  //  刪除記憶體。

  map< int, int* >::iterator itMap = mapInt.begin();

  for ( ; itMap != mapInt.end();  ++itMap ) {

  delete itMap->second;

  }

}

 

例3:

void main() {

  map< int, int > mapInt;

  for ( int i = 0; i < 5; i++ ) {

    mapInt.insert( make_pair( i,  i ) );

  }

 

  mapInt.insert( make_pair( 5,  2 ) );

 

  //  刪除所有實值為2的元素

  map< int, int >::iterator itMap = mapInt.begin();

  for ( ; itMap != mapInt.end();  ++itMap ) {

  if (  itMap->second == 2 ) {

    mapInt.erase( itMap );

  }

  }

}

 

分析:

  例1將導致記憶體洩露,因為mapInt[ 2  ] = new int( 2 );這條語句把原來鍵值為2的元素的實值指標覆蓋了,原來的指標就成為野指標,導致記憶體洩露。

例2也將導致記憶體洩露,因為mapInt.insert( make_pair( 2, new int( 2 ) ) );這條語句因為鍵值為2的元素已經存在,導致插入元素失敗,所以指向剛分配的記憶體的指標成為野指標,導致記憶體洩露。

map容器插入元素的方法。可以map容器的insert成員插入元素,或者直接用map容器的下標運算式賦值,但這裡有一個地方要注意,如果實值為指標的話,插入重複鍵值的元素時,會導致記憶體洩露。所以對於插入元素時,必須檢查是否插入成功。

 

正確的方法:

void main() {

  map< int, int* > mapInt;

  bool bRet;

  for ( int i = 0; i < 5; i++ ) {

  int* pI = new int( i );

  bRet = mapInt.insert( make_pair( i, pI ) ).second;

  if ( !bRet ) {

  //  插入元素不成功。

    delete pI;

  }

  }

  //  再插入鍵值為2的元素

  int* pI = new int( 2 );

  bRet = mapInt.insert( make_pair( 2, pI ) ).second;

  if ( !bRet ) {

  //  插入元素不成功。

  delete pI;

  }

 

  //  做一些操作

  //  刪除記憶體。

  map< int, int* >::iterator itMap = mapInt.begin();

  for ( ; itMap != mapInt.end();  ++itMap ) {

  delete itMap->second;

  }

}

 

例3將導致未定義的錯誤,在中即是訪問記憶體,程式當掉。因為mapInt.erase( itMap );呼叫後itMap迭代器已無效了,所以當++itMap時,訪問非法記憶體,導致程式當掉。

如果erase()總是返回下一元素的位置,那就可以像在vector容器中刪除元素一樣,如:

//  刪除所有實值為2的元素

  map< int, int >::iterator itMap = mapInt.begin();

  for ( ; itMap != mapInt.end();  ) {

  if (  itMap->second == 2 ) {

    itMap = mapInt.erase( itMap );

  }

  else {

    ++itMap;

  }

 

  }

但是,注意,以上的方式只在vc使用P.J.STL中才能編譯透過,而使用SGI STL庫則編譯不過,因為SGISTL庫在設計中考慮到如果不需要這一特性,就會損失,因此否決了這種想法。所以要保證可移植性,最好採用下面的方法:

 

//  刪除所有實值為2的元素

  map< int, int >::iterator itMap = mapInt.begin();

  for ( ; itMap != mapInt.end();  ) {

  if (  itMap->second == 2 ) {

  //  itMap++將使itMap指向下一個元素,但返回原迭代器的副本,所以

  //  erase()被呼叫時,itMap不指向將要被刪除的元素

    mapInt.erase( itMap++ );

  }

  else {

    ++itMap;

  }

  }

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/10752043/viewspace-993405/,如需轉載,請註明出處,否則將追究法律責任。

相關文章