À quoi sert le mot clé «final» en C ++ 11 pour les fonctions?

143

Quel est le but du finalmot clé en C ++ 11 pour les fonctions? Je comprends que cela empêche le remplacement de fonction par des classes dérivées, mais si tel est le cas, n'est-il pas suffisant de déclarer vos finalfonctions comme non virtuelles ? Y a-t-il autre chose qui me manque ici?

lezebulon
la source
30
" n'est-il pas suffisant de déclarer comme non-virtuelles vos" fonctions "finales " Non, les fonctions de substitution sont implicitement virtuelles que vous utilisiez le virtualmot - clé ou non.
ildjarn
13
@ildjarn ce n'est pas vrai s'ils n'ont pas été déclarés comme virtuels dans la super classe, vous ne pouvez pas dériver d'une classe et transformer une méthode non virtuelle en une méthode virtuelle ..
Dan O
10
@DanO Je pense que vous ne pouvez pas passer outre, mais vous pouvez "cacher" une méthode de cette façon .. ce qui conduit à de nombreux problèmes car les gens ne veulent pas cacher les méthodes.
Alex Kremer
16
@DanO: Si ce n'est pas virtuel dans la super classe, alors ce ne serait pas "écrasant".
ildjarn
2
Encore une fois, « surcharger » a ici une signification spécifique, qui est de donner un comportement polymorphe à une fonction virtuelle. Dans votre exemple, ce funcn'est pas virtuel, il n'y a donc rien à remplacer et donc rien à marquer comme overrideou final.
ildjarn

Réponses:

129

Ce qui vous manque, comme idljarn déjà mentionné dans un commentaire, c'est que si vous surchargez 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!
};
David Rodríguez - Dribeas
la source
Merci! c'est le point qui me manquait: c'est-à-dire que même vos classes "feuilles" doivent marquer leur fonction comme virtuelle même si elles ont l'intention de remplacer des fonctions, et de ne pas être remplacées elles
lezebulon
8
@lezebulon: Vos classes feuilles n'ont pas besoin de marquer une fonction comme virtuelle si la super classe l'a déclarée comme virtuelle.
Dan O
5
Les méthodes des classes feuilles sont implicitement virtuelles si elles sont virtuelles dans la classe de base. Je pense que les compilateurs devraient avertir si ce «virtuel» implicite est absent.
Aaron McDaid du
@AaronMcDaid: Les compilateurs avertissent généralement du code qui, étant correct, peut causer de la confusion ou des erreurs. Je n'ai jamais vu personne surpris par cette caractéristique particulière du langage d'une manière qui pourrait causer un problème, donc je ne sais pas vraiment à quel point cette erreur pourrait être utile. Au contraire, oublier le virtualpeut provoquer des erreurs, et C ++ 11 a ajouté la overridebalise à une fonction qui détectera cette situation et ne parviendra pas à se compiler lorsqu'une fonction censée remplacer se cache
David Rodríguez - dribeas
1
À partir des notes de modification de GCC 4.9: "Nouveau module d'analyse d'héritage de type améliorant la dévirtualisation. La dévirtualisation prend désormais en compte les espaces de noms anonymes et le mot-clé final C ++ 11" - il ne s'agit donc pas seulement de sucre syntaxique, il a également un avantage potentiel d'optimisation.
kfsone
127
  • Il s'agit d'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 le remplacement des méthodes 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 qu'elle ne soit surchargée 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
    };

Wikipédia fait en outre un point intéressant :

Notez que overrideni finalles mots-clés de langue ne le sont. Ce sont des identifiants techniques; ils n'acquièrent une signification particulière que lorsqu'ils sont utilisés dans ces contextes spécifiques .Dans tout autre endroit, ils peuvent être des identifiants valides.

Cela signifie que ce qui suit est autorisé:

int const final = 0;     // ok
int const override = 1;  // ok
Nawaz
la source
1
merci, mais j'ai oublié de mentionner que ma question concernait l'utilisation de "final" avec des méthodes
lezebulon
Vous l'avez mentionné @lezebulon :-) "à quoi sert le mot-clé" final "en C ++ 11 pour les fonctions ". (je souligne)
Aaron McDaid
Vous l'avez édité? Je ne vois aucun message indiquant "édité il y a x minutes par lezebulon". Comment est-ce arrivé? Peut-être que vous l'avez édité très rapidement après l'avoir soumis?
Aaron McDaid du
5
@Aaron: les modifications effectuées dans les cinq minutes suivant la publication ne sont pas reflétées dans l'historique des révisions.
ildjarn
@Nawaz: pourquoi ne sont-ils pas des mots-clés uniquement des spécificateurs? Est-ce pour des raisons de compatibilité signifie qu'il est possible que le code préexistant avant C ++ 11 utilise final et override à d'autres fins?
Destructor
45

"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 de l'intérieur Blah(), voire en ligne. Sans cela, 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 qui a été surchargée DoSomething().

chris vert
la source
29

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

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

