webentwicklung-frage-antwort-db.com.de

Welchen Zweck hat das Schlüsselwort "final" in C++ 11 für Funktionen?

Welchen Zweck hat das Schlüsselwort final in C++ 11 für Funktionen? Ich verstehe, dass das Überschreiben von Funktionen durch abgeleitete Klassen verhindert wird, aber wenn dies der Fall ist, reicht es dann nicht aus, Ihre final-Funktionen als nicht virtuell zu deklarieren. Gibt es noch etwas, was mir fehlt?

115
lezebulon

Was Ihnen fehlt, da idljarn bereits in einem Kommentar erwähnt, ist, dass, wenn Sie eine Funktion einer Basisklasse überschreiben, sie möglicherweise nicht als nicht-virtuell markiert werden kann:

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!
};
  • Es soll verhindern, dass eine Klasse vererbt wird. Aus Wikipedia :

    C++ 11 bietet außerdem die Möglichkeit, das Erben von Klassen oder das Überschreiben von Methoden in abgeleiteten Klassen zu verhindern. Dies geschieht mit dem speziellen Bezeichner final. Zum Beispiel:

    struct Base1 final { };
    
    struct Derived1 : Base1 { }; // ill-formed because the class Base1 
                                 // has been marked final
    
  • Es wird auch verwendet, um eine virtuelle Funktion zu markieren, um zu verhindern, dass sie in den abgeleiteten Klassen überschrieben wird:

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

Wikipedia macht weiter einen interessanten Punkt :

Beachten Sie, dass weder override noch final Sprachschlüsselwörter sind. Sie sind technisch identifizierbar. sie erhalten nur eine besondere Bedeutung, wenn sie in diesen spezifischen Kontexten verwendet werden. An anderen Orten können sie gültige Bezeichner sein.

Das heißt, Folgendes ist erlaubt:

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

"final" ermöglicht auch eine Compiler-Optimierung, um den indirekten Aufruf zu umgehen:

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

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

  void Blah( void ) { DoSomething(); }

};

mit "final" kann der Compiler CDerived::DoSomething() direkt aus Blah() oder sogar inline aufrufen. Andernfalls muss ein indirekter Aufruf innerhalb von Blah() generiert werden, da Blah() innerhalb einer abgeleiteten Klasse aufgerufen werden könnte, die DoSomething() überschrieben hat.

39
chris green

Nichts zu den semantischen Aspekten von "final" hinzuzufügen.

Ich möchte jedoch zu chris green's Kommentar hinzufügen, dass "final" in sehr ferner Zukunft zu einer sehr wichtigen compiler-Optimierungstechnik werden könnte. Nicht nur im einfachen Fall, den er erwähnte, sondern auch für komplexere Hierarchien der realen Klassen, die von "final" "geschlossen" werden können, sodass Compiler effizienteren Dispatching-Code erzeugen können als mit dem üblichen vtable-Ansatz. 

Ein entscheidender Nachteil von vtables besteht darin, dass der Zeiger für ein solches virtuelles Objekt (bei einer typischen Intel-CPU mit 64 Bit) allein 25% (8 von 64 Byte) einer Cache-Zeile verbraucht. In der Art von Anwendungen, die ich gerne schreibe, tut dies sehr weh. (Und meiner Erfahrung nach ist dies das reinste Argument gegen C++ aus rein puristischer Sicht, d. H. Von C-Programmierern.)

In Anwendungen, die eine extreme Leistung erfordern, was für C++ nicht so ungewöhnlich ist, kann dies in der Tat erstaunlich werden, da dieses Problem nicht manuell im C-Stil oder mit einem komischen Vorlagen-Jonglieren behoben werden muss.

Diese Technik wird als Devirtualization bezeichnet. Ein Begriff, an den es sich zu erinnern lohnt. :-)

Es gibt eine großartige Rede von Andrei Alexandrescu, die ziemlich gut erklärt, wie Sie solche Situationen heute umgehen können und wie "final" dazu beitragen könnte, ähnliche Fälle "automatisch" in der Zukunft zu lösen (mit Zuhörern besprochen):

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

