(おそらく) 最終クラスは必要ない

C++11 は final を導入しました 「キーワード」。メンバー関数とクラスを final としてマークするために使用できます。 つまり、派生クラスでオーバーライドすることも、基本クラスにすることもできません。

この投稿では、詳しく見て、final の使用を検討する理由を説明します。 場合によっては問題のあるクラスです。

final メンバー関数

final メンバー関数は、オーバーライドできないメンバー関数です。もちろん、それは virtual の場合にのみ意味があります virtual 以外のメンバー関数 関数は定義によりオーバーライドできません。つまり、final メンバー関数のキーワードは、派生クラスでのみ意味があります。

例を考えてみましょう:

class base
{
public:
 virtual void foo(); 
 virtual void bar();
};

class derived : public base
{
public:
 // foo() is now finished; there is now possible way it could be improved
 void foo() override final;
 
 // bar() isn't finished; it could be improved
 void bar() override;
};

基本クラス base があります 2 つの virtual で 関数 foo()bar() .次に、派生クラス derived を作成します 両方の関数をオーバーライドします。しかし、 foo() の実装を書くとき クラス second_derived は必要ありません foo() をオーバーライドできます bar() のみをオーバーライドする必要があります。 、 foo() このままで完璧です。だから final を使います foo() で .

つまり final です 私はそれで問題ありません.私はそれが少し不必要であり、実際のユースケースを見ることができませんが、それ以外は問題ありません.

final クラスについて

しかし、bar() に気付いたとします。 final にする必要があります .実際、派生から派生するのはまったく意味がありません!

そのためには final を使用できます クラスについて:

class base
{
 // as before
};

class derived final
: public base
{
 // as before
 // final on member functions no unnecessary
};

derived から継承しようとするとエラーになります

実際には、それを使用したい場合が 2 つあります。派生クラスでそれ以上の派生を防ぐか、基本クラスでも派生クラスでもないクラスで、継承階層で使用されるのをまったく防ぎます。後者の場合は、 virtual のない機能を持つ 誰かがポリモーフィックに使用しようとするのを防ぎたい. どちらの場合でも final 継承を防ぐために使用できます。

しかし、特定のタイプでそれを行うと、私はあなたを嫌い、私のコードは そうします そのために、継承の使用法を復習しましょう。

継承アプリケーション

継承の最も顕著な特徴は、少なくとも「OOP の人々」にとっては、ポリモーフィズムを有効にすることです。その場合、基本クラスには (純粋な) virtual があります。 関数と (純粋な) virtual destructor.Derived クラスは public を継承します

ここまでは Java です。

しかし、public 以外にも継承のケースがあります。 継承。 private も使用できます 継承。

public の間 継承モデルの「is-a」関係 (または:モデル化することになっている)、private 継承モデル - まあ - 実際には何もありません.これは実装の詳細であり、部外者には見えません.

では、どこで使用する必要がありますか?

継承が必要だが基本クラスに virtual がない場所 デストラクタおよび/またはポリモーフィズムを持ちたくない.1つの例は、protectedを持つクラスです ヘルパー クラスとして意図されているが、派生する必要があるデストラクタ。通常、合成は 明らかに 推奨されますが、有効な使用例がある場合もあります。

その 1 つがポリシー ベースの設計です。

ポリシー ベースの設計

ポリシーベースの設計。 Alexandrescu の Modern C++ Design book で説明されているのは、クラスをさまざまなポリシーに抽出する手法であり、各ポリシーは、ユーザーがカスタマイズする必要がある特定の側面を処理します。

たとえば、コンテナ クラスがあるとします。コンテナは動的メモリを割り当てる必要があり、それをカスタマイズできれば便利です。これは、ポリシーベースの設計によって行うことができます。コンテナ クラスは、追加のテンプレート パラメータ Allocator これはユーザー定義型 (ポリシー) であり、特定の機能セットを提供する必要があります。手動で new を呼び出す代わりに と delete コンテナー内で、指定されたポリシー タイプのいくつかの関数を呼び出します。その後、ユーザーは新しいアロケーター タイプを作成して、その動作をカスタマイズできます。

ポリシーベースの設計は優れており、多くのアプリケーションがあります。しかし、基本的な問題があります。それは、ポリシーをどのように保存するかということです。通常の方法でメンバーとして保持するか、ポリシーを継承するかのどちらかです。後者には、比較して利点があります。 3 文字とポリシー クラスの共通の特性のため、前者の方法にします。

エボ

ポリシー クラスは、呼び出すことができるいくつかの機能を提供するためだけに存在します。多くの場合、ポリシー クラスは実際にはデータ メンバーを格納せず、です。 .

空の型のサイズは?

0 ではありません ご想像のとおり、1 です .

そのため、メンバーとして空の (ポリシー) クラスがある場合でも、そのために数バイトを支払う必要があります!アライメントに関しては、これはスペースの膨大な浪費になる可能性があります.

幸いなことに、空の型から継承することでこれを回避できます。その後、コンパイラは空のベースの最適化を有効にすることができます。

空の基本クラスは 0 を持つことができます サイズ。ただし、基本クラスとしてのみ。したがって、ポリシー クラスは (private として格納する必要があります) ) ベース。

これは final に関して結果をもたらします :final の空のクラスがある場合 EBO は使用できません。

class の場合、これは回避できます。 宣言されていません final そもそも!だからお願い 次のガイドラインに従うことで、(一般的な) ライブラリ作成者の生活が楽になり、あなたの生活が向上します:

<オール> <リ>

空の基本クラスを宣言しない final .

<リ>

virtual 以外から削除することも検討してください 一貫性のためのクラスです。それらがポリモーフィックに使用できないという事実は、virtual の欠如によってすでに示されています。 関数なので、public とにかく継承を行うべきではありません。また、クラスの作成者として、private を防ぐために使用することはめったにありません。 /protected 継承なので、final は省略できます。

final の唯一の使用方法 これ以上変更してはならないポリモーフィックな派生クラスにあり、final を配置するのが面倒です ただし、前に述べたように、その理由も思いつきません。個人的には final は使用しません。 .

更新 :Jon Kalb が final の使用について適切な理由を提供しています。 以下のコメントでポリモーフィック階層のクラスについて説明します。MEC++ の Scott Meyers ガイドライン 33 には、「リーフ以外のクラスを抽象化する必要があります」と記載されているように、final クラスは必然的にリーフ クラスです。したがって、キーワードは、継承を防止することにより、クラス ライターにこのイディオムを強制する権限を与えます。

結論

final キーワードは、a) メンバー関数のそれ以上のオーバーライドを防ぐために、および b) クラスからの継承を防ぐために使用できます。

したがって、b) はめったに使用せず、virtual を持つクラスでのみ使用することをお勧めします 関数。