最近、C++では、「関数呼び出し」演算子をオーバーロードできることを発見しました。これを行うには、2組の括弧を記述する必要があるという奇妙な方法があります。
class A {
int n;
public:
void operator ()() const;
};
そして、次のように使用します。
A a;
a();
これはいつ役に立ちますか?
これを使用して、関数のように機能するオブジェクトを作成できます "functors" :
class Multiplier {
public:
Multiplier(int m): multiplier(m) {}
int operator()(int x) { return multiplier * x; }
private:
int multiplier;
};
Multiplier m(5);
cout << m(4) << endl;
上記は20
を出力します。上にリンクされたウィキペディアの記事は、より実質的な例を示しています。
テンプレートの使用を開始するまでは、operator()を使用することで構文上の利点が得られるだけです。ただし、テンプレートを使用する場合は、実際の関数とファンクター(関数として機能するクラス)を同じように扱うことができます。
class scaled_sine
{
explicit scaled_sine( float _m ) : m(_m) {}
float operator()(float x) const { return sin(m*x); }
float m;
};
template<typename T>
float evaluate_at( float x, const T& fn )
{
return fn(x);
}
evaluate_at( 1.0, cos );
evaluate_at( 1.0, scaled_sine(3.0) );
テンプレートを使用して実装されたアルゴリズムは、呼び出されるものが関数であるかファンクターであるかを気にせず、構文を気にします。標準のもの(例:for_each())または独自のもの。そして、ファンクターは状態を持つことができ、呼び出されたときにあらゆる種類のことを行うことができます。関数は、静的ローカル変数またはグローバル変数を持つ状態のみを持つことができます。
関数ポインタをカプセル化するクラスを作成している場合、これにより使用法がより明確になる可能性があります。
コンパイラーは、関数と関数呼び出しをインライン化することもできます。ただし、関数ポインタをインライン化することはできません。このように、関数呼び出し演算子を使用すると、たとえば標準のC++ライブラリアルゴリズムで使用する場合にパフォーマンスを大幅に向上させることができます。
たとえば、ジェネレータを実装する場合:
// generator
struct Generator {
int c = 0;
virtual int operator()() {
return c++;
}
};
int sum(int n) {
Generator g;
int res = 0;
for( int i = 0; i < n; i++ ) {
res += g();
}
return res;
}
私はまだ1つのエキゾチックな使用の可能性を見ています:
不明なタイプのオブジェクトがあり、次のように同じタイプの別の変数を宣言する必要があるとします。
_ auto c=decltype(a*b)(123);
_
このようなパターンが広く使用されると、decltypeは非常に煩わしくなります。このケースは、引数のタイプに基づいて関数と演算子の結果のタイプを自動的に発明するスマート型システムを使用している場合に発生する可能性があります。
さて、その型システムの各型の各特殊化が次のようにoperator()
の魔法の定義を備えている場合:
_template<????> class Num<???>{
//specific implementation here
constexpr auto operator()(auto...p){return Num(p...);}
}
_
decltype()
もう必要ありません。簡単に書くことができます。
_auto c=(a*b)(123);
_
オブジェクトのoperator()は、それ自体の型のコンストラクターにリダイレクトするためです。