C ++ファンクタとは何ですか?


876

C ++のファンクターについては、よく耳にします。誰かが私にそれらが何であるか、そしてどのような場合にそれらが役立つかについて概要を教えてもらえますか?


4
この主題は、この質問への回答として取り上げられました
。stackoverflow.com/ questions / 317450 / why

2
C ++でクロージャーを作成するために使用されます。
Copper.hat

以下の回答を見て、誰かが何をoperator()(...)意味するのか疑問に思っている場合、それは「関数呼び出し」演算子をオーバーロードしています。これは単に、オペレーターにとってオペレーターのオーバーロードです()operator()と呼ばれる関数の呼び出しを間違えないでくださいoperator。ただし、通常の演算子オーバーロード構文と見なしてください。
zardosht

回答:


1041

ファンクタは、operator()を定義するクラスにすぎません。これにより、関数のように見えるオブジェクトを作成できます。

// this is a functor
struct add_x {
  add_x(int val) : x(val) {}  // Constructor
  int operator()(int y) const { return x + y; }

private:
  int x;
};

// Now you can use it like this:
add_x add42(42); // create an instance of the functor class
int i = add42(8); // and "call" it
assert(i == 50); // and it added 42 to its argument

std::vector<int> in; // assume this contains a bunch of values)
std::vector<int> out(in.size());
// Pass a functor to std::transform, which calls the functor on every element 
// in the input sequence, and stores the result to the output sequence
std::transform(in.begin(), in.end(), out.begin(), add_x(1)); 
assert(out[i] == in[i] + 1); // for all i

ファンクタにはいくつか良い点があります。1つは、通常の関数とは異なり、状態を含むことができるということです。上記の例では、42を追加する関数を作成します。しかし、その値42はハードコーディングされておらず、ファンクターインスタンスを作成したときにコンストラクター引数として指定されています。別の値でコンストラクターを呼び出すだけで、27を追加した別の加算器を作成できます。これはそれらをうまくカスタマイズできるようにします。

最後の行が示すように、ファンクターをstd :: transformや他の標準ライブラリアルゴリズムなどの他の関数の引数として渡すことがよくあります。上記で述べたように、ファンクタは状態を含むため「カスタマイズ」でき、より柔軟になる(ただし、関数ポインタを使用したい場合は、関数を作成する必要がある)これは引数に正確に1を追加します。ファンクタは一般的であり、初期化したものをすべて追加します)。これらも潜在的に効率的です。上記の例では、コンパイラーはどの関数std::transformを呼び出すかを正確に認識しています。呼び出す必要がありますadd_x::operator()。つまり、その関数呼び出しをインライン化できます。これにより、ベクトルの各値に対して関数を手動で呼び出したかのように効率的になります。

代わりに関数ポインターを渡した場合、コンパイラーはそれが指す関数をすぐに認識できなかったため、かなり複雑なグローバル最適化を実行しない限り、実行時にポインターを逆参照してから呼び出しを行う必要があります。


32
この行を説明できますか?std :: transform(in.begin()、in.end()、out.begin()、add_x(1)); なぜそこにadd42ではなくadd_xと書くのですか?
アレック、2011

102
@Alecs両方とも機能します(ただし、効果は異なります)。を使用した場合add42、以前に作成したファンクタを使用し、各値に42を追加します。add_x(1)私ファンクタ、唯一の各値に1を加算1の新しいインスタンスを作成します。それは、ファンクタを最初に作成するのではなく、必要に応じて「オンザフライ」でインスタンス化し、実際に何かに使用する前にそれを維持することを頻繁に示すことです。
jalf

8
もちろん@zadane。operator()呼び出し側がそれを呼び出すために使用するので、それらはを持っている必要があります。ファンクターがメンバー関数、コンストラクター、演算子、メンバー変数に関して他に何を持っているかは完全にあなた次第です。
2014

4
@ rikimaru2013関数型プログラミングの用語では、そのとおりです。関数もファンクターですが、C ++の用語では、ファンクターは特に関数として使用されるクラスです。用語は早い段階で少し乱用されましたが、分割は有用な区別であり、今日も存続しています。C ++コンテキストで関数を「ファンクター」として参照し始めると、会話が混乱するだけです。
srm 2016年

