C ++ 11の関数の「最終」キーワードの目的は何ですか?


143

finalC ++ 11の関数のキーワードの目的は何ですか?派生クラスによる関数のオーバーライドを防ぐことは理解していますが、これが当てはまる場合は、final関数を非仮想として宣言するだけで十分ではないでしょうか?私がここで見逃している別のものはありますか?


30
最終『の機能は、非仮想あなたのように宣言するのに十分ではありません』あなたが使用するかどうかをオーバーライド機能は、暗黙的に仮想され、」いいえvirtualキーワードをか。
ildjarn 2012年

13
それらは、スーパークラスの仮想として宣言されていなかった場合は真実ではない@ildjarn、あなたはクラスから派生することはできませんし、仮想一つに非仮想メソッドを変換...
ダン・O

10
@DanOオーバーライドはできないと思いますが、その方法でメソッドを「隠す」ことができます。メソッドを非表示にするつもりはないため、多くの問題が発生します。
Alex Kremer

16
@DanO:スーパークラスで仮想でない場合、「オーバーライド」されません。
ildjarn、2012年

2
ここでも、「オーバーライド」には特定の意味があります。これは、仮想関数に多態的な動作を与えることです。あなたの例funcでは仮想ではないので、オーバーライドするものは何もないので、overrideまたはとしてマークするものは何もありませんfinal
ildjarn

回答:


129

idljarnがコメントですでに述べたように、欠落しているのは、基本クラスから関数をオーバーライドする場合、非仮想としてマークすることができないということです。

struct base {
   virtual void f();
};
struct derived : base {
   void f() final;       // virtual as it overrides base::f
};
struct mostderived : derived {
   //void f();           // error: cannot override!
};

ありがとう!これが私が欠けていたポイントです。つまり、「リーフ」クラスでさえ、関数をオーバーライドするつもりであり、自分でオーバーライドされない場合でも、その関数を仮想としてマークする必要があります
lezebulon

8
@lezebulon:スーパークラスが関数を仮想として宣言した場合、リーフクラスは関数を仮想としてマークする必要はありません。
Dan O

5
リーフクラスのメソッドは、基本クラスで仮想の場合、暗黙的に仮想です。この暗黙の「仮想」が欠落している場合、コンパイラーは警告する必要があると思います。
アーロン・マクデイド

@AaronMcDaid:コンパイラは通常、コードが正しい場合、混乱やエラーを引き起こす可能性があることを警告します。問題の原因となるような方法でこの言語のこの特定の機能に驚いた人を見たことがありません。そのため、そのエラーがどの程度役立つかはわかりません。逆に、忘れてvirtual追加のエラーを引き起こす可能性があり、そしてC ++ 11 overrideその状況を検出し、機能をすることを意図しているとき、コンパイルに失敗する関数にタグをオーバーライドし、実際の皮を
デビッド・ロドリゲス- dribeas

1
GCC 4.9からの変更点:「仮想化を改善する新しいタイプの継承分析モジュール。仮想化では、匿名の名前空間とC ++ 11の最終的なキーワードが考慮されるようになりました」-単なる構文上の問題ではなく、最適化の利点もある。
kfsone 2014年

126
  • クラスの継承を防ぐためです。ウィキペディアから:

    C ++ 11には、クラスからの継承を防止する機能、または単に派生クラスのメソッドのオーバーライドを防止する機能も追加されています。これは、特別な識別子finalを使用して行われます。例えば:

    struct Base1 final { };
    
    struct Derived1 : Base1 { }; // ill-formed because the class Base1 
                                 // has been marked final
  • また、派生クラスでオーバーライドされないように仮想関数をマークするためにも使用されます。

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

ウィキペディアはさらに興味深い点を指摘しています:

overridefinal言語キーワードでもないことに注意してください。それらは技術的には識別子です。それらは、これらの特定のコンテキストで使用された場合にのみ特別な意味を持ちます。他の場所では、それらは有効な識別子になることができます。

つまり、以下が許可されます。

int const final = 0;     // ok
int const override = 1;  // ok

1
感謝しますが、私の質問はメソッドでの「最終」の使用に関するものであることを言及するのを忘れていました
lezebulon

@lezebulon :-)「関数の C ++ 11の "final"キーワードの目的は何ですか?」(私の強調)
アーロン・マクデイド

編集しましたか?「x分前にlezebulonによって編集されました」というメッセージは表示されません。どうしてこうなりました?送信後、すぐに編集したのでしょうか?
アーロンマクデイド

5
@Aaron:投稿後5分以内に行われた編集は、変更履歴に反映されません。
ildjarn 2012年

