(おそらく)最終クラスが必要ですか?

前回の投稿で、C++11 の 203 について説明しました 218 を使ってはいけないというガイドラインも出しました。 非ポリモーフィック クラスについて。私の推論は次のとおりです:

<オール> <リ>

一部のクラス (ポリシー クラスや、EBO が必要なその他のクラスなど) では、それらを 229 にします。 有害な可能性があります。

<リ>

他のクラス - ポリモーフィックに使用されていないクラス - 239 241 がない場合、すべての (優れた) C++ 開発者は、ポリモーフィックな継承階層でクラスを使用するべきではないと早い段階で教えられます。 functions.251 継承は意味がなく、有害です。誰もが知っている、264

<リ>

278 の使用例はほとんどありません ポリモーフィックな階層にあるため、一般的には必要ありません。

これは、reddit とブログのコメントの両方で議論を引き起こしたので、議論を統一し、それぞれの議論について書くために、このフォローアップを書くことにしました.

ポリモーフィック クラスの場合:Final を使用して MEC++ アイテム 33 を適用できます

Jon Kalb は以前の投稿で 289 を思い出させてくれました は、Modern Effective C++ の項目 33 を強制するために使用できます。それは、「非リーフ クラスを抽象化する必要がある」と述べています。そのためには、いくつかの問題と「悪い」抽象化が必要です。

それを強制したい場合は、抽象ではないリーフ クラスからの継承を防ぐ方法が必要です。これはまさに 290 です。 すべての具象クラス 302 をマークするだけです そのガイドラインに違反することはできません。

ポリモーフィック クラスの場合:311 最適化として使用できます

何人かが 327 と言いました コンパイラの最適化に役立ちます。これは本当です。

devirtualization と呼ばれる一般的なコンパイラの最適化があります。 .330 を呼び出す 関数は通常の関数を呼び出すよりもコストがかかります:344 逆参照して呼び出す必要がある正しい関数ポインターを取得するには、これらに従う必要があります。

したがって、コンパイラがどの関数を呼び出す必要があるかについて静的な情報を取得できる場合、通常の関数呼び出しを挿入するだけで済みます。しかし、コンパイラはいつそれを認識しますか? あなたがしないから機能があります どの関数を呼び出すかを知っています。

これは、基本クラスではなく、階層の下位にあるクラスを取る関数がある場合に発生する可能性があります。

特に:クラスがリーフの場合、次の翻訳単位を検討してください:

struct base
{
 virtual void foo() = 0;
 ...
};

struct derived : base
{
 void foo() override;
 ...
};

void func(const derived &d)
{
 d.foo();
}

ここで、コンパイラは 361 の場合よりも多くの情報を持っています 370 しかかかりません .特に、388 よりもさらに派生した既知の型があることがわかる場合 、394 への呼び出しを非仮想化できます タイプが 406 でなければならないことがわかっているため または子供および その 416

複数の翻訳単位を含む大規模なプロジェクトでは、これはここよりもわかりにくくなります。たとえば、クラス 426 を持つ別のファイルが存在する可能性があります。 .しかし、リンク時の最適化でそれが示されることもあります.それでも、 437 444 で コンパイラーがそれを簡単に認識できるようにして、最適化を促進しましょう。

オーケー、前回の投稿で、ポリモーフィック クラスの理由が思いつかなかったと述べました。非仮想化については知っていましたが、書くときに考えたことはありませんでした。これは、私がそれらの古典的な OOP 継承階層をほとんど使用しないためです。 .

さらに興味深い議論のポイントに進みます:非ポリモーフィック クラスと 452 .

464 広い契約と狭い契約

/u/quicknir が reddit で興味深いコメントを投稿し、私のブログ投稿自体よりも多くの賛成票を獲得したため、多くの人が同意しているようです.

彼はワイド コントラクトとナロー コントラクトについて書きました。クラスが 470 の場合 、これは狭い契約です。継承することはできません。ある日 482 を削除することにした場合 、広がる これは互換性を破る変更ではありません。ただし、その逆もあります。疑わしい場合は、常に 492 を使用してください 非ポリモーフィック クラス。彼は主張します。

彼はまた、私のロジックは非 505 という事実に基づいていると書いています。 がデフォルトであり、516 の状況を考慮するように求められました がデフォルトで、526 があります 535 を入れることを本当に主張しますか?

これは私に考えさせられました.私は他の議論に関係なく盲目的に立場を維持する人ではありません.誰かが良い技術的議論をするなら、私は時々立場を変えることができます.

そして (残念ながら) これは 良い技術的議論。

では、540 が存在する世界を考えてみましょう。 がデフォルトです。この世界では、非ポリモーフィック クラスから自動的に継承してはならないというガイドラインが適用されます。クラスの作成者として、継承を許可するために積極的な作業を行う必要があります。おそらく、これはより良い世界です。

そして、この世界では、クラスは 552 であると教えられて C++ を学んでいたでしょう。 デフォルトで.そして私はそのアプローチの利点を見るでしょう.おそらくこの世界では、561に対する私の主な議論 - EBO - 考慮されていないため、存在しません。

もちろん、私は 578 を支持するつもりはありません どこでも.誰もそうしません.

そうです、/u/quicknir は正しいです。私の主張は慣性から来ています。

したがって、584 を配置する必要があります ポリモーフィックでないすべてのクラスで?

595 を追加します。 コードベースのすべての非ポリモーフィック クラスに適用されますよね?

私はおそらくしません。

私はライブラリの作成者ですが、自分のクラスがどのように使用されているかわかりません。おそらく誰かが (non-609 ) 継承。ポリシー クラスはごく少数のクラスですが、私にとってはそうではありません。

記憶は中心 ほとんどすべてのクラスは、アロケータを使用してテンプレート化できます。また、これらのアロケータは空にすることができるため、EBO の利点を得るために継承が使用されます。

悲しいことに、EBO の状況は解決されていません。EBO の使用はハックであることに同意しますが、正直なところ、ほとんどの C++ 手法と同様に、EBO は依然として私の設計の不可欠な部分です。そのため、614 アロケーターや私が使用する他のポリシー クラスについても同様です。また、ポリシー クラスについても同様に行うことをお勧めします。

しかし、625 と競合しない、より良い解決策が必要です .いくつかの代替手段があります:

<オール> <リ>

638 を許可するだけです 640 からの継承 classes.これにより EBO の使用が許可されますが、654 の使用が妨げられると主張することもできます。 .

<リ>

667 のような新しい属性を作成します .クラス メンバーにそれを配置すると、コンパイラはそれらにゼロ サイズを与えることができます.しかし、これは属性には「大きすぎる」と主張する人がいるかもしれません.

<リ>

674 を与える 新しい意味で、現在は無視されています。その意味を変更して、メンバーとしてクラスに 681 のサイズを与えることができます .

<リ>

それらの「魔法の」標準ライブラリ クラスの 1 つを作成します。A 697 、Boost に似ていますが、701 で動作します しかし、個人的には、「普通の」ユーザーが実装できない標準ライブラリ コンポーネントが大嫌いです。

標準化プロセスの経験を持つ誰かが EBO に関する提案を起草できるかもしれません。空のクラスの状況に対する適切な解決策が与えられた場合、711 に関する私の見解は次のとおりです。 変更される予定です。どこでも使用します。

でも現状なので 726 は入れません そうする場合は、EBO の結果を念頭に置いてください。ポリシー クラスに対しては行わないでください。ジェネリック コードが簡単になります。