6
それはクラスですか、それともクラスのインスタンスですか?ほとんどのソースでadd42は、ファンクターと呼ばれますadd_x(ファンクターのクラスまたはファンクタークラス)。ファンクタは関数クラスではなく関数オブジェクトとも呼ばれるため、この用語は一貫していると思います。この点を明確にしていただけますか?
セルゲイタシェノフ2016年

121

少し追加。を使用してboost::function、次のように関数とメソッドからファンクタを作成できます。

class Foo
{
public:
    void operator () (int i) { printf("Foo %d", i); }
};
void Bar(int i) { printf("Bar %d", i); }
Foo foo;
boost::function<void (int)> f(foo);//wrap functor
f(1);//prints "Foo 1"
boost::function<void (int)> b(&Bar);//wrap normal function
b(1);//prints "Bar 1"

また、boost :: bindを使用して、このファンクタに状態を追加できます

boost::function<void ()> f1 = boost::bind(foo, 2);
f1();//no more argument, function argument stored in f1
//and this print "Foo 2" (:
//and normal function
boost::function<void ()> b1 = boost::bind(&Bar, 2);
b1();// print "Bar 2"

さらに、boost :: bindとboost :: functionを使用すると、クラスメソッドからファンクタを作成でき、実際にはデリゲートです。

class SomeClass
{
    std::string state_;
public:
    SomeClass(const char* s) : state_(s) {}

    void method( std::string param )
    {
        std::cout << state_ << param << std::endl;
    }
};
SomeClass *inst = new SomeClass("Hi, i am ");
boost::function< void (std::string) > callback;
callback = boost::bind(&SomeClass::method, inst, _1);//create delegate
//_1 is a placeholder it holds plase for parameter
callback("useless");//prints "Hi, i am useless"

ファンクタのリストまたはベクトルを作成できます

std::list< boost::function<void (EventArg e)> > events;
//add some events
....
//call them
std::for_each(
        events.begin(), events.end(), 
        boost::bind( boost::apply<void>(), _1, e));

これらすべてに1つの問題があります。コンパイラのエラーメッセージは人間が読めるものではありません:)


4
operator ()クラスのデフォルトはプライベートなので、最初の例ではパブリックにすべきではありませんか?
NathanOliver

4
多分ある時点で、この答えは更新に値するでしょう。ラムダは何からでもファンクタを取得する最も簡単な方法だからです
idclev 463035818

102

Functorは、関数のように機能するオブジェクトです。基本的に、を定義するクラスoperator()

class MyFunctor
{
   public:
     int operator()(int x) { return x * 2;}
}

MyFunctor doubler;
int x = doubler(5);

本当の利点は、ファンクターが状態を保持できることです。

class Matcher
{
   int target;
   public:
     Matcher(int m) : target(m) {}
     bool operator()(int x) { return x == target;}
}

Matcher Is5(5);

if (Is5(n))    // same as if (n == 5)
{ ....}

11
関数ポインタのように使用できることを追加するだけです。
マーティンヨーク

7
@LokiAstari-コンセプトが初めての人にとっては、少し誤解を招く可能性があります。ファンクタは「のように」使用できますが、常に「代わりに」関数ポインタとは限りません。たとえば、関数ポインタをとる関数は、ファンクタが関数ポインタと同じ引数と戻り値を持っている場合でも、代わりにファンクタをとることはできません。しかし、概して、設計時にはファンクタが優先され、理論的には「よりモダンな」方法で進められます。
MasonWinsauer 2014年

なぜ2つ目intが戻るはずのときに戻るのboolですか?これはC ++ではなくC ++です。この回答が書かれたとき、bool存在しませんでしたか?
モニカの訴訟に資金を提供する

@QPaysTaxes私が推測するタイプミス。私はおそらく最初の例のコードをコピーして貼り付け、変更するのを忘れていました。私はそれを修正しました。
James Curran

