web-dev-qa-db-ja.com

最終的な仮想機能のポイントは何ですか?

Wikipedia には、C++ 11 final修飾子に関する次の例があります。

struct Base2 {
    virtual void f() final;
};

struct Derived2 : Base2 {
    void f(); // ill-formed because the virtual function Base2::f has been marked final
};

仮想機能を導入し、すぐに最終機能としてマークするポイントがわかりません。これは単に悪い例ですか、それともそれ以上ですか?

51
fredoverflow

通常、finalは、仮想関数の基本クラスの定義では使用されません。 finalは、派生クラスが関数をオーバーライドすることを防ぐために、関数をオーバーライドする派生クラスによって使用されます。オーバーライド関数は通常仮想である必要があるため、さらに派生した型で誰でもその関数をオーバーライドできることを意味します。 finalを使用すると、別の関数をオーバーライドするが、それ自体をオーバーライドできない関数を指定できます。

たとえば、クラス階層を設計していて、関数をオーバーライドする必要があるが、クラス階層のユーザーに同じことを許可したくない場合、派生クラスで関数をfinalとしてマークする場合があります。

54
bames53

関数にfinalというラベルを付けるには、virtual、つまりC++ 11§10.3paraである必要があります。 2:

[...]便宜上、仮想関数はそれ自体をオーバーライドすると言います。

およびパラ4:

あるクラスBの仮想関数fがvirt-specifier finalでマークされ、Bから派生したクラスDで関数D :: fがB :: fをオーバーライドする場合、プログラムの形式は正しくありません。 [...]

つまり、finalは仮想関数(または継承をブロックするクラス)でのみ使用する必要があります。したがって、この例では、virtualを使用して有効なC++コードにする必要があります。

編集:完全に明確にするために:「ポイント」は、仮想が使用される理由についての懸念について尋ねました。使用される最終的な理由は、(i)コードがコンパイルされないためです。(ii)十分な場合に、より多くのクラスを使用してサンプルをより複雑にするのはなぜですか?したがって、仮想の最終関数を持つクラスが1つだけ例として使用されます。

8
Paul Preney

私にはまったく役に立たないようです。これは、構文を示すための例にすぎないと思います。

1つの可能な使用法は、fを実際にオーバーライド可能にしたくないが、それでもvtableを生成したい場合ですが、それでも物事を行う恐ろしい方法です。

8
Antimony

仮想機能を導入し、すぐに最終機能としてマークするポイントがわかりません。

この例の目的は、finalがどのように機能するかを説明することです。

実用的の目的は、vtableがクラスのサイズにどのように影響するかを確認することです。

_struct Base2 {
    virtual void f() final;
};
struct Base1 {
};

assert(sizeof(Base2) != sizeof(Base1)); //probably
_

_Base2_は単にプラットフォーム固有のテストに使用できますが、f()をオーバーライドする意味はありません。テスト目的のためだけにあるため、finalとマークされています。もちろん、これをしている場合、デザインに何か問題があります。私は個人的にvirtualのサイズをチェックするためだけにvfptr関数を持つクラスを作成しません。

4
Luchian Grigore

上記のニースの回答に追加する-これはよく知られているfinalのアプリケーションです(Javaから非常に影響を受けています)。 Baseクラスで関数wait()を定義し、only oneすべての子孫でwait()の実装が必要だと仮定します。この場合、wait()をfinalとして宣言できます。

例えば:

class Base { 
   public: 
       virtual void wait() final { cout << "I m inside Base::wait()" << endl; }
       void wait_non_final() { cout << "I m inside Base::wait_non_final()" << endl; }
}; 

派生クラスの定義は次のとおりです。

class Derived : public Base {
      public: 
        // assume programmer had no idea there is a function Base::wait() 

        // error: wait is final
        void wait() { cout << "I am inside Derived::wait() \n"; } 
        // that's ok    
        void wait_non_final() { cout << "I am inside Derived::wait_non_final(); }

} 

Wait()が純粋な仮想関数の場合、役に立たない(正しくない)になります。この場合:コンパイラーは、派生クラス内でwait()を定義するように求めます。そうすると、wait()が最終的なためエラーが発生します。

なぜ最終関数は仮想である必要があるのですか?(これも混乱します)(imo)1)finalの概念は仮想関数の概念に非常に近い[仮想関数には多くの実装があります-最終関数には1つの実装のみ]、2)vtableを使用して最終的な効果を実装するのは簡単です。

2
AJed

レガシコードをリファクタリングする(たとえば、母クラスから仮想であるメソッドを削除する)場合、これは子クラスがこの仮想関数を使用していないことを確認するのに役立ちます。

