これは、特定のユースケースとユーザーがコードを追加する方法によって異なります。以下は純粋に理論的な分析ですが、実際に決定する前に、現実的なシナリオを作成し、パフォーマンスをテストする必要があります。
コンパイル時にコードを追加する場合(つまり、ユーザーにコードを提供し、ユーザーがロジックを作成してすべてを一緒にコンパイルする場合)、おそらく最善のアプローチは、Functor
型引数を取るテンプレートを提供することです。
template <typename Functor> void doProcessing( Functor f) {
f( data );
}
利点は、コンパイラがコード全体にアクセスできることです。つまり、コンパイラがコードをインライン化するかどうかについて最善の決定を下すことができ、インライン化の場合はさらに最適化できる可能性があります。欠点は、新しいロジックごとにプログラムを再コンパイルする必要があることと、製品をユーザー拡張機能と一緒にコンパイルする必要があることです。多くの場合、それは不可能です。
メイン製品のコンパイル後に拡張機能を実行する場合、つまりクライアントが独自の拡張機能を作成し、コンパイルされた実行可能ファイル(プラグインを考えてください)に使用できる場合は、代替案を検討する必要があります。
これらは純粋なパフォーマンス順に並べられています。Cウェイのコストは、3つのうち小さい方ですが、ほとんどの場合、インターフェイスオプションとの違いはごくわずかであり(関数呼び出しごとに余分な間接参照)、呼び出し元のオブジェクトに状態を保持するオプションが購入されます(これにより、最初のオプションでグローバル状態を介して実行されます)。
3番目のオプションは、呼び出し可能なユーザーに型消去を適用するため、最も一般的であり、コンパイラーがサポートしている場合はラムダなどの関数アダプターを使用して、ユーザーが既存のコードを再利用できるようにします。これは最も一般的であり、ユーザーにほとんどの選択肢を残します。ただし、それに関連するコストがあります。型消去には仮想ディスパッチに加えて、実際のコードへの追加の関数呼び出しがあります。boost::bind
std::bind
ユーザーがインターフェースをコードに適合させるための関数を作成する必要がある場合、手動で作成されたアダプターのコストはおそらく同等であるため、汎用性が必要な場合はboost::function
/std::function
が最適です。
As of the difference in costs of the alternatives, they are most probably very small overall, and whether scratching one or two operations matters will depend on how tight the loop is and how expensive the user code is. If the user code is going to take a couple hundred instructions, there is probably no point in not using the most generic solution. If the loop is run a few thousand times per second, scratching a few instructions will not make a difference either. Go back, write a realistic scenario and test, and while testing be aware of what really matters to the user. Scratching a few seconds of an operation that takes minutes is not worth loosing the flexibility of the higher level solutions.