1
@Riasat Matcherがライブラリにある場合、Is5()の定義は非常に簡単です。また、Is7()、Is32()などを作成できます。さらに、これは単なる例です。ファンクターはもっと複雑になる可能性があります。
James Curran 2017

51

「ファンクター」という名前は、C ++が登場するずっと前から伝統的にカテゴリー理論で使用されてきました。これはファンクタのC ++の概念とは関係ありません。名前関数オブジェクトを使用することをお勧めしますC ++では、 "functor"と呼ばれるものではなく、ことをお勧めします。これは、他のプログラミング言語が同様の構造を呼び出す方法です。

単純な関数の代わりに使用:

特徴:

  • 関数オブジェクトには状態がある可能性があります
  • 関数オブジェクトはOOPに適合します(他のすべてのオブジェクトと同様に動作します)。

短所:

  • プログラムをより複雑にします。

関数ポインタの代わりに使用:

特徴:

  • 関数オブジェクトはしばしばインライン化されるかもしれません

短所:

  • 関数オブジェクトは、実行時に他の関数オブジェクトタイプと交換できません(少なくとも、基本クラスを拡張しない限り、オーバーヘッドが発生します)。

仮想関数の代わりに使用:

特徴:

  • 関数オブジェクト(非仮想)はvtableとランタイムのディスパッチを必要としないため、ほとんどの場合より効率的です

短所:

  • 関数オブジェクトは、実行時に他の関数オブジェクトタイプと交換できません(少なくとも、基本クラスを拡張しない限り、オーバーヘッドが発生します)。

1
これらの使用例を実際の例で説明できますか?関数型を関数型ポインタおよび関数型ポインタとしてどのように使用できますか?
Milad Khajavi 2013

1
ファンクタが実際に状態を保持しているとはどういう意味ですか?
erogol 2013

ある種のポリモーフィズムを持つには基本クラスが必要であることを指摘してくれてありがとう。単純な関数ポインターと同じ場所でファンクターを使用する必要があるという問題があり、ファンクター基本クラスを作成するしか方法がありませんでした(C ++ 11のものは使用できないため)。私があなたの答えを読むまで、このオーバーヘッドが理にかなっているかどうか確信が持てませんでした。
idclev 463035818

1
@Erogolファンクタは、たまたま構文をサポートするオブジェクトfoo(arguments)です。したがって、変数を含めることができます。たとえば、update_password(string)関数がある場合、それがどのくらいの頻度で発生したかを追跡したい場合があります。ファンクタを使用すると、private long time最後に発生したタイムスタンプを表すことができます。関数ポインタまたはプレーン機能で、あなただけの直接ドキュメントや使用法によって、よりむしろdefinition.lによって関連している名前空間の変数外を使用する必要があるだろう
基金モニカの訴訟

4
name名前が理由なく構成されていることを言及してくださった。私は数学的(または必要に応じて関数)ファンクタとC ++のファンクタとの関係を検索しています。
Hi-Angel

41

他の人が述べたように、ファンクターは関数のように機能するオブジェクトです。つまり、関数呼び出し演算子をオーバーロードします。

ファンクタは一般的にSTLアルゴリズムで使用されます。関数型言語のクロージャのように、関数呼び出しの前後に状態を保持できるので便利です。たとえばMultiplyBy、引数に指定された量を乗算するファンクタを定義できます。

class MultiplyBy {
private:
    int factor;

public:
    MultiplyBy(int x) : factor(x) {
    }

    int operator () (int other) const {
        return factor * other;
    }
};

次に、MultiplyByオブジェクトをstd :: transformなどのアルゴリズムに渡すことができます。

int array[5] = {1, 2, 3, 4, 5};
std::transform(array, array + 5, array, MultiplyBy(3));
// Now, array is {3, 6, 9, 12, 15}

関数へのポインタに対するファンクタのもう1つの利点は、より多くの場合に呼び出しをインライン化できることです。関数ポインターをに渡した場合transformその呼び出しがインライン化され、コンパイラーが常に同じ関数をそれに渡すことを認識していない限り、ポインターを介して呼び出しをインライン化することはできません。


37

私たちの間で私のような初心者のために:少し調査した後、私はjalfが投稿したコードが何をしているかを理解しました。

