コンテンツにスキップ

C++/final

出典: フリー教科書『ウィキブックス(Wikibooks)』
< C++

finalキーワードの概要[編集]

C++11から導入された final キーワードは、クラス、メソッド、仮想メソッドに対して適用することができます。final が付与された要素は、それ以上継承やオーバーライドができなくなります。つまり、final キーワードは継承やオーバーライドを明示的に禁止する役割を持っています。

final キーワードを適切に使用することで、クラスの設計を固定化し、意図しない継承やオーバーライドを防ぐことができます。これにより、プログラムの予期せぬ振る舞いを防ぎ、保守性と安全性を高めることができます。

finalクラス[編集]

final をクラスに付与すると、そのクラスは継承不可能になります。

class Base { /* ... */ };
class Derived final : public Base { /* ... */ }; 
class AnotherDerived : public Derived { /* エラー! Derivedはfinalなので継承できない */ };

final クラスはインスタンス化は可能ですが、それ以上の継承は許可されません。final クラスを使うことで、クラスの階層構造を固定化し、意図せぬ継承による問題を回避できます。

finalメソッド[編集]

メソッドに final を付与すると、そのメソッドはオーバーライド不可能になります。

struct Base {
    void foo() { /* ... */ }
    void bar() final { /* ... */ }
};

struct Derived : Base {
    void foo() override { /* オーバーライド可能 */ }
    void bar() override { /* エラー! barはfinalなのでオーバーライド不可能 */ }
};

final メソッドをオーバーライドしようとするとコンパイルエラーになります。この機能を使うことで、メソッドの実装を固定化し、サブクラスでの誤った変更を防げます。

final仮想メソッド[編集]

仮想メソッドに final を付与すると、その仮想メソッドはオーバーライド不可能になります。

struct Base {
    virtual void foo() final { /* ... */ }
};

struct Derived : Base {
    void foo() override { /* エラー! fooはfinalなのでオーバーライド不可能 */ }
};

final 仮想メソッドは、サブクラスでオーバーライドされることを明示的に禁止します。一方、非仮想の final メソッドは、サブクラスで新しい同名の仮想メソッドを導入することは可能です。

struct Base {
    void bar() final { /* ... */ }
};

struct Derived : Base {
    virtual void bar() { /* 新しい仮想メソッドの導入は可能 */ }
};

final 仮想メソッドは、一般的に基底クラスの実装を固定化する目的で使用されます。

finalとパフォーマンス[編集]

final は、コンパイラの最適化の手がかりとしても使用できます。オーバーライドやさらなる継承が不可能であることがわかれば、コンパイラはより積極的な最適化を行えるからです。

具体的には、final 仮想メソッドに対してはデバッグ時の動的ディスパッチのオーバーヘッドを取り除くことができます。また、final クラスのインライン化の判断がしやすくなります。final の適切な使用は、パフォーマンスの改善につながる可能性があります。

ただし、最適化のメリットがプログラムの冗長性よりも小さい場合は、final を使わない方が望ましいでしょう。プログラムの保守性との兼ね合いを考慮する必要があります。

finalとデザインパターン[編集]

final は、いくつかのデザインパターンで有用です。一例としてデコレータパターンがあげられます。デコレータパターンでは、基底クラスの機能を確実にラップしたい場合があります。そのときに、基底クラスのメソッドを final にしておけば、誤ってデコレータからオーバーライドされるのを防げます。

一方、テンプレートメソッドパターンなど、継承とオーバーライドを前提とするパターンとは相性が悪いでしょう。デザインパターンを適用する際は、final キーワードとの親和性に注意が必要です。

finalの制限事項[編集]

final はクラス、メソッド、仮想メソッドにしか適用できません。その他の言語機能、例えばクラステンプレートや変数、型エイリアスなどには final を付与できません。また、複数の修飾子を組み合わせる際の規則に注意が必要です。

struct Base {
    virtual void foo() const final; // OK
    virtual void bar() final const; // エラー! finalの位置が間違っている
};

