24

C++ でメンバ関数を仮想化しない本当の理由はありますか? もちろん、パフォーマンスに関する議論は常にありますが、仮想関数のオーバーヘッドがかなり低いため、ほとんどの状況ではそれが当てはまらないようです。

一方で、仮想であるべき関数を仮想にするのを忘れて、何度か噛まれたことがあります。そして、それはパフォーマンスよりも大きな議論のようです. メンバー関数をデフォルトで仮想化しない理由はありますか?

4

7 に答える 7

27

あなたの質問を読む 1 つの方法は、「プログラマーがそのデフォルトをオーバーライドしない限り、なぜ C++ はすべての関数をデフォルトで仮想化しないのか」です。「C++ の設計と進化」の私のコピーを参考にしないと、すべてのメンバー関数が非仮想にされない限り、すべてのクラスに余分なストレージが追加されます。私には、これはコンパイラの実装でより多くの努力を必要とし、執着したパフォーマンスに飼料を提供することで C++ の採用を遅らせたと思われます (私はそのグループに数えられます)。

あなたの質問を読む別の方法は、「C++ プログラマーは、そうしない十分な理由がない限り、すべての関数を仮想化しないのはなぜですか?」です。パフォーマンスの言い訳がおそらく理由です。アプリケーションとドメインによっては、これが正当な理由である場合とそうでない場合があります。たとえば、私のチームの一部は市場データ ティッカー プラントで働いています。1 つのストリームで 1 秒あたり 100,000 件以上のメッセージが処理されると、仮想機能のオーバーヘッドは受け入れられなくなります。私のチームの他の部分は、複雑な取引インフラで働いています。ほとんどの関数を仮想化することは、そのコンテキストではおそらく良い考えです。追加の柔軟性がマイクロ最適化に勝るからです。

于 2008-11-15T04:26:52.513 に答える
27

この言語の設計者である Stroustrup 氏は、次のように述べています。

多くのクラスは、基本クラスとして使用するように設計されていないためです。たとえば、class complexを参照してください。

また、仮想関数を持つクラスのオブジェクトには、仮想関数呼び出しメカニズムに必要なスペースが必要です (通常、オブジェクトごとに 1 ワード)。このオーバーヘッドは非常に大きく、他の言語 (C や Fortran など) からのデータとのレイアウトの互換性を妨げる可能性があります。

設計の根拠については、C++ の設計と進化を参照してください。

于 2008-11-15T04:29:56.643 に答える
11

いくつかの理由があります。

まず、パフォーマンス: はい、仮想機能のオーバーヘッドは、単独で見ると比較的低いです。しかし、これはコンパイラがインライン化することも防ぎます。これは、C++ での最適化の大きな原因です。C++ 標準ライブラリは、それを構成する何十もの小さなワンライナーをインライン化できるため、それと同じように機能します。さらに、仮想メソッドを持つクラスは POD データ型ではないため、多くの制限が適用されます。memcpy'ingだけではコピーできず、構築にコストがかかり、より多くのスペースを占有します。非PODタイプが関与すると、突然違法になったり、効率が低下したりすることがたくさんあります。

2 つ目は、適切な OOP の実践です。クラスのポイントは、ある種の抽象化を行い、内部の詳細を隠し、「このクラスはそのように動作し、常にこれらの不変条件を維持し、無効な状態になることは決してない」という保証を提供することです。 . 他のメンバーが任意のメンバー関数をオーバーライドできるようにする場合、それを実現するのはかなり困難です。クラスで定義したメンバー関数は、不変条件が維持されるようにするために存在します。それを気にしなければ、内部データ メンバーを公開して、人々が自由に操作できるようにすることができます。しかし、クラスには一貫性を持たせたいと考えています。つまり、パブリック インターフェイスの動作を指定する必要があります。それには特定のものが含まれる場合があります個々の関数を仮想化することによるカスタマイズ可能性のポイントですが、ほとんどの場合、ほとんどのメソッドを非仮想化することも含まれているため、不変条件が維持されることを保証する仕事を行うことができます。非仮想インターフェイスのイディオムは、この良い例です: http://www.gotw.ca/publications/mill18.htm

第 3 に、特に C++ では、継承はほとんど必要ありません。多くの場合、テンプレートとジェネリック プログラミング (静的ポリモーフィズム) は、継承 (ランタイム ポリモーフィズム) よりも優れています。はい、仮想メソッドと継承が必要な場合もありますが、それがデフォルトではないことは確かです。もしそうなら、あなたはそれを間違っています。それが何か他のものであるふりをしようとするのではなく、言語で作業してください。Java ではなく、Java とは異なり、C++ では継承は例外であり、ルールではありません。

