関数オブジェクト(後編)
 
(このコンテンツはメールマガジンの STL & iostream 入門に手を加えたものです。「 STL と iostream が使えるかのチェック」等はメールマガジンの方のページをご覧ください)
 
 演算子と関数オブジェクト ( #10 )
 
 関数オブジェクトが役立つのは、アルゴリズムを「クラスの配列」に使用する場合です。既存のクラスを使用する場合、アルゴリズムが使用する演算子を持っていないことがほとんどです。例えば次のようなクラス。
 
class CNoOperator
{
public:
    int m_i;
};
	
 
 == は使えない例  このクラスは int 型のメンバ変数を持っていますが、この変数に = などで直接アクセスすることはできません。ピリオド( . )を使ってメンバとしてアクセスする必要があります。なので、このクラスの配列は == 演算子を使用する std::count() には使用できません。
 こういう場合には関数オブジェクトが有効です。たとえば、次のようなプレディケートを作成することでこの CNoOperator クラスを std::count_if() に使用できるようになります。
 
////////////////////////////////////////////////////////////////
//    std::count_if() の使用例。
#include <stdio.h>
#include <functional>
#include <algorithm>

// 使用したいクラス。
class CNoOperator
{
public:
    int m_i;
};

// そのためのプレディケート。
class CPredForNoOperator
{
public:
    bool operator()( CNoOperator &p_rcNoOperator )
    {
        if( p_rcNoOperator.m_i == 100 )
            return true;
        return false;
    }
};

// 使用例。
void Use_count_if2()
{
    CNoOperator cAry[6];
    int iNo;
    iNo = std::count_if( cAry, cAry + 6, CPredForNoOperator() );
    printf( "%d\n", iNo );
}
// 結果
2

/////////////////////////////
	
 
代わりにプレディケートを使用する例  このプレディケート CPredForNoOperator は CNoOperator を受け取ることができます。これを std::count_if() に渡すことで、 CNoOperator の配列もアルゴリズムで処理できるようになります。
 
 実際には、この例では == 演算子をグローバル関数としてオーバーロードすることでも解決できます。でも、演算子のオーバーロード関数でも解決できない場合があります。
 たとえば、文字列などの「組込型」どうしの場合には、オーバーロード関数を作ることができません。また = 演算子は必ずメンバ関数として作る必要があるため、これも無理です。
 そういう時にも関数オブジェクトが役立ちます。
 
////////////////////////////////////////////////////////////////
//    関数オブジェクトの使用例。
#include <stdio.h>
#include <functional>
#include <algorithm>

// 関数オブジェクト。
class CIsLmn
{
public:
    bool operator()( const char *const p_pch )
    {
        const char *const chLmn = "LMN";
        return std::equal( chLmn, chLmn + 3, p_pch );
    }
};

// 使用例。
void Use_CIsLmn()
{
    const char ch1[] = "ABC";
    const char ch2[] = "LMN";
    const char ch3[] = "XYZ";
    const char *chAry[3] = { ch1, ch2, ch3 };

    const char **pchLmn
         = std::find_if( chAry, chAry + 3, CIsLmn() );
    if( pchLmn != chAry + 3 )
        printf( "%s\n", *pchLmn );
}

// 結果
LMN

/////////////////////////////
	
 
 std::find_if() と文字列を受け取る関数オブジェクトを使うことで、文字列どうしの比較を比較を行います。これが std::find() だと、文字列ポインタどうしの比較になってしまうので比較できません。
 アルゴリズムだけだと、使える型が限られてきます。これに関数オブジェクトを加えることで、自由度が飛躍的に向上するというわけです。
 
 関数オブジェクトの正しい作り方 ( #11 )
 
 #08 で言ったように、関数オブジェクトの作り方には「簡単」なものと「複雑」なものとがあります。今回はその「複雑」な方に目を向けてみましょう。
 #09 で紹介した CIsCarryUp を例にとって見てみます。まず、関数オブジェクトは std::unary_function std::binary_function のどちらかから継承する必要があります。このふたつはクラステンプレートになっていて、テンプレート引数に ( ) 演算子のオーバーロード関数の引数と戻り値を指定しなければなりません。
 この規則に則ると、次のようなクラスになります。
 
////////////////////////////////////////////////////////////////

class CIsCarryUpTrue
    : public std::unary_function< int, bool >
{
public:
    bool operator()( int p_i )
    {
        if( 5 <= p_i )
            return true;
        return false;
    }
};

/////////////////////////////
	
 
 CIsCarryUpTrue は引数をひとつだけ受け取るので std::unary_function から継承します。 std::plus のような引数をふたつ受け取るものは std::binary_function から継承します(ってことは CGenerator のように引数を受け取らないものは継承しなくていいということですね)。
 
透明
透明
■ C++ : 継承 ( #10 )
 C++ には「継承」という、あるクラスの機能を受け継いだ新しいクラスを作る方法があります。
 
class CParent
{
public:
    void Print()
    {
        printf( "ABCDE\n" );
    }
};
	
 
 というクラスがまずあったとします。このクラスが持つ Print() メンバ関数を持つクラスを作りたいなーと思ったら、このクラスから「継承」します。
 
class CChild
    : public CParent
{};
	
 
 2行目に「 : public CParent 」というのがありますね。この指定で「 CChild クラスは CParent クラスの機能を受け継いだ派生クラスです」という意味になり、実際に CChild クラスは CParent と同じく Print() メンバ関数を使うことができます。
 
    CChild cChild;
    cChild.Print();
	
 
 受け継げるものは public メンバのみですが、 public メンバであれば、メンバ関数(コンストラクタとデストラクタは除く)・メンバ変数・型メンバすべてを受け継ぐことができます。また、 protected メンバを継承先クラスのメンバ関数から呼び出すこともできます。
 
 継承を使うことで、継承元クラスが持つメンバを派生クラスすべてで使うことができるため、汎用性が高まります。たとえば STL の #11 で std::unary_function から派生する例を見ました。こうすることで std::unary_function の型メンバを派生クラスすべてで使用することになるため、 std::unary_function から派生した関数オブジェクトを使う場合には「型メンバの名前」についてはまったく迷う必要がなくなるのです。
透明
透明
 
 さて、継承することで変わったことはなんでしょう。実は、基本的な部分はまったく変わっていません。これまでと同じように使えます。唯一変わった部分は「型を明示的に取得できるようになった」点です。
 std::unary_function には std::unary_function::argument_type という「型」がメンバとして存在します。この型はテンプレート引数の第1引数と同じ型です。上の例だと int 型ということですね。同じく戻り値の型は std::unary_function::result_type で取得できます。これは bool ですね。
 つまり、この型メンバを使うと ( ) 演算子のオーバーロード関数の、引数と戻り値の型を取得できるのです。
 この機能は、次のようなクラスを作る時に役立ちます。
 
////////////////////////////////////////////////////////////////

template< class type_unary_function >
class CNewPred
    : public type_unary_function
{
    typename type_unary_function::argument_type m_Arg;
};

/////////////////////////////
	
 
 CNewPred クラスはテンプレート引数として受け取った型から継承します。そして、テンプレート引数が持つメンバ argument_type 型のメンバ変数を持ちます。
 これができるのは、テンプレート引数に std::unary_function 派生クラスを渡したときだけです。先ほどの CIsCarryUpTrue のように、ちゃんとテンプレート引数を指定して継承することで、 CIsCarryUpTrue が持つ情報を間接的に伝えることができるのです。このシステムを使えば、 CNewPred は std::unary_function に対応しさえすれば、多くの関数オブジェクトから簡単に派生できるようになるのです。
 この機能は、特に引数をふたつ受け取る関数オブジェクトにとって非常に大事です。次回紹介する「バインダー」は、この機能を使って必要な型を取得します。これから作る関数オブジェクトは、必ずこのような方法で作るようにしましょう。
 
透明
透明
■ C++ : typedef と typename ( #09 )
 typedef は、元々ある型に、別の名前を付ける機能です。例えば、 
 
typedef int Int;
	
 とすると、 Int int 型と同じになります。
 typedef は変数などと同じスコープを持つので、ローカルで使うと関数内でしか機能しません。
 
void Use_typedef1()
{
    typedef int Int;
}

void Use_typedef2()
{
//    Int i = 100;    // だめ!
}
	
 
 さらに、 typedef で作った「型の別名」を、クラスメンバにすることもできます。
 
class CTypedef
{
public:
    typedef int type_Int;
};
	
 
 この「型メンバ」はいわば「 static なメンバ」なので、クラス名と :: 演算子を使ってアクセスします。
 
void Use_CTypedef()
{
    CTypedef::type_Int i = 100;
    printf( "%d\n", i );
}
	
 
 型メンバはクラステンプレートで特に有効です。
 
template< class type_value >
class CTypedefInTemplate
{
public:
    typedef type_value type_Member;
};
	
 
 とすることで、テンプレート引数として与えられた型を型メンバとして使用することができます。これは STL でも iostream でも多用されているので、しっかり慣れておきましょう。
 
 さて、さらにややこしい話になります。「型メンバを持つクラスをテンプレートで指定する」場合、その型メンバはそのままでは使用できない場合があります。テンプレート引数なので、その型メンバの型が使用されるまで分からないからです。
 そういうときは「未知の型ですよ」という指定として typename というキーワードを使用します。
 
template< class type_arg >
void UseTypedefMember( type_arg p_arg )
{
    typename type_arg::type_Int i = 100;
    printf( "%d\n", i );
}
	
 
 typename を使うと、指定されたメンバを「型」と認識してくれます。もちろん実際に型でなければコンパイルエラーが出てくれるので安心です。
 C++ のテンプレートは、コンパイル時にチェックしてくれるタイプセーフな機能です。その分意味不明なコンパイルエラーに出くわしやすいので、 typedef や typename を使ってうまく回避するようにしましょう。
透明
透明
 
 さて、 CIsCarryUpTrue に話を戻しましょう。この関数オブジェクトは「5以上かどうか」を調べるプレディケートです。これだけだと本当に使えないので「比較する値を決められる」「クラステンプレート化して比較する型を決められる」ようにしましょう。それが次のクラスです。
 
////////////////////////////////////////////////////////////////

template< class type_value >
class CIsCarryUpPerfect
    : public std::unary_function< type_value, bool >
{
    type_value m_value;
public:
    CIsCarryUpPerfect( type_value p_value )
    {
        m_value = p_value;
    }

    bool operator()( type_value p_i )
    {
        if( m_value <= p_i )
            return true;
        return false;
    }
};

/////////////////////////////
	
 
 正直に言えばパーフェクトとはほど遠い( const 関係とか)ですが、これくらいまですればかなり使えるようになるでしょう。「ないものは作る」から「使い倒せるものを作る」へとステップアップすることが STL を無駄なく使う近道と言えるでしょう。
 と言っても「あるものは作らない」のがもちろん最善の策です。次回はその方法について見ていきましょう。
 
 バインダーで楽しよう ( #12 )
 
 関数オブジェクトが特定のアルゴリズムに使用できるかは、引数をいくつ取るかによって変わってきます。たとえば #07 では std::transform() std::negate を渡していました。これは std::negate が引数をひとつだけ受け取る関数オブジェクトだからです。 std::plus のように引数をふたつ受け取る関数オブジェクトは渡せません。
 でも実際は、 #07 の最後でちょっと触れたように、渡す方法が存在します。それは「バインダー」を使うことです。「バインダー」を使うと、関数オブジェクトが持つふたつの引数をひとつにすることができます。なくなった引数の部分には、特定の値を埋めることができます。
 では実際に試してみましょう。
 
////////////////////////////////////////////////////////////////
//    std::bind1st() の使用例。
#include <stdio.h>
#include <functional>
#include <algorithm>

void Use_bind1st()
{
    int iSourceAry[] = { 2, 5, 7 };
    int iAry[3];
    std::transform
        ( iSourceAry
        , iSourceAry + 3
        , iAry
        , std::bind1st( std::plus<int>(), 10 ) );    // これ。
    for( int *pi = iAry; pi != iAry + 3; ++pi )
        printf( "%d, ", *pi );
}

// 結果
12, 15, 17, 

/////////////////////////////
	
 
 STL のバインダー用関数のひとつが std::bind1st() です。この関数は「引数をひとつ受け取る関数オブジェクト」の代わりに使用できます。
  std::bind1st() の第1引数は「引数を2つ受け取る関数オブジェクト」です。この関数オブジェクトの引数をひとつにするのが std::bind1st() の役目です。
  std::bind1st() の第2引数は、第1引数の関数オブジェクトの第1引数に渡す値です。関数オブジェクトの引数を2つからひとつに減らすときに、減らした所に埋めるのがこの値です。
 つまり std::bind1st() を通すことで
 
std::bind1st( std::plus<int>(), 10 )
	
 
が、
 
std::plus( 10, x )
	
 
 になるということです。
 想像通り、 std::bind2nd() なんてものもあります。これは
 
std::bind2nd( std::plus<int>(), 10 )
	
 

 
std::plus( x, 10 )
	
 
 と、2番目の引数に渡してくれます。 std::plus() だと違いが表れませんが、 std::minus() とか比較用プレディケートでは大きな違いです。使い方が同じな分だけ使い間違えやすいと思うので注意してください。
 
 さてここでは std::plus() という初めから用意された関数オブジェクトを使用しました。もちろんこの代わりに自分で作った関数オブジェクトを使用することもできます。
 std::bind1st() に渡せる関数オブジェクトは、次の条件を満たしていなければなりません。
 
1:引数をふたつ受け取る関数オブジェクトである。
2:std::binary_function から継承している。
3:operator()() が const メンバ関数である。
 
 1は当然ですね。2が実は重要です。 std::bind1st() は前回説明した方法で std::binary_function から型の情報を取得するので、継承していないとコンパイルエラーになります。
[注: Visual C++ 6.0 ではコンパイルエラーどころかコンパイラが異常終了しました。ご注意ください]
 3は std::bind1st() が受け付ける関数オブジェクト(つまり第1引数)の型が const 参照なので、 const メンバ関数じゃないと呼べないからです。
 以上を満たすのは、たとえば次のような関数オブジェクトです。
 
////////////////////////////////////////////////////////////////

class CPlus
    : public std::binary_function< int, int, int >
{
public:
    int operator()( int p_Rh, int p_Lh ) const
    {
        return p_Rh + p_Lh;
    }
};

/////////////////////////////
	
 
 このような関数オブジェクトなら std::bind1st() に渡すことができます。
 と言うよりも、これが関数オブジェクトの一番正しい作り方なのです。自分が作った関数オブジェクトを使い倒すためにも、このようなルール通りの作り方を心がけるようにしましょう。
 
透明
透明
■ C++ : const メンバ関数 ( #11 )
 メンバ関数には「 const 」なものと「非 const 」なものと2種類あります。
 一般のメンバ関数は後者の「非 const 」なメンバ関数です。ところが、このメンバ関数は使用できない場合があるのです。
 
class CNotConst
{
public:
    // 非 const なメンバ関数。
    void NotConst()
    {}
};

void Use_CNotConst( const CNotConst &p_rcNotConst )
{
//  p_rcNotConst.NotConst();    // コンパイルエラー!
}
	
 
 この関数の中では、引数 p_rcNotConst は const な型になります。 const な型は、非 const メンバ関数は呼べないので、 CNotConst::NotConst() は呼べないのです。
 「じゃあ const 型への参照なんて使わなければいい」と思うかもしれません。ですが「 const 型へのポインタ」や「 const 型への参照」は STL や iostream でごく普通に使用されていますし、より安全で堅牢なプログラムを組むためには必要不可欠なシステムです。
 そこで出てくるのが「 const メンバ関数」です。
 
class CConst
{
public:
    // const メンバ関数。
    void Const() const
    {}
};

void Use_Const( const CConst &p_rcConst )
{
    p_rcConst.Const();
}
	
 
 const メンバ関数なら、 const 型でも呼び出すことができます。
 ひとつ注意することがあります。 const メンバ関数の中では、メンバ変数は const と見なされます。だから
 
    void Const() const
    {
        m_i = 100;
    }
	
 
 とか
 
    int &Const() const
    {
        return m_i;
    }
	
 
 とかはコンパイルエラーになります。後者は
 
    const int &Const() const
    {
        return m_i;
    }
	
 
 とすれば OK 。そうすれば m_i は書き換えられることがないので const を守ることができるのです。
 const メンバ関数は、不必要なメンバ変数の書き換えをコンパイル時にチェックすることができる非常に有用な機能です。ぜひ使いこなせるようにしてください。
透明
透明
 
 
 関数を関数オブジェクトに ( #13 )
 
 実は、普通の関数やメンバ関数も、関数オブジェクトにすることができます。前回のバインダーのように、特殊な変換用関数が用意されているのです。
 普通の関数を関数オブジェクトとして使う場合には、 std::ptr_fun() を使います。
 
////////////////////////////////////////////////////////////////
//    std::ptr_fun() の使用例。
#include <stdio.h>
#include <functional>
#include <algorithm>

bool Output( int p_i )
{
    printf( "%d, ", p_i );
    return true;
}

void Use_ptr_fun()
{
    int iSourceAry[] = { 2, 5, 7 };
    std::for_each
        ( iSourceAry
        , iSourceAry + 3
        , std::ptr_fun( Output ) );
}

// 結果
2, 5, 7, 

/////////////////////////////
	
 
 Output() は整数値を受け取って出力する関数です。この関数へのポインタをそのまま std::ptr_fun() に渡して、それを関数オブジェクトとして渡します。そうすると、 Output() が関数オブジェクトとして呼び出されるのです。
  std::ptr_fun() に渡せる関数にはいくつか制約があります。ひとつは引数の数がアルゴリズムから渡される数と一致すること。これは std::bind1st() などを使うことで調整することができます。
 もうひとつは、必ず戻り値を返すこと。これは std::ptr_fun() が戻り値を返す仕様になっているため、 void にできないからです。
 でも、2点さえ満たしていれば、これまで皆さんが作った関数を関数オブジェクトにすることができて、アルゴリズムに使用することができるのです。
 
 このように使用できるのは、関数だけじゃありません。クラスのメンバ関数も使用できます。
 
////////////////////////////////////////////////////////////////
//    std::mem_fun1() の使用例。
#include <stdio.h>
#include <functional>
#include <algorithm>

class COutput
{
public:
    bool Output( int p_i )
    {
        printf( "%d, ", p_i );
        return true;
    }
};

void Use_mem_fun1()
{
    COutput cOutput;    // 変数を作っておきます。
    int iSourceAry[] = { 2, 5, 7 };
    std::for_each
        ( iSourceAry
        , iSourceAry + 3
        , std::bind1st
            ( std::mem_fun1( &COutput::Output )    // 第1引数
            , &cOutput ) );    // 第2引数。
}

// 結果
2, 5, 7, 

/////////////////////////////
	
 
 ちょっとややこしいですね。順に見ていきましょう。
 まずメンバ関数を呼び出したいクラスの変数を作っておきます。まずこれが大事です。
 そのあとは先ほどの例とほとんど同じ。
 そして最後、 std::for_each() の最後の引数。まず std::bind1st() を使います。メンバ関数ポインタはそのメンバ関数を持つクラス型変数とペアで使います。このふたつを一組にするために std::bind1st() が必要なのです。
  std::bind1st() の第1引数には、 std::mem_fun1 を介して、呼び出したいメンバ関数へのポインタを渡します。
  std::bind1st() の第2引数にはそのメンバ関数を持つクラス型変数へのポインタを渡します。このクラスを通して、第1引数のメンバ関数が呼ばれるわけです。
 こうすることで、特定のクラスの特定のメンバ関数を関数オブジェクトとして使用できるのです。
 
 メンバ関数を関数オブジェクトに使用できるようになると、アルゴリズムの活用の幅が大きく広まるはず。ちょっと難しいかもしれませんが、挑戦してみてください。
 
透明
透明
■ C++ : メンバ関数ポインタ ( #12 )
 メンバ関数ポインタは、普通の関数ポインタとは大きく異なります。
 まず重要なのが、メンバ関数ポインタは、メンバ関数を持つクラス型変数とセットで使用する、という点です。メンバ関数ポインタは「特定のメンバ関数」を指し示すポインタを格納しますが、そのメンバ関数が属する変数は格納しません。メンバ関数ポインタとクラス型変数は別々に用意して、使う時は組み合わせて使用します。
 
class CMemberFunc
{
public:
    void MemberFunc()
    {
        printf( "MemberFunc()\n" );
    }
};

void Use_CMemberFunc()
{
    void ( CMemberFunc::*pmfnMemberFunc )()
        = &CMemberFunc::MemberFunc;
    CMemberFunc cMemberFunc;
    (cMemberFunc.*pmfnMemberFunc)();
}
	
 
 まずメンバ関数ポインタを格納する変数を作ります。この書式は
 
戻り値 ( クラス名::*メンバ関数ポインタの変数名 )( 引数 )
 
 となります。 pmfnMemberFunc が、メンバ関数ポインタを格納する変数の名前です。この変数に、メンバ関数のアドレスを格納します。格納できるのは「クラス名」に属するメンバ関数の中で「戻り値」「引数」ともに同じメンバ関数のみです。使えるクラスを制限することで、安全性を守っています。
 使う時は、クラス型変数と .* とメンバ関数ポインタをくっつけて使用します。そうすると「クラス型変数」が持つメンバ関数が呼ばれます。ちなみにこれは「クラス型変数」が「実変数」か「参照」のときで、「ポインタ」の時は ->* を使用します。 STL の #13 で紹介する std::mem_fun1() の例はこちらです。
 
 メンバ関数ポインタは「呼ぶメンバ関数を固定して、関係するクラス型変数をとっかえひっかえしたい」ときや「同じ戻り値・引数のメンバ関数を同じように呼び出したい」場合、 std::mem_fun1() を使用したときのように「特定のメンバ関数を変数として渡したい」ときに使用します。ただの関数ポインタよりも安全性が高いので、以上のような処理に気軽に使ってみてください。
透明
透明
 
(C)KAB-studio 2000 ALL RIGHTS RESERVED.