ファンクタは、関数のように「呼び出す」ことができるクラスまたは構造体オブジェクトです。これは、をオーバーロードすることで可能になり() operatorます。() operator(その呼ばれるかわからない)任意の数の引数を取ることができます。他の演算子は2つしか取りません。つまり、+ operator2つの値(演算子の両側に1つ)しか取り込めず、オーバーロードした値を返します。内に任意の数の引数を収めることができ() operatorます。これにより、柔軟性が得られます。

ファンクタを作成するには、まずクラスを作成します。次に、選択したタイプと名前のパラメーターを使用して、クラスのコンストラクターを作成します。同じステートメントの後に、コンストラクターに対して以前に宣言されたパラメーターを使用してクラスメンバーオブジェクトを構築するイニシャライザリスト(単一のコロン演算子を使用します。これも初めてです)が続きます。次に、() operatorオーバーロードされます。最後に、作成したクラスまたは構造体のプライベートオブジェクトを宣言します。

私のコード(jalfの変数名がわかりにくいことがわかりました)

class myFunctor
{ 
    public:
        /* myFunctor is the constructor. parameterVar is the parameter passed to
           the constructor. : is the initializer list operator. myObject is the
           private member object of the myFunctor class. parameterVar is passed
           to the () operator which takes it and adds it to myObject in the
           overloaded () operator function. */
        myFunctor (int parameterVar) : myObject( parameterVar ) {}

        /* the "operator" word is a keyword which indicates this function is an 
           overloaded operator function. The () following this just tells the
           compiler that () is the operator being overloaded. Following that is
           the parameter for the overloaded operator. This parameter is actually
           the argument "parameterVar" passed by the constructor we just wrote.
           The last part of this statement is the overloaded operators body
           which adds the parameter passed to the member object. */
        int operator() (int myArgument) { return myObject + myArgument; }

    private: 
        int myObject; //Our private member object.
}; 

これのいずれかが不正確であるか、単なる間違っている場合は、遠慮なく修正してください!


1
()演算子は、関数呼び出し演算子と呼ばれます。括弧演算子と呼ぶこともできると思います。
ゴータム

4
「このパラメーターは、実際には、先ほど書いたコンストラクターによって渡された引数「parameterVar」です
オービットのライトネスレース

22

ファンクタは、パラメータ化(テンプレート化)された型に関数を適用する高次関数です。これは、マップ高次関数の一般化です。たとえば、次のstd::vectorようなファンクタを定義できます。

template<class F, class T, class U=decltype(std::declval<F>()(std::declval<T>()))>
std::vector<U> fmap(F f, const std::vector<T>& vec)
{
    std::vector<U> result;
    std::transform(vec.begin(), vec.end(), std::back_inserter(result), f);
    return result;
}

この関数は、a std::vector<T>を取り、a を返す関数がstd::vector<U>与えられると、a Fを取り、T戻りますU。ファンクタはコンテナタイプに対して定義する必要はありません。次のものを含む、あらゆるテンプレートタイプに対して定義できますstd::shared_ptr

template<class F, class T, class U=decltype(std::declval<F>()(std::declval<T>()))>
std::shared_ptr<U> fmap(F f, const std::shared_ptr<T>& p)
{
    if (p == nullptr) return nullptr;
    else return std::shared_ptr<U>(new U(f(*p)));
}

型をaに変換する簡単な例を次に示しますdouble

double to_double(int x)
{
    return x;
}

std::shared_ptr<int> i(new int(3));
std::shared_ptr<double> d = fmap(to_double, i);

std::vector<int> is = { 1, 2, 3 };
std::vector<double> ds = fmap(to_double, is);

ファンクタが従うべき2つの法律があります。1つは恒等則であり、ファンクターに恒等関数が与えられている場合、それは恒等関数を型に適用することと同じであるfmap(identity, x)必要があります。つまり、次のようになりますidentity(x)

struct identity_f
{
    template<class T>
    T operator()(T x) const
    {
        return x;
    }
};
identity_f identity = {};

