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() );
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 というテンプレートクラスを継承したクラスに operator() というオペレータ関数を定義して、その関数オブジェクト(?)を作れるようにする必要があります。*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

*1:2007-07-19:修正です。普通の関数でも問題ありません。

*2:2007-07-19:修正です。

*3:2007-07-19:修正です。削除対象の要素は後ろに行くわけではなく、その処理については不定です。