web-dev-qa-db-ja.com

イテレータでstd :: mapの要素を削除するにはどうすればよいですか?

std::mapをループし、その内容に基づいてアイテムを削除したいと思います。これはどの程度最善ですか?

57
user542687

C++ 11準拠のコンパイラを使用している場合、これを行う簡単な方法を次に示します。

std::map<K, V>::iterator itr = myMap.begin();
while (itr != myMap.end()) {
    if (ShouldDelete(*itr)) {
       itr = myMap.erase(itr);
    } else {
       ++itr;
    }
}

アイデアは、コンテナの先頭から末尾までイテレータを進め、各ステップで現在のキー/値のペアを削除するかどうかをチェックすることです。その場合、eraseメンバー関数を使用して反復処理された要素を削除し、マップ内の次の要素への反復子を返します。それ以外の場合は、イテレータを通常どおりに進めます。

C++ 11準拠のコンパイラがない場合、または古いコードベースを使用している場合、少し注意が必要です。 C++ 11より前は、eraseメンバー関数はマップ内の次の要素への反復子を返しませんでした。つまり、反復中に要素を削除するには、3つの部分からなるダンスを使用する必要があります。

  1. 現在のイテレータをコピーします。
  2. 現在の反復子を次の要素に進めます。
  3. 古いイテレータのコピーでeraseを呼び出します。

これはここに示されています:

std::map<K, V>::iterator itr = myMap.begin();
while (itr != myMap.end()) {
    if (ShouldDelete(*itr)) {
       std::map<K, V>::iterator toErase = itr;
       ++itr;
       myMap.erase(toErase);
    } else {
       ++itr;
    }
}

イテレータでeraseを呼び出した場合、invalidate itになるため、このプロセスが必要でした。これは、インクリメントやデクリメントなどの操作が未定義の動作につながることを意味します。上記のコードは、イテレータのコピーをセットアップし、itrを次の要素に移動して、イテレータの一時コピーを消去することでこれを回避します。

巧妙なトリッキーネスを使用すると、読みやすさを犠牲にしてこのコードを縮小できます。次のパターンは古いC++コードでは一般的ですが、C++ 11では必要ありません。

std::map<K, V>::iterator itr = myMap.begin();
while (itr != myMap.end()) {
    if (ShouldDelete(*itr)) {
       myMap.erase(itr++);  // <--- Note the post-increment!
    } else {
       ++itr;
    }
}

ここでポストインクリメント演算子を使用することは、古いイテレータのコピーを作成する賢い方法です(postfix ++演算子は元のイテレータ値のコピーを返すことに注意してください)一方で、古いイテレータも進めます。

101
templatetypedef
for(MyMap::iterator it = mymap.begin(); it!=mymap.end(); ) {
  if(mycondition(it))
    it = mymap.erase(it);
  else
    it++;
}

編集:これはMSVCでのみ動作するようです

edit2:c ++ 0xでは、これは連想コンテナでも機能します

8
Timo


これは1つの簡単な方法です。

    int value_to_delete( 2 );
    for( std::map<int, int>::iterator i = mm.begin(); i != mm.end(); ) {
        if( i->second != value_to_delete ) {
            mm.erase( i++ ); // advance before iterator become invalid
        }
        else {
            ++i;
        }
    }
7
Chan