std::vector<int> is = { 1, 2, 3 };
// These two statements should be equivalent.
// is1 should equal is2
std::vector<int> is1 = fmap(identity, is);
std::vector<int> is2 = identity(is);

次の法則は合成法です。これは、ファンクターに2つの関数の合成が与えられた場合、最初の関数にファンクターを適用し、2番目の関数に再度適用することと同じであることを示しています。したがって、以下fmap(std::bind(f, std::bind(g, _1)), x)と同じである必要がありますfmap(f, fmap(g, x))

double to_double(int x)
{
    return x;
}

struct foo
{
    double x;
};

foo to_foo(double x)
{
    foo r;
    r.x = x;
    return r;
}

std::vector<int> is = { 1, 2, 3 };
// These two statements should be equivalent.
// is1 should equal is2
std::vector<foo> is1 = fmap(std::bind(to_foo, std::bind(to_double, _1)), is);
std::vector<foo> is2 = fmap(to_foo, fmap(to_double, is));

2
functorはこの意味で正しく使用する必要があると主張している記事(en.wikipedia.org/wiki/Functorも参照)、および関数オブジェクトに使用するのはごちゃごちゃしているだけです:jackieokay.com/2017/01/26/functors.html Itただし、ここでは関数オブジェクトの意味のみを考慮している多数の回答があるため、これでは遅すぎる可能性があります。
armb 2017

2
この回答は、700を超える賛成投票の回答になります。誰かがHaskellをC ++よりもよく知っているので、C ++言語はいつも私を困惑させました。
mschmidt

カテゴリ理論とC ++?これはバルトス・ミレウスキーの秘密のSOアカウントですか?
Mateen Ulhaq 2018

1
標準の表記でファンクタ法則を要約すると便利かもしれません:fmap(id, x) = id(x)fmap(f ◦ g, x) = fmap(f, fmap(g, x))
Mateen Ulhaq 2018

@mschmidtはファンクターもこれを意味しますが、C ++は名前を多重定義して「関数オブジェクト」と同じことを意味します
Caleth

9

私の問題を解決するためにFunctorを使用せざるを得なかった実際の状況は次のとおりです。

私には一連の関数(たとえば、そのうちの20個)があり、それぞれが3つの特定の場所で異なる特定の関数を呼び出すことを除いて、すべて同じです。

これは信じられないほどの無駄であり、コードの重複です。通常、私は関数ポインターを渡し、それを3つのスポットで呼び出すだけです。(したがって、コードは20回ではなく、1回だけ出現する必要があります。)

しかし、私は、いずれの場合にも、特定の機能には完全に異なるパラメータープロファイルが必要であることに気付きました。2つのパラメーター、5つのパラメーターなど。

別の解決策は、特定の関数が派生クラスのオーバーライドされたメソッドである基本クラスを持つことです。しかし、私は本当にこの継承のすべてを構築したいのですか、それで私は関数ポインタを渡すことができますか????

解決策:それで私がやったことは、必要な関数を呼び出すことができるラッパークラス(「Functor」)を作成したことです。事前に(パラメーターなどで)設定してから、関数ポインターの代わりに渡します。これで、呼び出されたコードは、内部で何が起こっているのかを知らなくても、Functorをトリガーできます。複数回呼び出すこともできます(3回呼び出す必要がありました)。


それだけです-Functorが明白で簡単なソリューションであることが判明した実用的な例で、コードの重複を20の関数から1に減らすことができました。


3
ファンクターが異なる特定の関数を呼び出し、これらの他の関数が受け入れるパラメーターの数が異なる場合、これは、ファンクターがこれらの他の関数にディスパッチするために可変数の引数を受け入れたことを意味しますか?
johnbakers 14

4
あなたは、コードの一部を引用して上記のシナリオを説明してくださいすることができ、私は... C ++に新しいです、この概念を理解したい
サンジーブ

3

コールバックで使用される場合を除いて、C ++ファンクタは、マトリックス クラスにMatlab好みのアクセススタイルを提供するのにも役立ちます。があります。


これ(行列の例)はoperator()、関数オブジェクトのプロパティを単純に使用していますが、使用していません。
再ナルデスク

