remove_ifの使い方
STLのremove_ifという便利そうな名前に惹かれて使ってみようとしたのですが、これが結構難しいというか、動くサンプルが無いとどう書けばいいのか戸惑うこと間違いなし。自分自身が泥沼にはまりそうになったので一度頭を整理するためにまとめなおします
以下のエントリについて
2007-07-18 id:cxxさんによりこのエントリの間違いが指摘されましたので、該当箇所を修正しました。修正点については次のエントリをご覧ください。
2つのremove_if
まずremove_ifには2つの種類があり、それぞれの定義は以下のようになっています。
- list::remove_if .. listのメソッドとしてのremove_if
template<class Predicate> void remove_if( Predicate _Pred ) // 大雑把な呼び方サンプル. list<int> vList; vList.remove_if( RemoveTest() );
- remove_if .. アルゴリズム(要#include
)の関数
template<class ForwardIterator, class Predicate> inline ForwardIterator remove_if( ForwardIterator first, ForwardIterator last, Predicate pred ) // 大雑把な呼び方サンプル. list<int> vList; list<int>::iterator ir = remove_if( vList.begin(), vList.end(), RemoveTest() ); vList.erase( ir, vList.end() );
二つのremove_ifでは呼び方が違うだけではなく、処理した内容そのものが違っているので注意が必要です。
remove_if その1 (list::remove_if)
まず、listのメソッドとしてのremove_ifの使い方を、実際に動作するコードで見てみます。
#include <list> using namespace std; template <class T> class RemoveTest : public std::unary_function<T, bool> { public: // このメソッドがtrueを返すときにremoveされます. bool operator() ( T& val ){ return ((val%2) == 0); // 偶数は削除対象. } }; int main( ) { // set. list<int> nums; for( int i=0; i<8; i++ ){ nums.push_back( i ); } // print BEFORE remove_if. list<int>::iterator ii=nums.begin(); for( ; ii!=nums.end(); ii++ ){ printf("%d,", *ii); } printf("\n"); // → 0,1,2,3,4,5,6,7, が出力されます. nums.remove_if( RemoveTest<int>() ); // print AFTER remove_if. ii=nums.begin(); for( ; ii!=nums.end(); ii++ ){ printf("%d,", *ii); } printf("\n"); // → 1,3,5,7 が出力されます. }
std::unary_function*1
この関数がtrueを返すとき、その要素が削除処理対象となるわけです。
別に継承したクラスをテンプレートクラスにする必要も無いので、以下のようにも書けますし、そのほうが分かりやすいですね。
以下にそのサンプルの差分を書いておきます。
// 継承クラスの定義部分. class RemoveTestInt : public std::unary_function<int, bool> { public: bool operator() ( int& val ){ return ((val%2) == 0); // 偶数は削除対象. } }; // 呼び出し部分. nums.remove_if( RemoveTestInt() );
remove_if その2 (list::remove_if)
C++でコードを書いているとクラスインスタンスのポインタをlistとかにして処理し、remove_ifでの条件はprivateメンバを使った計算をしたいことがあります。
その場合には以下のようなコードを書くことで実装することが可能です。
// クラス定義. class CValue { private: int mValue; public: void set( int v ){ mValue = v; } int get(){ return mValue; } public: // remove_if用のオブジェクトクラス定義. class RemoveTest : public std::unary_function<CValue*, bool> { public: bool operator() ( CValue*& pval ){ return ((pval->get()%2) == 0); } }; }; int main( ) { // set. list<int> nums; for( int i=0; i<8; i++ ){ nums.push_back( i ); } // print BEFORE remove_if. list<CValue*>::iterator vi = val_list.begin(); for( ; vi!=val_list.end(); vi++ ){ printf("%d,", (*vi)->get() ); } printf("\n"); // → 0,1,2,3,4,5,6,7, が出力されます. val_list.remove_if( CValue::RemoveTest() ); // print AFTER remove_if. vi = val_list.begin(); for( ; vi!=val_list.end(); vi++ ){ printf("%d,", (*vi)->get() ); } printf("\n"); // → 1,3,5,7 が出力されます. }
remove_if その3 (STLアルゴリズムのremove_if)
以上の2例と似ているようでかなり違うのがSTLアルゴリズムのremove_ifです。
アルゴリズムのremove_ifの場合、実際にリストから削除は行われません。その代わりに、remove_ifで与えた条件関数によって削除対象となった要素がリストの末尾にまとめられ、それの先頭位置がremove_ifが返すイテレータとなるわけです。*2削除対象にならなかった要素が先頭にまとめられ、その残りの部分の先頭となる要素を指すイテレータがstd::remove_ifが返す値となるのです。
なので、remove_ifを行った後、ユーザはそのイテレータを元に削除処理を行う必要があるのです。
以下にサンプルを載せておきます。
void print_list_int( list<int>& l ) { list<int>::iterator ii=l.begin(); for( ; ii!=l.end(); ii++ ){ printf("%d,", *ii); } printf("\n"); } int main( ) { // set. list<int> nums; for( int i=0; i<8; i++ ){ nums.push_back( i ); } // print BEFORE remove_if. print_list_int( nums ); // → 0,1,2,3,4,5,6,7, と出力されます. // アルゴリズムのremoveは移動のみ.. list<int>::iterator ri = remove( nums.begin(), nums.end(), RemoveTestInt() ); // print AFTER remove_if. print_list_int( nums ); // → 1,3,5,7,4,5,6,7, と出力されます. // 実際に削除します. nums.erase( ri, nums.end() ); // print AFTER remove_if, erase. print_list_int( nums ); // → 1,3,5,7, と出力されます. }
deleteとかしたい場合に便利なのかな?と思うのですが、削除対象になった要素の値は不定になってしまうのがよく分かりません。*3