27
Mario Knezović

Ein Anwendungsfall für das Keyword "final", das mir gefällt, lautet wie folgt:

// 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 */}
};
7
YoungJohn

Final kann nicht auf nicht virtuelle Funktionen angewendet werden.

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

Es wäre nicht sehr sinnvoll, eine nicht virtuelle Methode als "final" kennzeichnen zu können. Gegeben

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() ruft immer A::foo auf.

Wenn A :: foo jedoch virtual wäre, würde B :: foo es überschreiben. Dies ist möglicherweise unerwünscht und daher wäre es sinnvoll, die virtuelle Funktion als endgültig zu definieren.

Die Frage ist jedoch: warum final auf virtuelle Funktionen zulassen. Wenn Sie eine tiefe Hierarchie haben:

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 */ };

Die final gibt dann ein "Floor" an, wie viel überschrieben werden kann. Andere Klassen können A und B erweitern und ihre foo überschreiben, aber wenn eine Klasse C erweitert, ist sie nicht zulässig.

Es macht also wahrscheinlich keinen Sinn, die "oberste Ebene" foo final zu machen, aber es könnte Sinn machen, wenn man sie nach unten zieht.

(Ich denke jedoch, es gibt Raum, um die Worte final und override auf nicht virtuelle Mitglieder auszudehnen. Sie hätten jedoch eine andere Bedeutung.)

7
Aaron McDaid

final fügt die explizite Absicht hinzu, Ihre Funktion nicht außer Kraft zu setzen, und verursacht einen Compiler-Fehler, falls dies verletzt wird:

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

Der Code wird kompiliert und B::foo überschreibt A::foo. B::foo ist übrigens auch virtuell. Wenn wir jedoch # 1 in virtual int foo() final ändern, ist dies ein Compiler-Fehler und wir dürfen A::foo in abgeleiteten Klassen nicht weiter überschreiben.

Beachten Sie, dass wir dadurch keine neue Hierarchie "wieder öffnen" können, d. H. Es gibt keine Möglichkeit, B::foo eine neue, nicht zusammenhängende Funktion zu machen, die unabhängig von einer neuen virtuellen Hierarchie stehen kann. Sobald eine Funktion final ist, kann sie nie mehr in einer abgeleiteten Klasse deklariert werden.

4
Kerrek SB

Mit dem final-Schlüsselwort können Sie eine virtuelle Methode deklarieren, N-mal überschreiben und dann festlegen, dass "diese nicht mehr überschrieben werden kann". Es wäre nützlich, wenn Sie die Verwendung Ihrer abgeleiteten Klasse einschränken möchten, so dass Sie sagen können: "Ich weiß, meine Superklasse lässt Sie dies überschreiben, aber wenn Sie von mir ableiten wollen, können Sie nicht!".

struct Foo
{
   virtual void DoStuff();
}

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

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

Wie andere Poster hervorgehoben haben, kann es nicht auf nicht-virtuelle Funktionen angewendet werden.

Das letzte Schlüsselwort dient dazu, das versehentliche Überschreiben einer Methode zu verhindern. In meinem Beispiel war DoStuff () möglicherweise eine Hilfsfunktion, die die abgeleitete Klasse einfach umbenennen muss, um ein korrektes Verhalten zu erhalten. Ohne final würde der Fehler erst beim Testen entdeckt.

4
Dan O

Wenn das Schlüsselwort in C++ zu einer Funktion hinzugefügt wird, verhindert es, dass es von einer Basisklasse überschrieben wird . Wenn eine Klasse hinzugefügt wird, wird die Vererbung eines beliebigen Typs verhindert. Dieses Programm schlägt beim Kompilieren fehl.

#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;
}

Ebenfalls:

#include <iostream>
class Base final
{
};

class Derived : public Base
{
};

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

Ergänzung zur Antwort von Mario Knezović:

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();

Der obige Code zeigt die Theorie, wurde aber nicht auf echten Compilern getestet. Sehr geschätzt, wenn jemand eine demontierte Ausgabe einfügt.

0
crazii