于 2008-11-15T13:59:56.697 に答える
8

「一般的な」ケースでそれらを測定する方法がないため、パフォーマンスとメモリのコストは無視します...

仮想メンバー関数を持つクラスは非 POD です。したがって、POD であることに依存する低レベル コードでクラスを使用する場合は、(他の制限の中で) すべてのメンバー関数を非仮想にする必要があります。

POD クラスのインスタンスで移植可能にできることの例:

  • memcpy でコピーします (ターゲット アドレスに十分なアラインメントがある場合)。
  • offsetof() でフィールドにアクセスする
  • 一般に、char のシーケンスとして扱います
  • ...ええと
  • それはそれについてです。私は何かを忘れてしまったに違いありません。

私が同意する他の人々が言及したこと:

  • 多くのクラスは継承用に設計されていません。メソッドを仮想化すると、子クラスがメソッドをオーバーライドする可能性があり、子クラスがあってはならないことを意味するため、誤解を招く可能性があります。

  • 多くのメソッドはオーバーライドできるように設計されていません: 同じことです。

また、サブクラス化/オーバーライドすることを意図している場合でも、必ずしも実行時のポリモーフィズムを意図しているとは限りません。オブジェクト指向のベスト プラクティスが何を言おうとしていることにもかかわらず、ごくまれに、継承が必要なのはコードの再利用です。たとえば、シミュレートされた動的バインディングに CRTP を使用している場合。繰り返しますが、メソッドを仮想化することで、クラスがランタイム ポリモーフィズムで適切に機能することを示唆したくはありません。

要約すると、ランタイム ポリモーフィズムのためにオーバーライドされることを意図しているものは仮想とマークする必要があり、そうでないものは仮想とマークするべきではありません。ほとんどすべてのメンバー関数が仮想であることを意図していることがわかった場合は、そうしない理由がない限り、それらを仮想としてマークします。ほとんどのメンバー関数が仮想であることを意図していないことがわかった場合は、そうする理由がない限り、それらを仮想としてマークしないでください。

メソッドを一方から他方に切り替えることは破壊的変更であるため、パブリック API を設計する際には注意が必要な問題です。しかし、ユーザーがクラスを「ポリモーフィング」したいかどうかは、ユーザーを獲得する前に必ずしもわかるわけではありません。うーん。抽象インターフェイスを定義し、継承を完全に禁止する STL コンテナー アプローチは安全ですが、ユーザーがより多くの入力を行う必要がある場合があります。

于 2008-11-15T13:20:24.623 に答える
6

次の投稿は主に意見ですが、次のとおりです。

オブジェクト指向設計には 3 つの要素があり、カプセル化 (情報隠蔽) はその 1 つです。クラスの設計がこれに関してしっかりしていない場合、残りはあまり重要ではありません。

「継承はカプセル化を破る」(Alan Snyder '86) と以前言われました。クラスは、非常に具体的な方法で継承をサポートするように設計する必要があります。そうしないと、継承者による誤用の可能性が生じます。

すべてのメソッドを仮想化することは、すべてのメンバーを公開することに似ています。少し大げさかもしれませんが、だから私は「アナロジー」という言葉を使いました

于 2008-11-15T04:27:39.160 に答える
3

クラス階層を設計しているときに、オーバーライドしてはならない関数を作成することが理にかなっている場合があります。1 つの例は、複数のプライベート仮想メソッドを呼び出すパブリック メソッドがある「テンプレート メソッド」パターンを実行している場合です。派生クラスでそれをオーバーライドしたくないでしょう。誰もが基本定義を使用する必要があります。

「final」キーワードはないため、メソッドをオーバーライドしてはならないことを他の開発者に伝える最善の方法は、メソッドを非仮想にすることです。(簡単に無視されるコメント以外)

クラス レベルでは、デストラクターを非仮想にすることで、そのクラスを STL コンテナーなどの基本クラスとして使用してはならないことを伝えます。

メソッドを非仮想化すると、その使用方法が伝達されます。

于 2008-11-15T05:03:09.837 に答える
3

非仮想インターフェイスのイディオムは、非仮想メソッドを利用します。詳細については、Herb Sutter の記事「仮想性」を参照してください。

http://www.gotw.ca/publications/mill18.htm

そして、NVI イディオムに関するコメント:

http://www.parashift.com/c++-faq-lite/strange-inheritance.html#faq-23.3 http://accu.org/index.php/journals/269 [サブセクションを参照]

于 2008-11-15T05:44:50.580 に答える