この質問があいまいではないことを願っていますが、Java から来ているので、C++ で非仮想関数を使用する理由が思いつきません。C++ の非仮想関数の利点を示す良い例はありますか。
5 に答える
仮想関数には、関連するランタイム コストがあります。これらは実行時にディスパッチされるため、呼び出しが遅くなります。それらは、オブジェクトの実際のタイプに従って実行時にアドレスが決定される、関数ポインターを介して通常の関数を呼び出すことに似ています。これにはオーバーヘッドが発生します。
C++ の設計上の決定事項の 1 つは、必要のないものにお金を払うべきではないということです。対照的に、Java 自体は、この種の低レベルの最適化にはあまり関心がありません。
C++ 言語が基づいている原則の 1 つは、使用しないものにお金を払うべきではないということです。
仮想関数呼び出しは、非仮想関数呼び出しよりもコストがかかります。これは、典型的な実装では、2 つ (または 3 つ) の追加レベルの間接化が行われるためです。仮想呼び出しはインライン化できません。つまり、本格的な関数を呼び出さなければならないという事実により、費用がさらに高くなる可能性があります。
クラスに仮想関数を追加すると、クラスはポリモーフィックになり、そのクラスのオブジェクト内に目に見えない内部構造がいくつか作成されます。これらの構造は追加の家計費を負担し、クラス オブジェクトの低レベルの処理を妨げます。
最後に、関数を仮想関数と非仮想関数 (つまり、オーバーライド可能な関数とオーバーライドできない関数) に分離することは、設計の問題です。クラス内のすべての関数を無条件に派生クラスでオーバーライド可能にすることはまったく意味がありません。
仮想関数の呼び出しが遅くなる可能性があることは事実ですが、ほとんどの C++ プログラマーが考えるほど遅くはありません。
最新の CPU は、分岐予測がかなり得意になっています。仮想関数への特定の呼び出しを実行するたびに、実際には同じ実装を呼び出している場合、CPU はそれを把握し、アドレスを計算する前に呼び出しを "推測" (投機的に実行) し始めます。これにより、多くの場合、仮想通話のコストを完全に隠すことができ、非仮想通話とまったく同じ速度になります。(これに疑問がある場合は、現世代のプロセッサで試してみてください。)
同じ実装を呼び出していない場合、実際には仮想ディスパッチに依存しているため、非仮想関数で直接置き換えることはできません。
これに対する唯一の一般的な例外は、コンパイラが呼び出し元と呼び出し先の間で定数の伝播、CSE などを実行できるインライン関数です。コンパイル時に呼び出しの宛先がわからない場合、明らかにこれを行うことはできません。
しかし経験則として、常に仮想関数を使用したいというあなたの本能はそれほど悪いものではありません。パフォーマンスの違いが目立つ場合はまれです。
標準ライブラリのごく少数のメンバー関数が仮想です。
what
ちなみに、標準例外のデストラクタと関数しか覚えていません。
2012年現在、仮想メンバー関数を持つ唯一の正当な理由は、派生クラス、つまりカスタマイズポイントでそのメンバー関数のオーバーライドをサポートすることであり、多くの場合、他の方法 (パラメーター化、テンプレート化など) で実現できます。
しかし、15 年前のように、Microsoft の MFC クラス フレームワークの設計に非常に不満を感じていたことを覚えています。存在しない、または非常に低品質のドキュメントの代わりに、すべてのメンバー関数を仮想にして、機能をオーバーライドできるようにし、物事をより簡単にデバッグできるようにしたかったのです。したがって、他のソフトウェアでも virtual がデフォルトであるべきだと主張しました。
それ以来、MFC は代表的なものではなく、C++ ソフトウェア全般を代表するものでもないことを理解したので、MFC 固有の理由は一般には当てはまりません。:-)
仮想機能の効率コストは、実質的に存在しません。:-) たとえば、国際標準化委員会のTechnical Report on C++ Performanceを参照してください。ただし、自由には責任が伴うため、派生クラスにこの自由を提供するには実際のコストがかかります。派生クラスは、メンバー関数をオーバーライドする際に基本クラスのコントラクトを尊重する必要があります。
C++ は、C のように高速であり、OO とジェネリック プログラミング (テンプレート) をサポートすることを目的としています。この両方の目標を達成するために、C++ メンバー関数は、仮想としてマークしない限り、既定では初期化できません。この場合、仮想テーブルが機能します。そのため、必要がない場合は、仮想関数を含まないクラスを構築できます。
非仮想呼び出しほど効率的ではありませんが、仮想テーブルを使用した仮想関数呼び出しは非常に高速です。メンバー関数を呼び出すだけのタイトなループ内でのみ違いに気付くかもしれません。したがって、Java の方法 (すべてのメンバーが「仮想」) は、実際にはより実用的な IMO です。