関数のC++ 11のfinal
キーワードの目的は何ですか?私はそれが派生クラスによる関数のオーバーライドを防ぐことを理解していますが、その場合、final
関数を非仮想として宣言するだけでは不十分ですか?私がここで見逃している別のものがありますか?
既にコメントで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!
};
クラスが継承されるのを防ぐためです。 From Wikipedia :
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 };
ウィキペディアはさらに 興味深い点 :
override
もfinal
も言語キーワードではないことに注意してください。それらは技術的には識別子です。 これらの特定のコンテキストで使用された場合にのみ特別な意味を獲得します。 他の場所では、有効な識別子になります。
つまり、次のことが許可されます。
int const final = 0; // ok
int const override = 1; // ok
「final」を使用すると、コンパイラーの最適化により、間接呼び出しをバイパスできます。
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()
内で間接呼び出しを生成する必要があります。
「最終」のセマンティックな側面に追加するものはありません。
しかし、「最終」はそれほど遠くない将来に非常に重要なコンパイラ最適化手法になる可能性があるというchris greenのコメントに追加したいと思います。彼が言及した単純な場合だけでなく、「最終」で「閉じる」ことができるより複雑な実世界のクラス階層でも、コンパイラは通常のvtableアプローチよりも効率的なディスパッチコードを生成できます。
Vtableの主な欠点の1つは、そのような仮想オブジェクト(一般的なIntel CPUで64ビットを想定)の場合、ポインターだけでキャッシュラインの25%(64バイトの8)を消費することです。私が書くのが好きな種類のアプリケーションでは、これは非常にひどく痛いです。 (そして、私の経験から、純粋なパフォーマンスの観点から、つまりCプログラマーによるC++に対する第1の議論です。)
極端なパフォーマンスを必要とするアプリケーション(C++ではそれほど珍しくありません)では、Cスタイルや奇妙なテンプレートジャグリングでこの問題を手動で回避する必要がないため、これは本当に驚くべきものになる可能性があります。
この手法は、Devirtualizationとして知られています。覚えておく価値のある用語。 :-)
Andrei Alexandrescuによる最近の素晴らしいスピーチがあります。これは、今日のそのような状況を回避する方法と、同様のケースを将来「自動的に」解決するのに「最終」がどのように役立つかを非常によく説明しています(リスナーと話し合いました):
http://channel9.msdn.com/Events/GoingNative/2013/Writing-Quick-Code-in-Cpp-Quickly
私が気に入っている「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 */}
};
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はそれをオーバーライドします。これは望ましくない場合があるため、仮想関数を最終的にすることは理にかなっています。
ただし、問題は、why仮想関数で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という言葉を拡張して、非仮想メンバーにオーバーライドする余地があると思います。ただし、意味は異なります。)
final
は、関数がオーバーライドされないようにする明示的な意図を追加します。これに違反すると、コンパイラエラーが発生します。
struct A {
virtual int foo(); // #1
};
struct B : A {
int foo();
};
コードが立つと、コンパイルされ、B::foo
がA::foo
をオーバーライドします。ちなみに、B::foo
も仮想です。ただし、#1をvirtual int foo() final
に変更した場合、これはコンパイラエラーであり、派生クラスでこれ以上A::foo
をオーバーライドすることはできません。
これにより、新しい階層を「再オープン」できないことに注意してください。つまり、B::foo
を、新しい仮想階層の先頭に独立して存在できる新しい無関係な関数にする方法はありません。関数がfinalになると、派生クラスで再び宣言することはできません。
Finalキーワードを使用すると、仮想メソッドを宣言し、N回オーバーライドし、「これはオーバーライドできなくなります」と指定できます。派生クラスの使用を制限するのに役立ちます。したがって、「私のスーパークラスでこれをオーバーライドできることはわかっていますが、派生したい場合はできません!」と言うことができます。
struct Foo
{
virtual void DoStuff();
}
struct Bar : public Foo
{
void DoStuff() final;
}
struct Babar : public Bar
{
void DoStuff(); // error!
}
他のポスターが指摘したように、非仮想機能には適用できません。
Finalキーワードの目的の1つは、メソッドを誤ってオーバーライドしないようにすることです。私の例では、DoStuff()は、正しい動作を得るために派生クラスの名前を変更する必要があるヘルパー関数であった可能性があります。最終版がないと、テストするまでエラーは発見されません。
C++のfinalキーワードを関数に追加すると、基本クラスによってオーバーライドされなくなります。また、クラスに追加されると、あらゆるタイプの継承が防止されます。最終指定子の使用を示す次の例を考えてみましょう。このプログラムはコンパイルに失敗します。
#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;
}
マリオ・ネゾビッチの答えの補足:
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();
上記のコードは理論を示していますが、実際のコンパイラでは実際にテストされていません。誰かが逆アセンブルされた出力を貼り付けてくれれば幸いです。