web-dev-qa-db-fra.com

Quel est le but du mot clé "final" dans C++ 11 pour les fonctions?

Quel est le but du mot clé final en C++ 11 pour les fonctions? Je comprends que cela évite de surcharger les fonctions par des classes dérivées, mais si tel est le cas, ne suffit-il pas de déclarer vos fonctions final non virtuelles? Y a-t-il autre chose qui me manque ici?

115
lezebulon

Ce qui vous manque, car idljarn a déjà été mentionné dans un commentaire, c'est que si vous êtes surchargeant une fonction d'une classe de base, vous ne pouvez pas la marquer comme non virtuelle:

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!
};
107
  • C'est pour empêcher une classe d'être héritée. De Wikipedia :

    C++ 11 ajoute également la possibilité d'empêcher l'héritage des classes ou simplement d'empêcher les méthodes de substitution dans les classes dérivées. Ceci est fait avec l'identifiant spécial final. Par exemple:

    struct Base1 final { };
    
    struct Derived1 : Base1 { }; // ill-formed because the class Base1 
                                 // has been marked final
    
  • Il est également utilisé pour marquer une fonction virtuelle afin d'éviter son remplacement dans les classes dérivées:

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

Wikipedia fait en outre un point intéressant :

Notez que ni override ni final ne sont des mots-clés de la langue. Ce sont des identifiants techniques; ils acquièrent une signification particulière uniquement lorsqu'ils sont utilisés dans ces contextes spécifiques. Dans n'importe quel autre emplacement, ils peuvent être des identificateurs valides.

Cela signifie que les éléments suivants sont autorisés:

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

"final" permet également à une optimisation du compilateur de contourner l'appel indirect:

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

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

  void Blah( void ) { DoSomething(); }

};

avec "final", le compilateur peut appeler CDerived::DoSomething() directement à partir de Blah(), ou même en ligne. Sans lui, il doit générer un appel indirect à l'intérieur de Blah() car Blah() pourrait être appelé à l'intérieur d'une classe dérivée ayant remplacé DoSomething().

39
chris green

Rien à ajouter aux aspects sémantiques de "final".

Mais je voudrais ajouter au commentaire de chris green que "final" pourrait devenir une très importante technique d'optimisation du compilateur dans un avenir pas si lointain. Non seulement dans le cas simple qu'il a mentionné, mais aussi pour les hiérarchies de classe plus complexes du monde réel qui peuvent être "fermées" par "finale", permettant ainsi aux compilateurs de générer un code de dispatching plus efficace qu'avec l'approche habituelle vtable. 

Un inconvénient majeur de vtables est que, pour tout objet virtuel de ce type (en supposant que 64 bits se trouvent sur un processeur Intel typique), le pointeur à lui seul absorbe 25% (8 sur 64 octets) d'une ligne de cache. Dans le genre d'applications que j'aime écrire, cela fait très mal. (Et de par mon expérience, c’est l’argument n ° 1 contre C++ du point de vue des performances puristes, c’est-à-dire des programmeurs C).

Dans les applications qui nécessitent des performances extrêmes, ce qui n’est pas si inhabituel pour C++, cela peut en effet devenir génial, car il n’est pas nécessaire de résoudre ce problème manuellement en style C ou en jonglant avec des modèles étranges.

Cette technique s'appelle dévirtualisation. Un terme à retenir. :-)

Andrei Alexandrescu a récemment prononcé un discours magnifique qui explique assez bien comment on peut contourner de telles situations aujourd'hui et comment "définitif" pourrait contribuer à résoudre "automatiquement" des cas similaires à l'avenir (discuté avec les auditeurs):

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

27
Mario Knezović

Voici un exemple d'utilisation du mot-clé "final" que je suis fan:

// 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 ne peut pas être appliqué à des fonctions non virtuelles.

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

Il ne serait pas très utile de pouvoir marquer une méthode non virtuelle comme "finale". Donné

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() appellera toujours A::foo.

Mais, si A :: foo était virtual, alors B :: foo le remplacerait. Cela peut être indésirable et il serait donc logique de rendre la fonction virtuelle finale.

La question est cependant, pourquoi autoriser final sur les fonctions virtuelles. Si vous avez une hiérarchie profonde:

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

Ensuite, la final définit «combien» peut être dépassé. D'autres classes peuvent étendre A et B et remplacer leur foo, mais si une classe étend C, cela n'est pas autorisé.

Donc, il n’a probablement aucun sens à faire le "niveau supérieur" foo final, mais cela pourrait avoir un sens plus bas.

(Je pense cependant qu'il est possible d'étendre les mots final et de remplacer les membres non virtuels. Ils auraient toutefois un sens différent.)

7
Aaron McDaid

final ajoute une intention explicite de ne pas substituer votre fonction, et provoquera une erreur de compilation si celle-ci est violée:

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

En l'état, le code est compilé et B::foo remplace A::foo. B::foo est aussi virtuel, soit dit en passant. Cependant, si nous remplaçons # 1 par virtual int foo() final, il s’agit alors d’une erreur du compilateur et nous ne sommes pas autorisés à remplacer A::foo dans les classes dérivées.

Notez que cela ne nous permet pas de "rouvrir" une nouvelle hiérarchie, c’est-à-dire qu’il n’ya aucun moyen de faire de B::foo une nouvelle fonction non liée pouvant être indépendante à la tête d’une nouvelle hiérarchie virtuelle. Une fois qu'une fonction est finale, elle ne peut plus être déclarée dans aucune classe dérivée.

4
Kerrek SB

Le mot-clé final vous permet de déclarer une méthode virtuelle, de la remplacer N fois, puis d'indiquer que "cela ne peut plus être remplacé". Cela serait utile pour restreindre l’utilisation de votre classe dérivée, afin que vous puissiez dire "Je sais que ma super classe vous permet de remplacer cela, mais si vous voulez dériver de moi, vous ne pouvez pas!".

struct Foo
{
   virtual void DoStuff();
}

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

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

Comme d'autres affiches l'ont souligné, cela ne peut pas être appliqué à des fonctions non virtuelles.

L'un des objectifs du mot-clé final est d'empêcher le contournement accidentel d'une méthode. Dans mon exemple, DoStuff () est peut-être une fonction d'assistance que la classe dérivée doit simplement renommer pour obtenir un comportement correct. Sans final, l'erreur ne serait découverte qu'après les tests.

4
Dan O

Le mot-clé final en C++, une fois ajouté à une fonction, empêche sa substitution par une classe de base . Ce programme échoue lors de la compilation.

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

Également:

#include <iostream>
class Base final
{
};

class Derived : public Base
{
};

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

Supplément à la réponse de 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();

Le code ci-dessus montre la théorie, mais pas réellement testé sur de vrais compilateurs. Très apprécié si quelqu'un colle une sortie désassemblée.

0
crazii