3

繰り返しになったように、ファンクターは関数(オーバーロード演算子())として扱うことができるクラスです。

これらは、一部のデータを関数の繰り返し呼び出しまたは遅延呼び出しに関連付ける必要がある状況で最も役立ちます。

たとえば、ファンクタのリンクリストを使用して、基本的な低オーバーヘッドの同期コルーチンシステム、タスクディスパッチャ、または割り込み可能なファイル解析を実装できます。例:

/* prints "this is a very simple and poorly used task queue" */
class Functor
{
public:
    std::string output;
    Functor(const std::string& out): output(out){}
    operator()() const
    {
        std::cout << output << " ";
    }
};

int main(int argc, char **argv)
{
    std::list<Functor> taskQueue;
    taskQueue.push_back(Functor("this"));
    taskQueue.push_back(Functor("is a"));
    taskQueue.push_back(Functor("very simple"));
    taskQueue.push_back(Functor("and poorly used"));
    taskQueue.push_back(Functor("task queue"));
    for(std::list<Functor>::iterator it = taskQueue.begin();
        it != taskQueue.end(); ++it)
    {
        *it();
    }
    return 0;
}

/* prints the value stored in "i", then asks you if you want to increment it */
int i;
bool should_increment;
int doSomeWork()
{
    std::cout << "i = " << i << std::endl;
    std::cout << "increment? (enter the number 1 to increment, 0 otherwise" << std::endl;
    std::cin >> should_increment;
    return 2;
}
void doSensitiveWork()
{
     ++i;
     should_increment = false;
}
class BaseCoroutine
{
public:
    BaseCoroutine(int stat): status(stat), waiting(false){}
    void operator()(){ status = perform(); }
    int getStatus() const { return status; }
protected:
    int status;
    bool waiting;
    virtual int perform() = 0;
    bool await_status(BaseCoroutine& other, int stat, int change)
    {
        if(!waiting)
        {
            waiting = true;
        }
        if(other.getStatus() == stat)
        {
            status = change;
            waiting = false;
        }
        return !waiting;
    }
}

class MyCoroutine1: public BaseCoroutine
{
public:
    MyCoroutine1(BaseCoroutine& other): BaseCoroutine(1), partner(other){}
protected:
    BaseCoroutine& partner;
    virtual int perform()
    {
        if(getStatus() == 1)
            return doSomeWork();
        if(getStatus() == 2)
        {
            if(await_status(partner, 1))
                return 1;
            else if(i == 100)
                return 0;
            else
                return 2;
        }
    }
};

class MyCoroutine2: public BaseCoroutine
{
public:
    MyCoroutine2(bool& work_signal): BaseCoroutine(1), ready(work_signal) {}
protected:
    bool& work_signal;
    virtual int perform()
    {
        if(i == 100)
            return 0;
        if(work_signal)
        {
            doSensitiveWork();
            return 2;
        }
        return 1;
    }
};

int main()
{
     std::list<BaseCoroutine* > coroutineList;
     MyCoroutine2 *incrementer = new MyCoroutine2(should_increment);
     MyCoroutine1 *printer = new MyCoroutine1(incrementer);

     while(coroutineList.size())
     {
         for(std::list<BaseCoroutine *>::iterator it = coroutineList.begin();
             it != coroutineList.end(); ++it)
         {
             *it();
             if(*it.getStatus() == 0)
             {
                 coroutineList.erase(it);
             }
         }
     }
     delete printer;
     delete incrementer;
     return 0;
}

もちろん、これらの例自体はそれほど有用ではありません。それらはファンクタがどのように役立つかを示すだけであり、ファンクタ自体は非常に基本的で柔軟性がないため、たとえば、ブーストが提供するものよりも機能が低くなります。


2

ファンクタはgtkmmで使用され、GUIボタンを実際のC ++関数またはメソッドに接続します。


pthreadライブラリを使用してアプリをマルチスレッド化する場合、Functorsが役立ちます。
スレッドを開始するためのの引数の1つは、pthread_create(..)自分のスレッドで実行される関数ポインターです。
しかし、不便な点が1つあります。このポインターは、それが静的メソッドでない限り、またはのようにクラス指定しない限り、メソッドへのポインターであってはなりませんclass::method。そしてもう1つ、メソッドのインターフェースは次のようにすることしかできません。

