7

私は最近、ファンクターに興奮していて、あちこちでそれらを使用しています。その後、ファンクターに 2 つの異なる操作を実行させる必要がある状況が発生し、ファンクターに別のメソッドを追加する (() 演算子をオーバーロードしない) ことを考えました。これが悪い習慣なのかどうかはわかりませんが (おそらく教えていただけますか)、オブジェクトだけでなく、そもそもなぜファンクターを使用しているのかを考えさせられました。だから私の質問は:

() 演算子のオーバーロードについて特別なことはありますか、それとも通常の名前付きメソッドを使用するよりも構文的に魅力的ですか?

アップデート:

まず、他の質問で説明されているように、ファンクターが関数ポインターよりも望ましい理由を知っています。名前付きメソッドを持つオブジェクトよりもそれらが望ましい理由を知りたいです。

第二に、ファンクターの別の名前のメソッドを使用したい場合の例について: 基本的に、グラフパーティションのモジュール性と呼ばれるものを計算する関数と、compute_modularity()変更後のモジュール性のゲインを計算する関数の 2 つがありますパーティションのcompute_modularity_gain()。これらの関数を同じファンクターの一部として最適化アルゴリズムに渡し、名前付き関数としてゲインを使用できると考えました。アルゴリズムに 2 つのファンクターを渡すだけではない理由は、別のファンクターとの組み合わせでのみ使用され、別のファンクターでは使用されないことを強制したいからですcompute_modularity_gain()compute_modularity()compute_stability()compute_stability_gain(). つまり、ゲイン関数はその兄弟関数と密接に結合されている必要があります。この制約を強制する別の方法がある場合は、お知らせください。

4

3 に答える 3

7

のオーバーロードの理由は、operator()ファンクターに関数ポインターと同じ呼び出しセマンティクスを持たせるためです。実際、必要に応じて関数ポインターを使用できます。

関数を使用する代わりにオーバーロードする理由はいくつかありますoperator()が、最も重要な理由は、関数ポインターを使用する場合、コンパイラーが間接的な関数呼び出しを最適化することはめったになく、ほとんどの場合呼び出しを最適化することoperator()です。なぜstd::sort通常ビートstd::qsort

これには複雑な理由がたくさんありますが、実際に要約すると、ほとんどの (いいえ?) コンパイラーが、最新のハードウェアでは高価な関数ポインター呼び出しを削除できる最適化を実装しているということです。

その後、ファンクタが 2 つの異なる操作を実行する必要がある状況が発生しました

それはもはや関手ではありません。2 つのファンクターを渡して目的を実行するか、テンプレート メソッドクラスを定義します。(ミックスインを使用して、実行時のオーバーヘッドなしで C++ でテンプレート メソッド パターンを実現することもできますが、ウィキペディアの記事ではこれについて説明していません) (注: C++ テンプレートと同じではありませんが、C++ テンプレートは、 AOPルート)

于 2011-04-05T01:26:57.913 に答える
1

ファンクターの背後にある基本的な意図は、ある種の作業を実行する方法を知っているコードを、その作業をいつ実行する必要があるかを知っているコードから分離することです (古典的な例は、ファンクターを UI ボタン​​に関連付けることです)。

ファンクター モデルの小さな利点の 1 つは、単純な古い関数ポインターが既にファンクターであることです。それらをラップするために余分な作業は必要ありません。a) 関数ポインタは、関数への直接呼び出しをラップするよりもわずかに効率が悪いため、これは小さな利点だと考えています。b) ほとんどの場合、ラップしているものになんらかの状態をバインドする必要があることがわかりました。それは単なるthisメンバー関数のポインターです。

単項インターフェースの主な利点は、ファンクターのプロデューサーとコンシューマーの共通語として機能することです。たとえば、すべてのファンクターがinvoke()メンバー関数を持つように定義することもできますが、他の群衆は で標準化することを決定しdo()、さらに別の群衆は を採用する可能性がありcall()ます。そして、これらのソリューションはすべて、より多くのタイピングを必要とします。

また、単一の「ファンクター」に複数のメンバー関数が厳密に必要になることはありません。一部のコードで複数の異なる操作を呼び出す必要がある場合は、単純に複数のファンクターを渡すことができます。これにより、操作が結合されたり、完全に無関係になったりする可能性があるため、優れた柔軟性が得られます。

分離された例は、等価比較器とハッシュ関数を必要とするハッシュ テーブルです。この場合、2 つの関数は無関係である可能性があります。クラスのoperator==()for equality をラップし、free 関数をラップしてハッシュを計算します。

結合された例は、いくつかの異なるイベントを発行する UI コンポーネントです。1 つのクラスがすべてのイベントに応答する場合もあれば、異なるクラスが異なるグループのイベントに応答する場合もあります。ファンクターを使用すると、どちらのモデルも簡単に選択できますが、すべてのコンポーネントのイベントのコールバックを定義する単一の「インターフェイス」を必要とするのは扱いにくいです。ファンクターを使用すると、1 つのオブジェクトが 2 つのコンポーネントからのイベントを異なる方法で処理したい場合にも、はるかに簡単になります。これは、ファンクターでラップされたメンバー関数の異なるセットを各コンポーネントに与えることができるためです。

最後に、既存の機能をファンクターにラップすることはよく理解されており、boost.bind などのライブラリによって広くサポートされていますが、実装するクラスと実装doX()doY()ないクラスを作成することは異なります。さらに、新しい標準ではラムダが追加され、ファンクターの作成が大幅に簡素化されます。

于 2011-04-05T01:18:16.760 に答える
0

ファンクターの唯一の特徴は、関数と同じように使用できることです。ただし、ファンクターには、コンストラクターを介して情報が注入される場合もあります。

std::function (または、コンパイラがまだサポートしていない場合は boost::function) を調べることもできます。これを使用して、呼び出しシグネチャが一致する任意のタイプのオブジェクトを適応させることができます。

std::bind または boost::bind を使用すると、具体的な引数を関数のパラメーターに関連付けることができます。これにより、ファンクターのコンストラクターを介してそれらを渡すのと同じ効果が得られます。bind を使用して this ポインターをメンバー関数に提供することもできるため、オブジェクトを明示的に指定せずに、単純なファンクターと同じ方法で呼び出すことができます。

于 2011-04-05T00:47:36.763 に答える