Boost Signalsライブラリでは、() 演算子をオーバーロードしています。
これは C++ の規則ですか? コールバックなどに?
私はこれを同僚のコードで見たことがあります(たまたまBoostの大ファンです)。そこにあるすべてのブーストの良さの中で、これは私を混乱させるだけでした.
この過負荷の理由についての洞察はありますか?
Boost Signalsライブラリでは、() 演算子をオーバーロードしています。
これは C++ の規則ですか? コールバックなどに?
私はこれを同僚のコードで見たことがあります(たまたまBoostの大ファンです)。そこにあるすべてのブーストの良さの中で、これは私を混乱させるだけでした.
この過負荷の理由についての洞察はありますか?
operator() をオーバーロードするときの主な目的の 1 つは、ファンクターを作成することです。ファンクターは関数のように機能しますが、ステートフルであるという利点があります。つまり、呼び出し間で状態を反映したデータを保持できます。
簡単なファンクターの例を次に示します。
struct Accumulator
{
int counter = 0;
int operator()(int i) { return counter += i; }
}
...
Accumulator acc;
cout << acc(10) << endl; //prints "10"
cout << acc(20) << endl; //prints "30"
ファンクターは、ジェネリック プログラミングで頻繁に使用されます。多くの STL アルゴリズムは非常に一般的な方法で記述されているため、独自の関数/ファンクターをアルゴリズムにプラグインできます。たとえば、アルゴリズム std::for_each を使用すると、範囲の各要素に操作を適用できます。次のように実装できます。
template <typename InputIterator, typename Functor>
void for_each(InputIterator first, InputIterator last, Functor f)
{
while (first != last) f(*first++);
}
このアルゴリズムは関数によってパラメータ化されているため、非常に汎用的であることがわかります。operator() を使用することにより、この関数ではファンクターまたは関数ポインターのいずれかを使用できます。両方の可能性を示す例を次に示します。
void print(int i) { std::cout << i << std::endl; }
...
std::vector<int> vec;
// Fill vec
// Using a functor
Accumulator acc;
std::for_each(vec.begin(), vec.end(), acc);
// acc.counter contains the sum of all elements of the vector
// Using a function pointer
std::for_each(vec.begin(), vec.end(), print); // prints all elements
operator() のオーバーロードに関するご質問については、可能です。メソッドのオーバーロードの基本的な規則を尊重する限り、複数の括弧演算子を持つファンクターを完全に作成できます (たとえば、戻り値の型だけでオーバーロードすることはできません)。
クラスが関数のように動作できるようにします。呼び出しが関数である必要があるロギング クラスで使用しましたが、クラスの追加の利点が必要でした。
このようなもの:
logger.log("Log this message");
これに変わります:
logger("Log this message");
ファンクターが単純な古い関数よりも優れている大きな理由を 1 つも説明せずに、多くの人がファンクターになると答えています。
答えは、ファンクターが状態を持つことができるということです。合計関数を考えてみましょう - 現在の合計を維持する必要があります。
class Sum
{
public:
Sum() : m_total(0)
{
}
void operator()(int value)
{
m_total += value;
}
int m_total;
};
C++ faq の Matrix exampleも参照してください。それを行うための良い用途がありますが、それはもちろんあなたが達成しようとしていることに依存します.
ファンクターは関数ではないため、オーバーロードできません。
あなたの同僚は正しいですが、 operator() のオーバーロードを使用して「ファンクター」(関数のように呼び出すことができるオブジェクト)を作成します。「関数のような」引数を期待するテンプレートと組み合わせると、オブジェクトと関数の区別が曖昧になるため、これは非常に強力です。
他のポスターが言ったように: ファンクターは、状態を持つことができるという点で、単純な関数よりも利点があります。この状態は、単一の反復 (コンテナー内のすべての要素の合計を計算する場合など) または複数の反復 (特定の基準を満たす複数のコンテナー内のすべての要素を見つける場合など) で使用できます。
std::for_each
コードで、 などをより頻繁に使用し始めるstd::find_if
と、() 演算子をオーバーロードできると便利な理由がわかります。また、派生クラスの他のメソッドの名前と競合しない明確な呼び出しメソッドをファンクターとタスクに持たせることもできます。
C++ でファンクターを形成するために operator() を使用することは、通常は同様の概念であるクロージャーを使用する関数型プログラミングパラダイムに関連しています。
ファンクターは基本的に関数ポインターに似ています。これらは通常、(関数ポインターのように) コピー可能であり、関数ポインターと同じ方法で呼び出されることを意図しています。主な利点は、テンプレート化されたファンクターで動作するアルゴリズムがある場合、operator() への関数呼び出しをインライン化できることです。ただし、関数ポインターは引き続き有効なファンクターです。
私が見ることができる 1 つの強みは、これについて議論することはできますが、operator() のシグネチャが異なる型間で同じように見え、同じように動作することです。メンバ メソッド report(..) を持つクラス Reporter と、メンバ メソッド write(..) を持つ別のクラス Writer がある場合、おそらく両方のクラスを使用したい場合は、アダプタを作成する必要があります。他のシステムのテンプレート コンポーネント。それが気にするのは、文字列またはあなたが持っているものを渡すことだけです。operator() のオーバーロードを使用したり、特殊な型アダプターを作成したりしないと、次のようなことはできませんでした。
T t;
t.write("Hello world");
T には、暗黙的に const char* (または const char[]) にキャスト可能なものすべてを受け入れる write というメンバー関数が存在する必要があるためです。この例の Reporter クラスにはそれがないため、T (テンプレート パラメーター) を Reporter にするとコンパイルに失敗します。
ただし、私が見る限り、これはさまざまなタイプで機能します
T t;
t("Hello world");
ただし、型 T にそのような演算子が定義されていることが明示的に要求されているため、T にはまだ要件があります。個人的には、ファンクターは一般的に使用されているため、それほど奇妙ではないと思いますが、他のメカニズムを見てみたいと思います。この動作。C# のような言語では、デリゲートを渡すだけで済みます。私は C++ のメンバー関数ポインターにあまり詳しくありませんが、同じ動作を実現できると想像できます。
シンタティック シュガーの動作以外には、そのようなタスクを実行するための演算子のオーバーロードの長所はあまりわかりません。
私よりも良い理由を知っている人はもっとたくさんいると思いますが、残りの人が共有できるように私の意見を述べたいと思いました.
別の同僚は、それが機能として機能を偽装する方法である可能性があると指摘しました。たとえば、次のようになります。
my_functor();
本当に:
my_functor.operator()();
つまり、これは次のことを意味します。
my_functor(int n, float f){ ... };
これもオーバーロードするために使用できますか?
my_functor.operator()(int n, float f){ ... };
他の投稿では、operator() がどのように機能し、なぜ便利なのかを説明しています。
私は最近、operator() を非常に広範囲に使用するコードを使用しています。この演算子をオーバーロードすることの欠点は、結果として一部の IDE が効果の低いツールになることです。Visual Studio では、通常、メソッド呼び出しを右クリックして、メソッドの定義や宣言に移動できます。残念ながら、VS は operator() 呼び出しをインデックス化するほどスマートではありません。特に、オーバーライドされた operator() 定義が随所にある複雑なコードでは、どのコードがどこで実行されているかを把握するのが非常に難しい場合があります。いくつかのケースでは、実際に何が実行されているかを見つけるために、コードを実行してトレースする必要があることがわかりました。