void* method(void* something)

そのため、特別なことをせずに、クラス内のメソッドを(単純な方法で)スレッドで実行することはできません。

C ++でスレッドを処理する非常に良い方法は、独自のThreadクラスを作成することです。MyClassクラスからメソッドを実行したい場合、私がしたことは、それらのメソッドをFunctor派生クラスに変換することでした。

また、Threadクラスにはこの static void* startThread(void* arg)
メソッドがありますpthread_create(..)。このメソッドへのポインタは、を呼び出すための引数として使用されます。そして、何startThread(..)引数に受け取るべきであるvoid*いずれかのヒープ内のインスタンスへのキャスト参照Functorバックにキャストされます派生クラス、Functor*実行された場合に、それのと呼ばれるrun()方法が。


2

追加するために、既存のレガシーメソッドをコマンドパターンに合わせるために関数オブジェクトを使用しました。(私が感じたOOパラダイムの真のOCPの美しさだけの場所); また、関連する関数アダプタパターンをここに追加します。

メソッドにシグネチャがあるとします。

int CTask::ThreeParameterTask(int par1, int par2, int par3)

コマンドパターンにどのように適合させるかを確認します。これを行うには、まず、関数オブジェクトとして呼び出すことができるように、メンバー関数アダプターを作成する必要があります。

注-これは醜く、Boostバインドヘルパーなどを使用できる場合がありますが、使用できない場合、または使用したくない場合は、これが1つの方法です。

// a template class for converting a member function of the type int        function(int,int,int)
//to be called as a function object
template<typename _Ret,typename _Class,typename _arg1,typename _arg2,typename _arg3>
class mem_fun3_t
{
  public:
explicit mem_fun3_t(_Ret (_Class::*_Pm)(_arg1,_arg2,_arg3))
    :m_Ptr(_Pm) //okay here we store the member function pointer for later use
    {}

//this operator call comes from the bind method
_Ret operator()(_Class *_P, _arg1 arg1, _arg2 arg2, _arg3 arg3) const
{
    return ((_P->*m_Ptr)(arg1,arg2,arg3));
}
private:
_Ret (_Class::*m_Ptr)(_arg1,_arg2,_arg3);// method pointer signature
};

また、上記のクラスが呼び出しを支援するために、ヘルパーメソッドmem_fun3が必要です。

template<typename _Ret,typename _Class,typename _arg1,typename _arg2,typename _arg3>
mem_fun3_t<_Ret,_Class,_arg1,_arg2,_arg3> mem_fun3 ( _Ret (_Class::*_Pm)          (_arg1,_arg2,_arg3) )
{
  return (mem_fun3_t<_Ret,_Class,_arg1,_arg2,_arg3>(_Pm));

}

ここで、パラメーターをバインドするために、バインダー関数を作成する必要があります。だから、ここに行く:

template<typename _Func,typename _Ptr,typename _arg1,typename _arg2,typename _arg3>
class binder3
{
public:
//This is the constructor that does the binding part
binder3(_Func fn,_Ptr ptr,_arg1 i,_arg2 j,_arg3 k)
    :m_ptr(ptr),m_fn(fn),m1(i),m2(j),m3(k){}

 //and this is the function object 
 void operator()() const
 {
        m_fn(m_ptr,m1,m2,m3);//that calls the operator
    }
private:
    _Ptr m_ptr;
    _Func m_fn;
    _arg1 m1; _arg2 m2; _arg3 m3;
};

そして、binder3クラスを使用するヘルパー関数-bind3:

//a helper function to call binder3
template <typename _Func, typename _P1,typename _arg1,typename _arg2,typename _arg3>
binder3<_Func, _P1, _arg1, _arg2, _arg3> bind3(_Func func, _P1 p1,_arg1 i,_arg2 j,_arg3 k)
{
    return binder3<_Func, _P1, _arg1, _arg2, _arg3> (func, p1,i,j,k);
}

