誰かがファンクターを手伝ってくれるかどうか疑問に思っていました。ファンクターとは何か、それらがどのように機能するのかをよく理解していません。グーグルで検索してみましたが、まだわかりません。ファンクターはどのように機能し、テンプレートでどのように機能しますか
2 に答える
ファンクターは基本的に「関数オブジェクト」です。これは、クラスまたは構造体でラップした単一の関数であり、他の関数に渡すことができます。
それらは、関数呼び出し演算子 ( operator() と呼ばれる) をオーバーロードする独自のクラスまたは構造体を作成することによって機能します。通常、ファンクターを取る関数の引数としてインプレースで構築するだけで、そのインスタンスを作成します。
次のものがあるとします。
std::vector<int> counts;
ここで、そのベクトルに含まれるすべてのカウントをインクリメントします。それらを手動でループしてインクリメントするか、ファンクターを使用できます。この場合、適切なファンクターは次のようになります。
struct IncrementFunctor
{
int operator() (int i)
{
return i + 1;
}
}
IncrementFunctor は、任意の整数を取り、それをインクリメントするファンクターになりました。これをカウントに適用するには、引数としてファンクターを取る std::transform 関数を使用できます。
std::transform(
counts.begin(), // the start of the input range
counts.end(), // the end of the input range
counts.begin(), // the place where transform should place new values.
// in this case, we put it right back into the original list.
IncrementFunctor()); // an instance of your functor
構文 IncrementFunctor() は、std::transform に直接渡されるそのファンクターのインスタンスを作成します。もちろん、インスタンスをローカル変数として作成して渡すこともできますが、これははるかに便利です。
さて、テンプレートに。std::transform のファンクターの型はテンプレート引数です。これは、 std::transform がファンクターがどのタイプであるかを知らない (または気にしない!) ためです。気にするのは、次のようなことができるフィッティング operator() が定義されていることだけです。
newValue = functor(oldValue);
コンパイラはテンプレートについて非常に賢く、多くの場合、テンプレートの引数が何であるかを独自に把握できます。この場合、コンパイラは、std::transform でテンプレート型として定義されている IncrementFunctor 型のパラメーターを渡していることを自動的に認識します。リストに対しても同じことが行われるため、コンパイラは実際の呼び出しが次のようになることを自動的に認識します。
std::transform<std::vector<int>::iterator, // type of the input iterator
std::vector<int>::iterator, // type of the output iterator
IncrementFunctor>( // type of your functor
counts.begin(), // the start of the input range
counts.end(), // the end of the input range
counts.begin(), // the place where transform should place new values.
// in this case, we put it right back into the original list.
IncrementFunctor()); // an instance of your functor
入力の手間がかなり省けます。;)
ファンクターは、関数呼び出し operatorを使用して呼び出す/呼び出すことができるもので、構文的には を追加()
し、オプションで括弧内に引数リストを指定します。
テンプレートに必要なのはこれだけです。テンプレートに関する限り、 this が呼び出されるのは、この構文を許可するもの、つまり、フリー関数またはオーバーライドするクラスのインスタンスのいずれかoperator()()
です。(「フリー」関数とは、メンバーではない関数のことです。つまり、グローバル スコープの関数、または以前に含まれていた名前空間のスコープの関数です。)
テンプレートのメタプログラミング以外では、通常、フリー関数がファンクターであるとは言いません。その名前は、オーバーライドするクラスのインスタンス用に予約されていますoperator()()
。
struct Foo {
public:
void operator()( int i ) { // do something }
void operator()( int i, char x ) { // do something else }
}
C++ ではテンプレートがコンパイルされるため、構文が理にかなっている限り、コンパイラは喜んで関数またはファンクターを使用します。
template<typename T> class Bar {
private int j ;
public:
Bar( int i ) : j(i) {}
void doIt(T t) {
t( j ) ;
}
}
Foo f;
extern void resize( int i ) ; // in some header
Bar<Foo> bf( 5 ) ;
// a Bar that is templated on Foo
Bar< void (&)(int) > br( 5 ) ;
// a Bar that is templated on a function taking int and returning void
br.doit( &resize ) ; // call resize with br.j
bf.doit( f ) ; // call Foo::operator()(int) on Foo f with bf.j