@Nawaz:なぜキーワードではなく指定子ではないのですか?それは互換性の理由によるものですか?それは、C ++ 11の前の既存のコードが他の目的でfinal&overrideを使用する可能性があるということですか?
デストラクタ2015年

45

「最終」では、コンパイラの最適化で間接呼び出しをバイパスすることもできます。

class IAbstract
{
public:
  virtual void DoSomething() = 0;
};

class CDerived : public IAbstract
{
  void DoSomething() final { m_x = 1 ; }

  void Blah( void ) { DoSomething(); }

};

"final"を使用すると、コンパイラーはCDerived::DoSomething()内から直接Blah()、またはインラインで呼び出すこともできます。オーバーライドされた派生クラス内で呼び出される可能性があるBlah()ため、それなしでは内部で間接呼び出しを生成する必要Blah()がありますDoSomething()


29

「最終」のセマンティックな側面に追加するものはありません。

しかし、私はクリス・グリーンのコメントに付け加えたいと思います。「最終」はそれほど遠くない将来、非常に重要なコンパイラ最適化手法になるかもしれないということです。彼が言及した単純なケースだけでなく、「final」で「閉じる」ことができるより複雑な実世界のクラス階層の場合も、コンパイラは通常のvtableアプローチよりも効率的なディスパッチコードを生成できます。

vtableの主な欠点の1つは、そのような仮想オブジェクト(通常のIntel CPUで64ビットを想定)の場合、ポインターだけでキャッシュラインの25%(64バイトのうち8バイト)を消費することです。私が書くのが好きな種類のアプリケーションでは、これはひどく痛いです。(そして私の経験から、それは純粋なパフォーマンスの観点から、つまりCプログラマーによるC ++に対する第1の議論です。)

C ++ではそれほど珍しいことではない、極端なパフォーマンスを必要とするアプリケーションでは、これは実際に素晴らしいものになり、Cスタイルまたは奇妙なテンプレートジャグリングでこの問題を手動で回避する必要はありません。

この手法は、非仮想化と呼ばれます。覚えておく価値のある用語。:-)

Andrei Alexandrescuによる最近の素晴らしいスピーチで、今日のそのような状況をどのように回避できるか、また「最終」が同様のケースを将来「自動的に」解決する一部になるかもしれない(リスナーと話し合い)

http://channel9.msdn.com/Events/GoingNative/2013/Writing-Quick-Code-in-Cpp-Quickly


23
8は64の25%ですか?
ildjarn 2015年

6
今それらを利用するコンパイラを知っている人はいますか?
Vincent Fourmond 2017

私が言いたいのと同じこと。
crazii

8

finalは非仮想関数には適用できません。

error: only virtual member functions can be marked 'final'

非仮想メソッドを「最終」としてマークできるようにしても、それほど意味がありません。与えられた

struct A { void foo(); };
struct B : public A { void foo(); };
A * a = new B;
a -> foo(); // this will call A :: foo anyway, regardless of whether there is a B::foo

a->foo()常にを呼び出しますA::foo

ただし、A :: fooがのvirtual場合、B :: fooはそれをオーバーライドします。これは望ましくない場合があるため、仮想関数をfinalにすることは理にかなっています。

しかし問題は、なぜ仮想関数でfinalを許可するです。深い階層がある場合:

struct A            { virtual void foo(); };
struct B : public A { virtual void foo(); };
struct C : public B { virtual void foo() final; };
struct D : public C { /* cannot override foo */ };

次に、finalは、どれだけのオーバーライドを実行できるかについて「フロア」を設定します。他のクラスはAとBを拡張してそれらをオーバーライドできますがfoo、クラスがCを拡張する場合は許可されません。

そのため、「トップレベル」のfooを作成することはおそらく意味finalがありませんが、それよりも下に置くことは意味があります。

(ただし、finalとoverrideという単語を非仮想メンバーに拡張する余地はあると思います。ただし、意味は異なる場合があります。)


例をありがとう、それは私が確信していなかったものです。しかし、それでも:最終的な(そして仮想的な)関数を持つポイントは何ですか?それがオーバーライドすることはできませんので、基本的には、関数が仮想であるという事実を使用することはできないだろう
lezebulon

@lezebulon、質問を編集しました。しかし、それから私はDanOの答えに気づきました-それは私が言おうとしていたことの良い明確な答えです。
アーロンマクデイド

私は専門家ではありませんが、トップレベルの関数を作成することが理にかなっていると感じることがありますfinal。たとえば、すべてShapeのを必要なことがわかっている場合foo()、派生形状を変更しないことを事前に定義して明確にします。または、私は間違っていますか?その場合に採用するより良いパターンがありますか?編集:ああ、おそらくその場合には、foo() virtual最初からトップレベルにするべきではないからでしょうか?しかし、それでもまだ、正しく(ポリモーフィック)を介して呼び出された場合でも、非表示にすることができますShape*...
アンドリュー・チョン

