14

これが機能する主な理由は、 for_each () が実際には 3 番目の引数が関数であると想定していないためです。3 番目の引数は、適切な引数で呼び出すことができるものであると単純に想定しています。適切に定義されたオブジェクトは、機能と同様に機能し、多くの場合、機能よりも優れています。たとえば、関数へのポインターとして渡される関数をインライン化するよりも、クラスのアプリケーション演算子をインライン化する方が簡単です。その結果、関数オブジェクトは通常の関数よりも高速に実行されることがよくあります。アプリケーション演算子 (§11.9) を持つクラスのオブジェクトは、関数型オブジェクト、ファンクター、または単に関数オブジェクトと呼ばれます。

[Stroustrup、C++ 第 3 版、18.4-最後の段落]

  1. operator()呼び出しは、実行時の関数呼び出しと同じだとずっと思っていました。通常の関数呼び出しとどう違うのですか?

  2. 通常の関数よりもアプリケーション演算子をインライン化する方が簡単なのはなぜですか?

  3. 関数呼び出しよりもどのように高速ですか?

4

3 に答える 3

11

通常、ファンクターはテンプレート化された関数に渡されます。そうする場合、「実際の」関数 (つまり、関数ポインター) を渡すか、ファンクター (つまり、オーバーロードされたクラス) を渡すかは問題ではありませんoperator()。基本的に、両方とも関数呼び出し演算子を持っているため、コンパイラがテンプレートをインスタンス化できる有効なテンプレート パラメーターですfor_eachつまり、渡されたファンクターの特定の、または渡された関数ポインターの特定のfor_eachでインスタンス化されます。 そして、ファンクターが関数ポインターよりも優れたパフォーマンスを発揮できるのは、その特殊化においてです。

結局のところ、関数ポインタを渡す場合、引数のコンパイル型の型はまさにそれ、つまり関数ポインタです。for_eachそれ自体がインライン化されていない場合、この特定for_eachのインスタンスはコンパイルされて不透明な関数ポインターを呼び出します。結局のところ、コンパイラーはどのようにして関数ポインターをインライン化できるのでしょうか? その型のどの関数が実際に渡されるかではなく、そのを知っているだけです-少なくとも、最適化時に非ローカル情報を使用できない限り、これは実行が困難です。

ただし、 functor を渡すと、その functorのコンパイル時の型を使用してfor_eachテンプレートがインスタンス化されます。そうすることで、おそらく、適切なoperator(). そのため、コンパイラが呼び出しに遭遇すると、operator()どの実装が意図されているか (そのファンクタの一意の実装) を正確に認識し、それをインライン化できるようになります。

ファンクターが仮想メソッドを使用すると、潜在的な利点がなくなります。そしてもちろん、ファンクターは、他のあらゆる種類の非効率的なことを実行できるクラスです。しかし、基本的なケースでは、これが、関数ポインター呼び出しよりもファンクター呼び出しを最適化およびインライン化する方がコンパイラーにとって簡単な理由です。

概要

関数ポインターはインライン化できません。これは、コンパイル中for_eachにコンパイラーが関数の型のみを持ち、関数の ID を持たないためです。対照的に、ファンクターはインライン化できます。これは、コンパイラーがファンクターの型しか持っていない場合でも、その型は通常、ファンクターのoperator()メソッドを一意に識別するのに十分であるためです。

于 2010-10-14T23:26:25.393 に答える
5

次の 2 つのテンプレートのインスタンス化を検討してください。

std::for_each<class std::vector<int>::const_iterator, class Functor>(...)

std::for_each<class std::vector<int>::const_iterator, void(*)(int)>(...)

1 番目は関数オブジェクトの型ごとにカスタマイズされ、operator()インラインで定義されることが多いため、コンパイラは独自の判断で呼び出しをインライン化することを選択できます。

2 番目のシナリオでは、コンパイラは同じシグネチャのすべての関数に対してテンプレートを 1 回インスタンス化するため、呼び出しを簡単にインライン化することはできません。

現在、スマート コンパイラは、特に次のようなシナリオで、コンパイル時にどの関数を呼び出すかを判断できる場合があります。

std::for_each(v.begin(), v.end(), &foo);

前述の単一の汎用インスタンスではなく、カスタム インスタンスを生成することで関数をインライン化します。

于 2010-10-14T23:26:40.080 に答える
1

operator() 呼び出しは、実行時の関数呼び出しと同じだとずっと思っていました。通常の関数呼び出しとどう違うのですか?

私の推測ではあまりありません。これの証拠として、それぞれのコンパイラのアセンブリ出力を見てください。同じレベルの最適化を想定すると、ほぼ同じになる可能性があります。this(ポインターが渡される必要があるという追加の詳細があります。)

通常の関数よりもアプリケーション演算子をインライン化する方が簡単なのはなぜですか?

あなたが引用した宣伝文句を引用するには:

たとえば、関数へのポインターとして渡される関数をインライン化するよりも、クラスのアプリケーション演算子をインライン化する方が簡単です。

私はコンパイラの人ではありませんが、これを次のように読みます: 関数が関数ポインタを介して呼び出されている場合、その関数ポインタに格納されているアドレスが実行時に変更されるかどうかをコンパイラが推測するのは難しい問題です。call命令を関数の本体に置き換えても安全です。考えてみると、関数自体の本体はコンパイル時に必ずしもわかっているとは限りません。

関数呼び出しよりもどのように高速ですか?

多くの場合、違いに気付かないと思います。ただし、コンパイラは自由にインライン化を行うことができるというあなたの引用の議論を考えると、これにより、コードの局所性が向上し、ブランチが少なくなる可能性があります。コードが頻繁に呼び出される場合、これは顕著なスピードアップをもたらします。

于 2010-10-14T23:55:14.237 に答える