// Removing foo method is not impacting any child class => this compiles
struct NoImpact { virtual void foo() final {} };
struct OK : NoImpact {};

// Removing foo method is impacting a child class => NOK class does not compile
struct ImpactChildClass { virtual void foo() final {} };
struct NOK : ImpactChildClass { void foo() {} };

int main() {}
2
Richard Dally

基本クラスでvirtualfinalの両方の関数を実際に宣言する理由は次のとおりです。

class A {
    void f();
};

class B : public A {
    void f(); // Compiles fine!
};

class C {
    virtual void f() final;
};

class D : public C {
    void f(); // Generates error.
};

finalhasとマークされた関数は、virtualにもなります。関数finalをマークすると、派生クラスで同じ名前とシグネチャを持つ関数を宣言できなくなります。

1
Omnifarious

仮想関数がfinalとして宣言されると便利な別のケースを見つけました。このケースは SonarQubeの警告リスト の一部です。警告の説明は次のとおりです。

コンストラクターまたはデストラクターからオーバーライド可能なメンバー関数を呼び出すと、メンバー関数をオーバーライドするサブクラスをインスタンス化するときに予期しない動作が発生する可能性があります。

例えば:
-契約により、サブクラスクラスコンストラクターは、親クラスコンストラクターを呼び出して開始します。
-親クラスのコンストラクターは、子クラスでオーバーライドされたものではなく、親メンバー関数を呼び出します。これは、子クラスの開発者を混乱させます。
-メンバー関数が親クラスで純粋な仮想である場合、未定義の動作を引き起こす可能性があります。

違反コード例

class Parent {
  public:
    Parent() {
      method1();
      method2(); // Noncompliant; confusing because Parent::method2() will always been called even if the method is overridden
    }
    virtual ~Parent() {
      method3(); // Noncompliant; undefined behavior (ex: throws a "pure virtual method called" exception)
    }
  protected:
    void         method1() { /*...*/ }
    virtual void method2() { /*...*/ }
    virtual void method3() = 0; // pure virtual
};

class Child : public Parent {
  public:
    Child() { // leads to a call to Parent::method2(), not Child::method2()
    }
    virtual ~Child() {
      method3(); // Noncompliant; Child::method3() will always be called even if a child class overrides method3
    }
  protected:
    void method2() override { /*...*/ }
    void method3() override { /*...*/ }
};

適合ソリューション

class Parent {
  public:
    Parent() {
      method1();
      Parent::method2(); // acceptable but poor design
    }
    virtual ~Parent() {
      // call to pure virtual function removed
    }
  protected:
    void         method1() { /*...*/ }
    virtual void method2() { /*...*/ }
    virtual void method3() = 0;
};

class Child : public Parent {
  public:
    Child() {
    }
    virtual ~Child() {
      method3(); // method3() is now final so this is okay
    }
  protected:
    void method2() override { /*...*/ }
    void method3() final    { /*...*/ } // this virtual function is "final"
};
0
dismine

これの代わりに:

public:
    virtual void f();

これを書くと便利だと思います。

public:
    virtual void f() final
        {
        do_f(); // breakpoint here
        }
protected:
    virtual void do_f();

主な理由は、潜在的に多くのオーバーライドされた実装のいずれかにディスパッチする前に、ブレークポイントの場所が1つになったことです。悲しいことに(IMHO)、「最終」と言うには、「仮想」と言うことも必要です。

0
Kevin Hopps

virtual + finalは、例を短くするために1つの関数宣言で使用されます。

virtualおよびfinalsyntaxに関して、Wikipediaの例は_struct Base2 : Base1_を導入することでより表現力豊かになります。 Base1にはvirtual void f();が含まれ、Base2にはvoid f() final;が含まれます(以下を参照)。

標準

N369 を参照:

  • virtual as _function-specifier_は_decl-specifier-seq_の一部にできます
  • finalは_virt-specifier-seq_の一部にできます

keywordvirtualと特別な意味を持つIdentifiersを使用する必要があるルールはありませんfinal一緒に。 8.4項、関数定義(heed opt = optional):

関数定義:

属性指定子-seq(opt)decl-specifier-seq(opt)宣言子virt-specifier-seq(opt)関数本体

練習

C++ 11では、virtualを使用するときにfinalキーワードを省略できます。これは、gcc> 4.7.1、c ++> 3.0、C++ 11、msvc、...でコンパイルされます( compiler Explorer を参照)。

_struct A
{
    virtual void f() {}
};

struct B : A
{
    void f() final {}
};

int main()
{
    auto b = B();
    b.f();
}
_

PS: cppreferenceの例 は、同じ宣言でfinalとvirtualを併用しません。

PPS:overrideにも同じことが当てはまります。

0
Roi Danton