結果だけでなく過程も見てください

日々の奮闘を綴る日記です。

STLコンテナから特定の条件を持つ要素を削除する方法いろいろ

久しぶりにC++ジェネリックなコードを書くことになりました。
備忘録として整理しておこうと思います。

今回はlistコンテナを中心に扱います。

(1)listにそのまま値が入っている場合

こういう状態のとき。

list<int> nList;
nList.push_back(1);
nList.push_back(9);
nList.push_back(2);
nList.push_back(9);
nList.push_back(3);
nList.push_back(9);

「9」が入っている要素だけを削除したい場合、以下のように書きます。

nList.remove_if( 9 );

結果、nListの要素は「1, 2, 3」となります。簡単ですね。

vector, deque, stringの場合

上の方法ではダメです。(remove_ifメンバ関数がないのでコンパイルエラーになる)
algorithmヘッダのremove_ifを使います。

vector<int> v;	
	:
v.erase( remove_if( v.begin(), v.end(), 9 ), v.end() );	

(2)述語がtrueのときだけ削除したいときは?

「述語」という用語を覚えてください。
「述語」とはbool型の値(または暗黙のうちにbool型に変換できる値)を返す関数のことです。

以下の形をしたものです。このような述語があった場合。

bool isNine( int n )	
{	
    return (n == 9);
}

「9」が入っている要素だけを削除したい場合、以下のように書きます。

nList.remove_if( isNine );

結果、nListの要素は「1, 2, 3」となります。

(3)ファンクタ(関数オブジェクト)を使う方法

(2)は以下の方法でも可能です。

class CIsNineFunctor	
{	
public:	
    bool operator() ( int n ){ return (n==9); }
};	

「9」が入っている要素だけを削除したい場合、以下のように書きます。

nList.remove_if( CIsNineFunctor() );

結果、nListの要素は「1, 2, 3」となります。

(4)述語がメンバ関数の場合

以下のようなキャラクタークラスがあり、死んだら(isDead()がtrueなら)リストから消去することを考えます。

class CCharacter	
{	
public:	
    bool isDead(){ return (m_nHP <= 0); }

private:	
    int m_nHP;  // キャラクターのHP。0以下で死んだと判定する。
};	

ぱっと考えつくのは以下ですが、以下はコンパイルが通りません。なぜかわかりますか?
nList.remove_if( &CCharacter::isDead );

それはlistのremove_ifの実装が、(ざっくり言うと)以下のようになっているからです。
_Predに「&CCharacter::isDead」を渡していることになります。
「&CCharacter::isDead」は実体を指しているわけではないので、コンパイルは通りません。
考えてみれば、当たり前ですね。

template<class _Pr1>			
    void remove_if(_Pr1 _Pred)		
{			
    const _Nodeptr _Phead = this->_Myhead;		
    _Nodeptr _Pnode = _Phead->_Next;		

    while (_Pnode != _Phead)		
    {		
        if (_Pred(_Pnode->_Myval))	
        {	
            消す
        }	
        else	
        {	
            何もせず次へ
            _Pnode = _Pnode->_Next;
        }
    }	
}		

メンバ関数の場合はmem_funもしくはmem_fun_refを使いましょう。

// listの要素がCHoge*の場合はmem_funを使いましょう
ポインタの場合は、自動的にメモリは解放されません。
事前に何らかの方法で解放しておく必要があるのでご注意ください。

list<CCharacter*> charaList;	
	:
charaList.remove_if( mem_fun( &CCharacter::isDead ) );	
// CHogeの場合はmem_fun_refを使いましょう	
list<CCharacter> charaList;	
	:
charaList.remove_if( mem_fun_ref( &CCharacter::isDead ) );

(5)listの要素がshared_ptrなどの場合
mem_funやmem_fun_refは、listの要素のメンバ関数を渡すことができます。
ですが、実際にはlist > hogeList;
のようにスマートポインタなどを実装するが一般的でしょう。

つまり

list<CCharacter>やlistlist<CCharacter*>のときはOK!
charaList.remove_if( mem_fun( &CCharacter::isDead ) );
list<shared_ptr<CCharacter> >のときはNG。コンパイルが通りません。
charaList.remove_if( mem_fun( &CCharacter::isDead ) );

これを回避するのがmem_fnです。

charaList.remove_if( std::mem_fn( &CCharacter::isDead ) );

従来(VS2005くらい?)はmem_fnはboostの持ち物だったらしいのですが、
おそらくVS2008から(TR1から)標準ライブラリの仲間入り(つまりstd::mem_fn)になっているそうです。

mem_funがズバリそのものの型に対してp->hoge()とやっていた
ところを、テンプレートを使って一つクッションを置いています。たぶん。
イメージとしては(p)->hoge()という感じですかね?
前者は型が固定されているので、コンパイルエラーとなってしまうわけですね。

引数付のメンバ関数などは複雑なので、後日気が向いたら書こうと思います。

参考文献

STL標準講座 ハーバート・シルト(Herbert Schildt)著 επιστημη 監修
http://books.shoeisha.co.jp/book/b71872.html

Effective STL Scott Meyers著 訳:細谷 昭
http://www.pej-hed.jp/washo/271.html

プライバシーポリシー お問い合わせ