ここで、これをCommandクラスで使用する必要があります。次のtypedefを使用します。

typedef binder3<mem_fun3_t<int,T,int,int,int> ,T* ,int,int,int> F3;
//and change the signature of the ctor
//just to illustrate the usage with a method signature taking more than one parameter
explicit Command(T* pObj,F3* p_method,long timeout,const char* key,
long priority = PRIO_NORMAL ):
m_objptr(pObj),m_timeout(timeout),m_key(key),m_value(priority),method1(0),method0(0),
method(0)
{
    method3 = p_method;
}

これを呼び出す方法は次のとおりです。

F3 f3 = PluginThreadPool::bind3( PluginThreadPool::mem_fun3( 
      &CTask::ThreeParameterTask), task1,2122,23 );

注:f3(); メソッドtask1-> ThreeParameterTask(21,22,23);を呼び出します。

次のリンクにあるこのパターンの完全なコンテキスト


2

関数を関数として実装することの大きな利点は、関数を呼び出し間で維持して再利用できることです。たとえば、文字列間のレーベンシュタイン距離を計算するWagner-Fischerアルゴリズムなど、多くの動的プログラミングアルゴリズムは、結果の大きなテーブルに入力することで機能します。関数が呼び出されるたびにこのテーブルを割り当てることは非常に非効率的であるため、関数をファンクタとして実装し、テーブルをメンバー変数にすることで、パフォーマンスを大幅に向上させることができます。

以下は、ファンクターとしてワーグナーフィッシャーアルゴリズムを実装する例です。operator()必要に応じてサイズを変更しながら、テーブルがコンストラクターで割り当てられ、で再利用される方法に注意してください。

#include <string>
#include <vector>
#include <algorithm>

template <typename T>
T min3(const T& a, const T& b, const T& c)
{
   return std::min(std::min(a, b), c);
}

class levenshtein_distance 
{
    mutable std::vector<std::vector<unsigned int> > matrix_;

public:
    explicit levenshtein_distance(size_t initial_size = 8)
        : matrix_(initial_size, std::vector<unsigned int>(initial_size))
    {
    }

    unsigned int operator()(const std::string& s, const std::string& t) const
    {
        const size_t m = s.size();
        const size_t n = t.size();
        // The distance between a string and the empty string is the string's length
        if (m == 0) {
            return n;
        }
        if (n == 0) {
            return m;
        }
        // Size the matrix as necessary
        if (matrix_.size() < m + 1) {
            matrix_.resize(m + 1, matrix_[0]);
        }
        if (matrix_[0].size() < n + 1) {
            for (auto& mat : matrix_) {
                mat.resize(n + 1);
            }
        }
        // The top row and left column are prefixes that can be reached by
        // insertions and deletions alone
        unsigned int i, j;
        for (i = 1;  i <= m; ++i) {
            matrix_[i][0] = i;
        }
        for (j = 1; j <= n; ++j) {
            matrix_[0][j] = j;
        }
        // Fill in the rest of the matrix
        for (j = 1; j <= n; ++j) {
            for (i = 1; i <= m; ++i) {
                unsigned int substitution_cost = s[i - 1] == t[j - 1] ? 0 : 1;
                matrix_[i][j] =
                    min3(matrix_[i - 1][j] + 1,                 // Deletion
                    matrix_[i][j - 1] + 1,                      // Insertion
                    matrix_[i - 1][j - 1] + substitution_cost); // Substitution
            }
        }
        return matrix_[m][n];
    }
};

1

Functorは、関数内のローカル関数の定義をシミュレートするためにも使用できます。質問別のを参照してください 。

ただし、ローカルファンクタは外部の自動変数にアクセスできません。ラムダ(C ++ 11)関数の方が優れたソリューションです。


-10

ファンクタの非常に興味深い使用法を「発見」しました。ファンクタは名前のないメソッドなので、1つのメソッドに適切な名前がない場合に使用します;-)


ファンクタを「名前のないメソッド」と表現するのはなぜですか。
アンダーソングリーン

5
名前のない関数はラムダと呼ばれます。
Paul Fultz II、2013
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.