finalconstoverride よりも後に記述する必要があります。

finalのベストプラクティス[編集]

final の使用は、プログラマの設計意図を明確にする効果がありますが、過剰に使うと却ってプログラムの柔軟性を損なう可能性があります。final を適用する対象は、以下のようなケースが考えられます。

- 継承を許可したくない最終クラス - オーバーライドを許可したくないメソッド - 最適化のヒントとして使う部分

一方で、クラスやメソッドに対して基本的に final を付与し、必要に応じて final を外すという運用は、望ましくありません。継承やオーバーライドは、適切に使えば強力な機能です。final の使用は、慎重に検討すべきです。

他の言語のfinalとの類似と差異
他の言語でもfinalキーワードが存在し、C++のfinalと類似した役割を果たしています。しかし、言語によって細かな違いがあります。ここでは他言語のfinalとの比較を追記します。
Java
Javaのfinalキーワードは、C++と同様にクラス、メソッド、変数に対して使用できます。finalクラスは継承不可能で、finalメソッドはオーバーライド不可能です。一方で、C++とは異なり、Javaには仮想関数の概念がないため、C++のfinal仮想関数に相当する機能はありません。
また、Javaではfinal変数が存在し、一度初期化されると値を変更できなくなります。これはC++にはない機能です。
C#
C#のsealedキーワードがC++のfinalに相当します。sealedクラスは継承不可能で、sealedメソッドはオーバーライド不可能です。しかし、C++のfinal仮想関数に相当する機能はありません。
一方、C#にはreadonlyキーワードがあり、フィールドの再代入を防げます。これはJavaのfinal変数に近い機能と言えます。
C++のfinalの特徴
以上のように、他言語でも類似の仕組みは存在しますが、C++のfinal仮想関数のような機能は他言語にはありません。また、C++は静的型付け言語なので、finalによるコンパイル時のチェックが可能です。
つまり、C++のfinalは、クラス、メソッド、仮想関数のすべてに適用でき、コンパイル時に継承やオーバーライドを強制的に制限できる点で、他言語に類を見ない機能だと言えるでしょう。


まとめ[編集]

final キーワードは、C++11から導入された機能です。final を使うと、クラス、メソッド、仮想メソッドに対して、継承やオーバーライドを明示的に禁止できます。これにより、プログラムのインターフェースをより堅牢にし、望ましくない振る舞いを防ぐことができます。一方で、final の過剰使用はプログラムの柔軟性を損なう可能性があるため、適切な使用が求められます。

final は、パフォーマンスの最適化のヒントにもなり得ます。また、デコレータパターンなどのデザインパターンで有用な場合もあります。しかし制限事項にも注意する必要があり、他の修飾子との組み合わせ方法にも規則があります。

final キーワードは、プログラムの設計意図を明確にし、予期せぬ動作を防ぐのに役立ちます。しかし、過剰に使うと柔軟性を失う可能性があるため、慎重に使用すべきです。クラスやメソッドに対して一律に final を付与し、必要に応じて外すという運用は避けるべきでしょう。

final を適切に使うポイントは、以下のようなケースが挙げられます。

- 最終的な実装を固定したいクラスやメソッドに対して - サブクラスでの変更を許可したくない部分に対して - コンパイラの最適化を助ける箇所に対して

一方で、柔軟性の確保が重要な部分では final を避けるべきです。特に、フレームワークやライブラリの作成時は、将来の拡張性を考慮する必要があります。

final の使用は、プログラムの保守性、パフォーマンス、拡張性など、様々な要因を総合的に検討した上で判断するのが賢明でしょう。使いすぎも使わなすぎも、プログラムのライフサイクルコストを高める可能性があります。

設計の初期段階から final の適切な使用を意識し、要求仕様やアーキテクチャに基づいて、慎重に final の適用を検討することが重要です。そうすることで、バランスの取れた堅牢で保守性の高いプログラムを構築できるはずです。