L'un des principaux inconvénients des vtables est que pour tout objet virtuel de ce type (en supposant 64 bits sur un processeur Intel typique), le pointeur à lui seul consomme 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 d'après mon expérience, c'est l'argument n ° 1 contre C ++ d'un point de vue puriste des performances, c'est-à-dire par les programmeurs C.)

Dans les applications qui nécessitent des performances extrêmes, ce qui n'est pas si inhabituel pour C ++, cela pourrait en effet devenir génial, ne nécessitant pas de contourner ce problème manuellement en style C ou en jonglant avec des modèles bizarres.

Cette technique est connue sous le nom de dévirtualisation . Un terme à retenir. :-)

Il y a un excellent discours récent d'Andrei Alexandrescu qui explique assez bien comment vous pouvez contourner de telles situations aujourd'hui et comment «final» pourrait faire partie de la résolution de cas similaires «automatiquement» à l'avenir (discuté avec les auditeurs):

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

Mario Knezović
la source
23
8 est 25% de 64?
ildjarn
6
Quelqu'un connaît un compilateur qui utilise ceux-ci maintenant?
Vincent Fourmond
même chose que je veux dire.
crazii
8

Final ne peut pas être appliqué aux fonctions non virtuelles.

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

Il ne serait pas très significatif 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 l'était virtual, alors B :: foo le remplacerait. Cela pourrait être indésirable, et il serait donc logique de rendre la fonction virtuelle définitive.

La question est cependant de savoir pourquoi autoriser les fonctions finales 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, le final«plancher» sur combien de dépassement peut être fait. D'autres classes peuvent étendre A et B et les remplacer foo, mais si une classe étend C, ce n'est pas autorisé.

Donc, cela n'a probablement pas de sens de faire le toto «de premier niveau» final, mais cela pourrait avoir un sens plus bas.

(Je pense cependant qu'il est possible d'étendre les mots final et de priorité aux membres non virtuels. Ils auraient cependant une signification différente.)

Aaron McDaid
la source
merci pour l'exemple, c'est quelque chose dont je n'étais pas sûr. Mais quand même: quel est l'intérêt d'avoir une fonction finale (et virtuelle)? En gros, vous ne pourrez jamais utiliser le fait que la fonction est virtuelle car elle ne peut pas être
remplacée
@lezebulon, j'ai édité ma question. Mais ensuite, j'ai remarqué la réponse de DanO - c'est une bonne réponse claire de ce que j'essayais de dire.
Aaron McDaid du
Je ne suis pas un expert, mais je pense que parfois, il peut être logique de créer une fonction de haut niveau final. Par exemple, si vous savez que vous voulez que tous les Shapes foo()soient, quelque chose de prédéfini et défini qu'aucune forme dérivée ne doit modifier. Ou ai-je tort et il existe un meilleur modèle à utiliser pour ce cas? EDIT: Oh, peut-être parce que dans ce cas, il ne faut tout simplement pas faire le plus haut niveau foo() virtualpour commencer? Mais quand même, il peut être caché, même s'il est appelé correctement (polymorphiquement) via Shape*...
Andrew Cheong
8

Voici un cas d'utilisation du mot-clé `` final '' que j'affectionne:

// 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 */}
};
JeuneJohn
la source
1
Oui, il s'agit essentiellement d'un exemple du modèle de méthode de modèle. Et avant C ++ 11, c'était toujours le TMP qui me faisait souhaiter que C ++ ait une fonctionnalité de langage telle que «final», comme le faisait Java.
Kaitain
6

final ajoute une intention explicite de ne pas avoir votre fonction remplacée, et provoquera une erreur du compilateur si cela est violé:

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

Dans l'état actuel du code, il compile et B::fooremplace A::foo. B::fooest également virtuel, d'ailleurs. Cependant, si nous changeons # 1 en virtual int foo() final, alors c'est une erreur du compilateur, et nous ne sommes pas autorisés à remplacerA::foo davantage 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'y a aucun moyen de créer B::fooune nouvelle fonction indépendante qui peut être indépendamment à la tête d'une nouvelle hiérarchie virtuelle. Une fois qu'une fonction est définitive, elle ne peut plus jamais être déclarée dans une classe dérivée.

Kerrek SB
la source
5

Le mot-clé final vous permet de déclarer une méthode virtuelle, de la remplacer N fois, puis d'exiger que «cela ne puisse 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é, il ne peut pas être appliqué à des fonctions non virtuelles.

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

Dan O
la source
1

Le mot clé final en C ++ lorsqu'il est ajouté à une fonction, l'empêche d'être remplacé par une classe de base. De plus, lorsqu'il est ajouté à une classe, il empêche l'héritage de tout type. Prenons l'exemple suivant qui montre l'utilisation du spécificateur final. 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;
}

Aussi:

#include <iostream>
class Base final
{
};

class Derived : public Base
{
};

int main()
{
  Derived d;
  return 0;
}
Krishna Ganeriwal
la source
0

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émontée.

Crazii
la source