final
仮想メソッドが派生クラスによってオーバーライドされるのを防ぐためにキーワードが使用されていることを知っています。ただし、メソッドでキーワードを実際に使用する必要がある場合に役立つ例はありません。さらに、仮想メソッドでの使用は、プログラマが将来クラスを拡張することを許可しないため、悪臭のように感じられます。final
virtual
final
私の質問は次です:
メソッド宣言final
で実際に使用する必要がある有用なケースはありますvirtual
か?
final
仮想メソッドが派生クラスによってオーバーライドされるのを防ぐためにキーワードが使用されていることを知っています。ただし、メソッドでキーワードを実際に使用する必要がある場合に役立つ例はありません。さらに、仮想メソッドでの使用は、プログラマが将来クラスを拡張することを許可しないため、悪臭のように感じられます。final
virtual
final
私の質問は次です:
メソッド宣言final
で実際に使用する必要がある有用なケースはありますvirtual
か?
回答:
final
キーワードの機能の要約:ベースクラスA
と派生クラスがあるとしB
ます。関数がf()
で宣言することができるA
ようにvirtual
クラスがあることを意味し、B
それを無効にすることができます。しかし、クラスB
は、さらに派生したクラスがB
オーバーライドできないようにしたい場合がありますf()
。私たちが宣言する必要があるときですf()
としてfinal
の中でB
。final
キーワードがなければ、メソッドを仮想として定義すると、派生クラスはそれを自由にオーバーライドできます。final
キーワードは、この自由に終止符を打つために使用されます。
あなたがそのようなことを必要とする理由の例:と仮定クラスがA
仮想関数を定義prepareEnvelope()
し、クラスがB
それと独自の仮想メソッドの呼び出しのシーケンスとして実装し、それを上書きしstuffEnvelope()
、lickEnvelope()
そしてsealEnvelope()
。クラスB
は、派生クラスがこれらの仮想メソッドをオーバーライドして独自の実装を提供できるようにすることを意図していますが、クラスB
は派生クラスがオーバーライドprepareEnvelope()
して、スタッフの順序を変更したり、リックしたり、シールしたり、それらのいずれかを呼び出したりすることを望んでいません。したがって、この場合、クラスはfinalとしてB
宣言さprepareEnvelope()
れます。
class B might wish that any class which is further derived from B should not be able to override f()
?そのようなクラスBとメソッドf()が存在し、うまく適合する実際のケースはありますか?
変更しないものとしてマークを付けることができると、デザインの観点からしばしば役立ちます。同様に、const
はコンパイラガードを提供し、状態が変化してはならないことfinal
を示します。これを使用して、継承階層の下位で動作を変更しないことを示すことができます。
例
車両がプレイヤーをある場所から別の場所に連れて行くビデオゲームを考えてみましょう。すべての車両は、出発前に有効な場所に移動していることを確認する必要があります(その場所の基地が破壊されていないなど)。車両に関係なくこのチェックが行われることを保証するために、非仮想インターフェースイディオム(NVI)を使用して開始できます。
class Vehicle
{
public:
virtual ~Vehicle {}
bool transport(const Location& location)
{
// Mandatory check performed for all vehicle types. We could potentially
// throw or assert here instead of returning true/false depending on the
// exceptional level of the behavior (whether it is a truly exceptional
// control flow resulting from external input errors or whether it's
// simply a bug for the assert approach).
if (valid_location(location))
return travel_to(location);
// If the location is not valid, no vehicle type can go there.
return false;
}
private:
// Overridden by vehicle types. Note that private access here
// does not prevent derived, nonfriends from being able to override
// this function.
virtual bool travel_to(const Location& location) = 0;
};
今、私たちのゲームには飛行車両があり、すべての飛行車両が必要とする共通点は、離陸前に格納庫内の安全検査チェックを通過する必要があるということです。
ここではfinal
、すべての飛行車両がこのような検査を通過することを保証し、飛行車両のこの設計要件を伝えるために使用できます。
class FlyingVehicle: public Vehicle
{
private:
bool travel_to(const Location& location) final
{
// Mandatory check performed for all flying vehicle types.
if (safety_inspection())
return fly_to(location);
// If the safety inspection fails for a flying vehicle,
// it will not be allowed to fly to the location.
return false;
}
// Overridden by flying vehicle types.
virtual void safety_inspection() const = 0;
virtual void fly_to(const Location& location) = 0;
};
final
このように使用することで、非仮想インターフェイスのイディオムの柔軟性を効果的に拡張して、継承階層の下で均一な動作を(たとえ後から考えても、壊れやすい基本クラスの問題に対抗して)仮想関数自体に提供できます。さらに、私たちは自分の部屋を購入して、存在するすべての飛行車両の実装を変更することなく、後付けとしてすべての飛行車両タイプに影響する中心的な変更を行います。
これは、の使用例の1つfinal
です。仮想メンバー関数がこれ以上オーバーライドされても意味がないコンテキストが発生します。これを行うと、設計が不安定になり、設計要件に違反する可能性があります。
これfinal
は、設計/アーキテクチャの観点から見ると便利です。
また、オプティマイザが仮想関数呼び出しを仮想化解除できるようにするこの設計情報を提供するので、オプティマイザの観点からも役立ちます(動的ディスパッチのオーバーヘッドがなくなり、多くの場合、呼び出し側と呼び出し先の間の最適化の障壁がなくなります)。
質問
コメントから:
なぜfinalとvirtualが同時に使用されるのですか?
これは、両方としての機能を宣言するために階層のルートに基本クラスの意味がないvirtual
とfinal
。コンパイラと人間の読者の両方が不必要なフープを飛び越えなければならないので、それは私にはかなりばかげているように見えますvirtual
。ただし、サブクラスは次のような仮想メンバー関数を継承します。
struct Foo
{
virtual ~Foo() {}
virtual void f() = 0;
};
struct Bar: Foo
{
/*implicitly virtual*/ void f() final {...}
};
この場合、Bar::f
virtualキーワードを明示的に使用するかどうかBar::f
は仮想関数です。virtual
キーワードは、この場合には、オプションとなります。したがって、それが仮想関数であっても、Bar::f
として指定することは意味があるかもしれませんfinal
(仮想関数にのみ使用final
できます)。
そして、一部の人々は、様式的に、それBar::f
が仮想であることを明示的に示すことを好むかもしれません。
struct Bar: Foo
{
virtual void f() final {...}
};
私にとって、このコンテキストで同じ関数にvirtual
とfinal
指定子の両方を使用するのは一種の冗長です(同様にvirtual
およびoverride
)。ただし、この場合はスタイルの問題です。他のリンケージ修飾子がないオプションのオプションであるにもかかわらず、外部リンケージのある関数宣言にvirtual
使用するのと同じように、それがここで何か価値のあるものを伝えると感じる人もいるかもしれませんextern
。
private
内のメソッドをどのようにオーバーライドする予定Vehicle
ですか?protected
代わりに意味しませんでしたか?
private
はオーバーライドに拡張されません(少し直観に反します)。仮想関数のパブリック/保護/プライベート指定子は、大まかに言えば、呼び出し元にのみ適用され、オーバーライドには適用されません。派生クラスは、その可視性に関係なく、基本クラスの仮想関数をオーバーライドできます。
protected
は、もう少し直感的に理解できるかもしれません。私はできる限り、最も低い可視性に到達することを好むだけです。言語がこのように設計されている理由は、そうでなければ、プライベート仮想メンバー関数は友情のコンテキストの外ではまったく意味をなさないからだと思います。呼び出すだけでなく、オーバーライドする
コンパイル時にどの関数が呼び出されるかがわかるため、多くの最適化が可能になります。
「コードのにおい」という言葉を注意深く投げてください。「最終」は、クラスを拡張することを不可能にしません。「最後の」単語をダブルクリックし、バックスペースを押して、クラスを拡張します。ただし、finalは優れたドキュメントであり、開発者が関数をオーバーライドすることを期待していないため、次の開発者は非常に注意する必要があります。finalメソッドがそのようにオーバーライドされると、クラスが適切に機能しなくなる可能性があるためです。
final
とvirtual
、これまで同時に使用することは?
final
とoverride
です。
final
は本当に彼らを助けることができます