C ++では、仮想メソッド宣言でfinalをいつ使用すればよいですか?


11

final仮想メソッドが派生クラスによってオーバーライドされるのを防ぐためにキーワードが使用されていることを知っています。ただし、メソッドでキーワードを実際に使用する必要がある場合に役立つ例はありません。さらに、仮想メソッドでの使用は、プログラマが将来クラスを拡張することを許可しないため、悪臭のように感じられます。finalvirtualfinal

私の質問は次です:

メソッド宣言finalで実際に使用する必要がある有用なケースはありますvirtualか?


Javaメソッドがfinalになるのと同じ理由で?
Ordous、2015

Javaでは、すべてのメソッドが仮想です。基本クラスのfinalメソッドは、パフォーマンスを向上させることができます。C ++には非仮想メソッドがあるため、この言語には当てはまりません。他に良い例を知っていますか?聞きたいです。:)
メタメーカー2015

私は物事を説明するのが苦手だと思います-私はマイク・ナキスとまったく同じことを言っていました。
Ordous、2015

回答:


12

finalキーワードの機能の要約:ベースクラスAと派生クラスがあるとしBます。関数がf()で宣言することができるAようにvirtualクラスがあることを意味し、Bそれを無効にすることができます。しかし、クラスBは、さらに派生したクラスがBオーバーライドできないようにしたい場合がありますf()。私たちが宣言する必要があるときですf()としてfinalの中でBfinalキーワードがなければ、メソッドを仮想として定義すると、派生クラスはそれを自由にオーバーライドできます。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()が存在し、うまく適合する実際のケースはありますか?
メタメーカー、2015

はい、ごめんなさい、ちょっと待ってください。今、例を書いています。
Mike Nakis、2015

@metamaker、そこに行きます。
Mike

1
本当に良い例、これが今のところ最良の答えです。どうも!
メタメーカー、2015

1
無駄な時間をありがとうございました。インターネットで良い例を見つけることができず、検索に多くの時間を費やしました。答えには+1を入れますが、評判が低いのでできません。:(たぶん後で。
metamaker

2

変更しないものとしてマークを付けることができると、デザインの観点からしばしば役立ちます。同様に、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が同時に使用されるのですか?

これは、両方としての機能を宣言するために階層のルートに基本クラスの意味がないvirtualfinal。コンパイラと人間の読者の両方が不必要なフープを飛び越えなければならないので、それは私にはかなりばかげているように見えますvirtual。ただし、サブクラスは次のような仮想メンバー関数を継承します。

struct Foo
{
   virtual ~Foo() {}
   virtual void f() = 0;
};

struct Bar: Foo
{
   /*implicitly virtual*/ void f() final {...}
};

この場合、Bar::fvirtualキーワードを明示的に使用するかどうかBar::fは仮想関数です。virtualキーワードは、この場合には、オプションとなります。したがって、それが仮想関数であっても、Bar::fとして指定することは意味があるかもしれませんfinal(仮想関数にのみ使用finalできます)。

そして、一部の人々は、様式的に、それBar::fが仮想であることを明示的に示すことを好むかもしれません。

struct Bar: Foo
{
   virtual void f() final {...}
};

私にとって、このコンテキストで同じ関数にvirtualfinal指定子の両方を使用するのは一種の冗長です(同様にvirtualおよびoverride)。ただし、この場合はスタイルの問題です。他のリンケージ修飾子がないオプションのオプションであるにもかかわらず、外部リンケージのある関数宣言にvirtual使用するのと同じように、それがここで何か価値のあるものを伝えると感じる人もいるかもしれませんextern


クラスprivate内のメソッドをどのようにオーバーライドする予定Vehicleですか?protected代わりに意味しませんでしたか?
Andy

3
@DavidPacker privateはオーバーライドに拡張されません(少し直観に反します)。仮想関数のパブリック/保護/プライベート指定子は、大まかに言えば、呼び出し元にのみ適用され、オーバーライドには適用されません。派生クラスは、その可視性に関係なく、基本クラスの仮想関数をオーバーライドできます。

@DavidPacker protectedは、もう少し直感的に理解できるかもしれません。私はできる限り、最も低い可視性に到達することを好むだけです。言語がこのように設計されている理由は、そうでなければ、プライベート仮想メンバー関数は友情のコンテキストの外ではまったく意味をなさないからだと思います。呼び出すだけでなく、オーバーライドする

プライベートに設定してもコンパイルできないと思いました。たとえば、JavaもC#もそれを許可していません。C ++は毎日私を驚かせます。プログラミングの10年後でも。
Andy

@DavidPacker私が最初に遭遇したとき、それも私をつまずきました。たぶん、protected他の人を混乱させないためにそれを作るべきだ。少なくとも、プライベート仮想関数をどのようにオーバーライドできるかについて、少なくともコメントを書いてしまいました。

0
  1. コンパイル時にどの関数が呼び出されるかがわかるため、多くの最適化が可能になります。

  2. 「コードのにおい」という言葉を注意深く投げてください。「最終」は、クラスを拡張することを不可能にしません。「最後の」単語をダブルクリックし、バックスペースを押して、クラスを拡張します。ただし、finalは優れたドキュメントであり、開発者が関数をオーバーライドすることを期待していないため、次の開発者は非常に注意する必要があります。finalメソッドがそのようにオーバーライドされると、クラスが適切に機能しなくなる可能性があるためです。


なぜだろうfinalvirtual、これまで同時に使用することは?
Robert Harvey

1
コンパイル時にどの関数が呼び出されるかがわかるため、多くの最適化が可能になります。 どのように説明するか、参照を提供できますか? ただし、finalは優れたドキュメントであり、開発者が関数をオーバーライドすることを期待していないため、次の開発者は非常に注意する必要があります。finalメソッドがそのようにオーバーライドされると、クラスが適切に機能しなくなる可能性があるためです。 悪臭じゃないですか。
メタメーカー、2015

@RobertHarvey ABIの安定性。それ以外の場合は、おそらくfinaloverrideです。
Deduplicator

- @metamaker私はここのコメントで説明したのと同じQ、持っていたprogrammers.stackexchange.com/questions/256778/...を - &funnilyだけ求めているので、他のいくつかの議論につまずいてきました!基本的には、最適化コンパイラー/リンカーが、参照/ポインターが派生クラスを表し、仮想呼び出しが必要かどうかを判断できるかどうかについてです。メソッド(またはクラス)が参照自体の静的型を超えてオーバーライドできない場合は、それらを仮想化することができます:直接呼び出します。ある場合いずれかの疑い-できるだけ頻繁に-彼らは事実上、呼び出す必要があります。宣言finalは本当に彼らを助けることができます
underscore_d
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.