158 votes

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

Quel est le but du mot-clé final en C++11 pour les fonctions? Je comprends qu'il empêche le remplacement de fonction par les classes dérivées, mais si tel est le cas, ne suffit-il pas de déclarer vos fonctions final comme non virtuelles? Y a-t-il autre chose que je rate ici?

31 votes

"n'est-il pas suffisant de déclarer comme non virtuelles vos fonctions « final » Non, les fonctions de remplacement sont implicitement virtuelles que vous utilisiez le mot-clé virtuel ou non.

15 votes

@ildjarn ce n'est pas vrai si elles n'ont pas été déclarées comme virtuelles 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.

10 votes

@DanO je pense que vous ne pouvez pas remplacer mais vous pouvez "cacher" une méthode de cette façon.. ce qui mène à de nombreux problèmes car les gens ne veulent pas cacher les méthodes.

142voto

Ce que vous manquez, comme idljarn l'a déjà mentionné dans un commentaire, c'est que si vous remplacez une fonction d'une classe de base, alors vous ne pouvez pas la marquer comme non-virtuelle :

struct base {
   virtual void f();
};
struct derived : base {
   void f() final;       // virtuelle car elle remplace base::f
};
struct mostderived : derived {
   //void f();           // erreur : impossible de remplacer !
};

0 votes

Merci! c'est le point que je manquais: c'est-à-dire que même vos classes "feuille" 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-mêmes

8 votes

@lezebulon : Vos classes de feuilles n'ont pas besoin de marquer une fonction comme virtuelle si la super classe l'a déclarée comme virtuelle.

5 votes

Les méthodes dans les classes dérivées sont implicitement virtuelles si elles sont virtuelles dans la classe de base. Je pense que les compilateurs devraient avertir si ce 'virtual' implicite est manquant.

135voto

Nawaz Points 148870
  • Il sert à empêcher une classe d'être héritée. De Wikipedia :

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

    struct Base1 final { };
    
    struct Derived1 : Base1 { }; // incorrect car la classe Base1 
                                 // a été marquée comme final
  • Il est également utilisé pour marquer une fonction virtuelle afin de l'empêcher d'être redéfinie dans les classes dérivées :

    struct Base2 {
        virtual void f() final;
    };
    
    struct Derived2 : Base2 {
        void f(); // incorrect car la fonction virtuelle Base2::f a 
                  // été marquée comme final
    };

Wikipedia fait en outre une observation intéressante:

Notez que ni override ni final ne sont des mots-clés du langage. Ce sont techniquement des identifiants; ils acquièrent un sens spécial uniquement lorsqu'ils sont utilisés dans ces contextes spécifiques. Dans tout autre contexte, ils peuvent être des identifiants valides.

Cela signifie que ce qui suit est autorisé :

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

1 votes

Merci, mais j'ai oublié de mentionner que ma question concernait l'utilisation de "final" avec les méthodes

0 votes

Vous l'avez bien mentionné @lezebulon :-) "quel est le but du mot-clé "final" en C++11 pour les fonctions". (mon emphase)

0 votes

Vous l'avez modifié ? Je ne vois aucun message qui dit "édité il y a x minutes par lezebulon". Comment est-ce arrivé ? Peut-être l'avez-vous modifié très rapidement après l'avoir soumis ?

50voto

chris green Points 81

"final" permet également à un compilateur d'optimiser en contournant 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 depuis Blah(), ou même l'incorporer en ligne. Sans cela, il doit générer un appel indirect à l'intérieur de Blah() car Blah() pourrait être appelé dans une classe dérivée qui a remplacé DoSomething().

33voto

Mario Knezović Points 71

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

Cependant, j'aimerais ajouter au commentaire de Chris Green que "final" pourrait devenir une technique d'optimisation du compilateur très importante dans un avenir proche. 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 "closes" par "final", permettant ainsi aux compilateurs de générer un code de dispatching plus efficace que l'approche habituelle de vtable.

Un inconvénient clé des vtables est que, pour tout objet virtuel (en supposant 64 bits sur un CPU Intel typique), le pointeur seul occupe 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 mon expérience, c'est l'argument n ° 1 contre le C++ du point de vue de la pure performance, c'est-à-dire par les programmeurs en C.)

Dans les applications qui nécessitent des performances extrêmes, ce qui n'est pas si rare pour le C++, cela pourrait effectivement devenir impressionnant, ne nécessitant pas de contourner ce problème manuellement de style C ou d'acrobaties de modèles bizarres.

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

Il y a un discours récent formidable d'Andrei Alexandrescu qui explique plutôt bien comment vous pouvez contourner de telles situations aujourd'hui et comment "final" pourrait faire partie de la résolution de problèmes similaires "automatiquement" à l'avenir (discuté avec les auditeurs) :

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

10 votes

Quelqu'un connaît un compilateur qui les utilise maintenant ?

0 votes

Même chose que je veux dire.

0 votes

@VincentFourmond @crazii J'ai trouvé une autre réponse sur SO qui confirme que oui, GCC, MSVC et Clang utilisent final pour les optimisations de dévirtualisation. Consultez les commentaires pour les versions spécifiques des compilateurs qui ont ajouté cette fonctionnalité, et même des liens vers le code du compilateur.

11voto

Aaron McDaid Points 7761

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

error: seules les fonctions membres virtuelles peuvent être marquées comme 'final'

Il ne serait pas très significatif de pouvoir marquer une méthode non virtuelle comme 'final'. Étant donné

struct A { void foo(); };
struct B : public A { void foo(); };
A * a = new B;
a -> foo(); // cela appellera de toute façon A :: foo, indépendamment de l'existence d'un B::foo

a->foo() appellera toujours A::foo.

Mais, si A::foo était virtual, alors B::foo le remplacerait. Cela pourrait être indésirable, 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 { /* ne peut pas remplacer foo */ };

Alors le final met un 'plafond' sur le niveau de substitution possible. D'autres classes peuvent étendre A et B et remplacer leur foo, mais si une classe étend C, alors cela n'est pas autorisé.

Il ne semble donc probablement pas logique de rendre la fonction 'de niveau supérieur' final, mais cela pourrait avoir du sens plus bas dans la hiérarchie.

(Je pense cependant qu'il y a de la place pour étendre les mots final et override aux membres non virtuels. Ils auraient cepend differente signification.)

0 votes

Merci pour l'exemple, c'est quelque chose dont j'étais incertain. Mais quand même : quel est l'intérêt d'avoir une fonction finale (et virtuelle)? Fondamentalement, vous ne pourriez jamais utiliser le fait que la fonction est virtuelle car elle ne peut pas être remplacée.

0 votes

@lezebulon, J'ai modifié 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.

0 votes

Je ne suis pas un expert, mais je pense que parfois il peut être judicieux de rendre une fonction de premier niveau final. Par exemple, si vous savez que vous voulez que toutes les formes Shape fassent foo() - quelque chose de prédéfini et de définitif que aucune forme dérivée ne devrait modifier. Ou, ai-je tort et y a-t-il un meilleur motif à utiliser dans ce cas? MODIFIER: Oh, peut-être parce que dans ce cas, il ne faut tout simplement pas rendre la fonction de premier niveau foo() virtuelle dès le départ? Mais quand même, elle peut être masquée, même si elle est appelée correctement (par polymorphisme) via Shape*...

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X