やあ子どもたち、今日はタイトルそのままの内容だよ。vectorコンテナの内容のうち、ある条件を満たすものだけを超高速に一括削除したい場合は、eraseと、remove_ifを使うものだってことはもうおじさんも何年も前から記事に書いたりしてきたし、EffectiveSTLとかにも書いてあるし、今やどこにでも書いてあることだから皆もよく知っていることと思う。
しかし、気をつけろ!これには大きな潜在的罠が潜んでいることをご存知だろうか。例えば以下の短い事故例を見てみよう。
vector< int > intv; for( int i=0; i<10; ++i ) intv.push_back( i ); intv.erase( remove_if( intv.begin(), intv.end(), [](int a){ return a&1;} ) ); for( auto& i: intv ) cout<< i<<", "; cout<<endl;
これは1から10の値をvectorコンテナに詰めて、そのうち奇数のみを一括除去しようとするプログラムだが、このプログラムは期待通りには動かず、出力は以下のようになる。
0, 2, 4, 6, 8, 6, 7, 8, 9,
こういう出力になる理由はおわかりだろうか、そう、上記プログラムでは、
erase()の2つ目の引数を書き忘れて、結果反復子を1つしか渡していない。erase()には引数が2つのバージョンと、1つのバージョンとがあり、引数が1つのバージョンは、与えられた反復子の示す場所のインスタンスが1つ除去されるだけだ。なので、remove_ifで返される反復子の場所、つまり、ゴミ領域の最初の要素が1つ除去されたのみの結果となった。(9つの要素数しか表示されてない、つまり要素が1つ削除されたのみというわけだ)
言うまでもなく、正しいeraseの行はこうなる。
intv.erase( remove_if( intv.begin(), intv.end(), [](int a){ return a&1;} ), intv.end() );
結果もいちおう載せておこう。
0, 2, 4, 6, 8
ここでの問題は「erase()の引数に間違って引数を1つしか与えなかった場合にコンパイラは何も教えてはくれない」ということだ。このバグを踏んだ者は、プログラムの奇妙な動作に出くわし、その原因を探し当てるのに少なからず時間をさいてしまう可能性があるということだ。実に恐ろしい罠だと思わないかこれ。
そこでおじさんは考えた。erase文の書き間違えを一切なくすためには、このerase-remove_if行を、そのまま別の何かでラップしてしまえばよいではないかと。夢、そのような過ちは起こるまいと。そこで紹介しよう。
template< class T, class Pred > void my_remove_and_erase( vector< T >& v, Pred pred ) { v.erase( remove_if( v.begin(), v.end(), pred ), v.end() ); return; }
はい、いかがだろうか。my_remove_and_erase()を使えば、紛らわしいvector::erase-remove_ifの記述を簡潔に書けるばかりでなく奇妙な動作のバグ地獄に堕ちる可能性も一切なくなるぞ。では今日の最後は以下にmy_remove_and_erase()を使った、上記プログラムの全リストを掲げて、今日はお別れとしよう。
vector< int > intv; for( int i=0; i<10; ++i ) intv.push_back( i ); my_remove_and_erase( intv, [](int a){return a&1;} ); for( auto& i: intv ) cout<< i<<", "; cout<<endl;
随分すっきりとするし、恐ろしい罠を踏む確率もゼロだね。チャオ!
Effective STL―STLを効果的に使いこなす50の鉄則
- 作者: スコットメイヤーズ,Scott Meyers,細谷昭
- 出版社/メーカー: ピアソンエデュケーション
- 発売日: 2002/01
- メディア: 単行本
- 購入: 9人 クリック: 155回
- この商品を含むブログ (95件) を見る