C++ では、実行時 (サブクラス、仮想関数) またはコンパイル時 (テンプレート、関数のオーバーロード) ポリモーフィズムのいずれかを使用して同じ機能を実装できる場合、なぜ一方を選択するのでしょうか?
コンパイルされたコードは、コンパイル時のポリモーフィズム(テンプレート型用に作成されたより多くのメソッド/クラス定義)のために大きくなると思います...
多くの場合、はい - テンプレート パラメーターのさまざまな組み合わせに対して複数のインスタンス化が行われるため、次のことを考慮してください。
- テンプレートでは、実際に呼び出された関数のみがインスタンス化されます
- デッドコードの排除
- 一定の配列次元により、メンバー変数
T mydata[12];
をオブジェクトに割り当てたり、ローカル変数の自動ストレージなどを許可したりできますが、ランタイム ポリモーフィック実装では動的割り当て (つまりnew[]
) を使用する必要がある場合があります。これは、場合によってはキャッシュ効率に劇的な影響を与える可能性があります。
- 関数呼び出しのインライン化。これにより、小さなオブジェクトの get/set 操作などの些細なことを、私がベンチマークした実装で約 1 桁高速化します
- 仮想ディスパッチを回避します。これは、関数ポインターのテーブルへのポインターをたどり、それらの 1 つに対して行外呼び出しを行うことになります (通常、パフォーマンスを最も損なうのは行外の側面です)。
...そして、そのコンパイル時間により、柔軟性が向上します...
テンプレートは確かに行います:
異なる型に対してインスタンス化された同じテンプレートが与えられた場合、同じコードが異なることを意味するT::f(1)
可能性がありvoid f(int) noexcept
ます。別の観点から見ると、さまざまなパラメーターの型が、テンプレート化されたコードが必要とするものを、最も適した方法で提供できます。virtual void f(double)
T::f
operator()(float)
SFINAEを使用すると、オブジェクトが積極的に推奨を行うことなく、オブジェクトがサポートする最も効率的なインターフェイスを使用するようにコードをコンパイル時に調整できます。
上記のインスタンス化のみの関数呼び出しの側面により、クラス テンプレートの一部の関数のみがコンパイルされる型でクラス テンプレートをインスタンス化することで「逃げる」ことができます。それらの機能は、他のタイプでサポートされTemplate<MyType>
ているすべての操作をサポートしているように見えTemplate<>
ますが、特定の操作を試みたときに失敗するだけです。Template<>
他の方法では、すべての操作に興味がない場合でも使用できるため、それは良いことです
- Concepts [Lite] が将来の C++ 標準になる場合、プログラマーは、テンプレート パラメーターとして使用される型がサポートしなければならないセマンティック操作に対して、より強力な先行制約を設定するオプションを利用できるようになります
Template<MyType>::operationX
。一般に、コンパイルの早い段階でより単純なエラーメッセージを表示します
...一方、ランタイムは「より安全な」ポリモーフィズムを提供します (つまり、誤って誤って使用されるのが難しくなります)。
おそらく、上記のテンプレートの柔軟性を考えると、より厳格であるためです。ランタイム ポリモーフィズムの主な「安全性」の問題は次のとおりです。
いくつかの問題は、最終的に「太った」インターフェースを奨励することになります (Stroustrup が C++ プログラミング言語で言及している意味で): 一部の派生型に対してのみ機能する関数を含む API、およびアルゴリズム コードは派生型に「問い合わせ」続ける必要があります。あなたのためにこれをしてください」、「これを行うことができますか」、「それはうまくいきましたか」など.
仮想デストラクタが必要です: 一部のクラスにはそれらがありません (例std::vector
) - それらから安全に派生することが難しくなり、仮想ディスパッチ テーブルへのオブジェクト内ポインタがプロセス間で有効でないため、ランタイム ポリモーフィック オブジェクトを配置することが難しくなります。複数のプロセスからアクセスするための共有メモリ内
両方が実行可能なオプションであるが、どちらか一方が明らかにより良い選択になる特定の例を誰か挙げることができますか?
もちろん。たとえば、クイック ソート関数を作成しているとします。仮想比較関数と仮想スワップ関数を使用して、Sortable 基本クラスから派生したデータ型のみをサポートするか、Less
デフォルトでstd::less<T>
,とstd::swap<>
。並べ替えのパフォーマンスがこれらの比較操作とスワップ操作のパフォーマンスによって圧倒的に支配されることを考えると、テンプレートはこれに非常に適しています。そのため、C++std::sort
は C ライブラリのジェネリックqsort
関数よりも明らかにパフォーマンスが優れています。ジェネリック関数は、事実上仮想ディスパッチの C 実装である関数ポインターを使用します。詳細については、こちらを参照してください。
また、コンパイル時のポリモーフィズムは、vtable を介して関数を呼び出す必要がないため、より高速なコードを生成しますか、それともコンパイラによって最適化されますか?
多くの場合は高速ですが、非常にまれに、テンプレート コードの肥大化の合計の影響が、コンパイル時のポリモーフィズムが通常は高速である無数の方法を圧倒する可能性があり、バランスをとってみると悪化します。