8

私が気に入っている「final」キーワードの使用例は次のとおりです。

// This pure abstract interface creates a way
// for unit test suites to stub-out Foo objects
class FooInterface
{
public:
   virtual void DoSomething() = 0;
private:
   virtual void DoSomethingImpl() = 0;
};

// Implement Non-Virtual Interface Pattern in FooBase using final
// (Alternatively implement the Template Pattern in FooBase using final)
class FooBase : public FooInterface
{
public:
    virtual void DoSomething() final { DoFirst(); DoSomethingImpl(); DoLast(); }
private:
    virtual void DoSomethingImpl() { /* left for derived classes to customize */ }
    void DoFirst(); // no derived customization allowed here
    void DoLast(); // no derived customization allowed here either
};

// Feel secure knowing that unit test suites can stub you out at the FooInterface level
// if necessary
// Feel doubly secure knowing that your children cannot violate your Template Pattern
// When DoSomething is called from a FooBase * you know without a doubt that
// DoFirst will execute before DoSomethingImpl, and DoLast will execute after.
class FooDerived : public FooBase
{
private:
    virtual void DoSomethingImpl() {/* customize DoSomething at this location */}
};

1
はい、これは基本的にテンプレートメソッドパターンの例です。また、C ++ 11以前は、JavaのようにC ++に「final」などの言語機能があることを望んでいたのは、常にTMPでした。
カイテン2017年

6

final 関数がオーバーライドされないように明示的な意図を追加します。これに違反すると、コンパイラエラーが発生します。

struct A {
    virtual int foo(); // #1
};
struct B : A {
    int foo();
};

現状では、コードはコンパイルされ、B::fooオーバーライドされますA::fooB::fooちなみに、仮想です。ただし、#1をに変更するvirtual int foo() finalと、これはコンパイラエラーであり、オーバーライドできません。A::foo、派生クラスでこれ以上する。

これは、新しい階層を「再開」することを許可しないことに注意してください。つまりB::foo、新しい仮想階層の先頭に独立して存在できる、無関係な新しい関数を作成する方法はありません。関数がfinalになると、派生クラスで再び宣言することはできません。


5

最後のキーワードを使用すると、仮想メソッドを宣言し、それをN回オーバーライドして、「これはオーバーライドできなくなります」と要求できます。これは、派生クラスの使用を制限するのに役立ちます。つまり、「スーパークラスでこれをオーバーライドできることはわかっていますが、私から派生したい場合はできません!」と言うことができます。

struct Foo
{
   virtual void DoStuff();
}

struct Bar : public Foo
{
   void DoStuff() final;
}

struct Babar : public Bar
{
   void DoStuff(); // error!
}

他のポスターが指摘したように、それは非仮想関数には適用できません。

finalキーワードの1つの目的は、メソッドの誤ったオーバーライドを防ぐことです。私の例では、DoStuff()がヘルパー関数であった可能性があり、派生クラスは、正しい動作を得るために名前を変更する必要があるだけです。finalがないと、テストするまでエラーは発見されません。


1

C ++の最後のキーワードは、関数に追加されると、基本クラスによってオーバーライドされるのを防ぎます。また、クラスに追加されると、任意の型の継承が妨げられます。最終指定子の使用を示す次の例を検討してください。このプログラムはコンパイルに失敗します。

#include <iostream>
using namespace std;

class Base
{
  public:
  virtual void myfun() final
  {
    cout << "myfun() in Base";
  }
};
class Derived : public Base
{
  void myfun()
  {
    cout << "myfun() in Derived\n";
  }
};

int main()
{
  Derived d;
  Base &b = d;
  b.myfun();
  return 0;
}

また:

#include <iostream>
class Base final
{
};

class Derived : public Base
{
};

int main()
{
  Derived d;
  return 0;
}

0

マリオ・クネゾビッチの回答の補足:

class IA
{
public:
  virtual int getNum() const = 0;
};

class BaseA : public IA
{
public:
 inline virtual int getNum() const final {return ...};
};

class ImplA : public BaseA {...};

IA* pa = ...;
...
ImplA* impla = static_cast<ImplA*>(pa);

//the following line should cause compiler to use the inlined function BaseA::getNum(), 
//instead of dynamic binding (via vtable or something).
//any class/subclass of BaseA will benefit from it

int n = impla->getNum();

上記のコードは理論を示していますが、実際のコンパイラでは実際にはテストされていません。誰かが逆アセンブルされた出力を貼り付けてくれるととても感謝します。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.