web-dev-qa-db-fra.com

Quel est l'intérêt d'une fonction virtuelle finale?

Wikipedia a l'exemple suivant sur le modificateur final C++ 11:

struct Base2 {
    virtual void f() final;
};

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

Je ne comprends pas l'intérêt d'introduire une fonction virtuelle et de la marquer immédiatement comme finale. Est-ce simplement un mauvais exemple ou y a-t-il autre chose?

45
fredoverflow

final ne sera généralement pas utilisé dans la définition d'une fonction virtuelle par la classe de base. final sera utilisé par une classe dérivée qui redéfinit la fonction afin d'empêcher que d'autres types dérivés ne surchargent la fonction. Étant donné que la fonction de remplacement doit être normalement virtuelle, cela signifierait que n'importe qui pourrait remplacer cette fonction dans un autre type dérivé. final permet de spécifier une fonction qui en remplace une autre mais qui ne peut pas être remplacée elle-même.

Par exemple, si vous concevez une hiérarchie de classes et que vous devez remplacer une fonction, mais que vous ne souhaitez pas autoriser les utilisateurs de la hiérarchie de classes à faire de même, vous pouvez marquer les fonctions comme définitives dans vos classes dérivées. 

51
bames53

Cela ne me semble pas du tout utile. Je pense que ce n'était qu'un exemple pour illustrer la syntaxe.

Une utilisation possible est si vous ne voulez pas que f soit vraiment remplaçable, mais vous voulez toujours générer une table virtuelle, mais c'est quand même une façon horrible de faire les choses.

8
Antimony

Pour qu'une fonction soit étiquetée final, elle doit être virtual, c'est-à-dire en C++ 11 §10.3 par. 2:

[...] Par commodité, nous disons que toute fonction virtuelle se substitue à elle-même.

et para 4:

Si une fonction virtuelle f dans une classe B est marquée avec le virt-specifier final et dans une classe D dérivée de B une fonction D :: f remplace B :: f, le programme est mal formé. [...]

c'est-à-dire que final doit uniquement être utilisé avec des fonctions virtuelles (ou avec des classes pour bloquer l'héritage). Ainsi, l'exemple nécessite l'utilisation de virtual pour qu'il s'agisse d'un code C++ valide.

EDIT: Pour être tout à fait clair: le "point" interrogé sur les raisons pour lesquelles le virtuel est même utilisé. La raison fondamentale pour laquelle il est utilisé est (i) parce que le code ne serait pas compilé autrement, et (ii) pourquoi rendre l'exemple plus compliqué en utilisant plus de classes quand on en a assez? Ainsi, exactement une classe avec une fonction finale virtuelle est utilisée comme exemple.

7
Paul Preney

Je ne comprends pas l'intérêt d'introduire une fonction virtuelle et de la marquer immédiatement comme finale.

Le but de cet exemple est d’illustrer comment final fonctionne, et c’est exactement ce que nous faisons.

Un but {pratique} _ pourrait être de voir comment une table virtuelle influence la taille d'une classe.

struct Base2 {
    virtual void f() final;
};
struct Base1 {
};

assert(sizeof(Base2) != sizeof(Base1)); //probably

Base2 peut simplement être utilisé pour tester les spécificités de la plate-forme, et il est inutile de surcharger f() car il est là uniquement à des fins de test, il est donc marqué final. Bien sûr, si vous faites cela, il y a quelque chose qui cloche dans la conception. Personnellement, je ne créerais pas de classe avec une fonction virtual juste pour vérifier la taille de la vfptr.

4
Luchian Grigore

Ajout aux réponses de Nice ci-dessus - Voici une application bien connue de final (très inspirée de Java). Supposons que nous définissions une fonction wait () dans une classe de base et que nous souhaitons un seul implémentation de wait () dans tous ses descendants. Dans ce cas, nous pouvons déclarer wait () comme final. 

Par exemple: 

class Base { 
   public: 
       virtual void wait() final { cout << "I m inside Base::wait()" << endl; }
       void wait_non_final() { cout << "I m inside Base::wait_non_final()" << endl; }
}; 

et voici la définition de la classe dérivée: 

class Derived : public Base {
      public: 
        // assume programmer had no idea there is a function Base::wait() 

        // error: wait is final
        void wait() { cout << "I am inside Derived::wait() \n"; } 
        // that's ok    
        void wait_non_final() { cout << "I am inside Derived::wait_non_final(); }

} 

Ce serait inutile (et non correct) si wait () était une fonction virtuelle pure. Dans ce cas: le compilateur vous demandera de définir wait () dans la classe dérivée. Si vous le faites, cela vous donnera une erreur car wait () est final. 

Pourquoi une fonction finale devrait-elle être virtuelle? (ce qui prête également à confusion) Parce que (imo) 1) le concept de final est très proche du concept de fonctions virtuelles [les fonctions virtuelles ont de nombreuses implémentations - les fonctions finales ont une seule implémentation], 2) il est facile de les mettre en œuvre effet final en utilisant vtables. 

2
AJed

Lors du refactoring du code hérité (par exemple, la suppression d'une méthode virtuelle d'une classe mère), cela est utile pour garantir qu'aucune classe enfant n'utilise cette fonction virtuelle.

// Removing foo method is not impacting any child class => this compiles
struct NoImpact { virtual void foo() final {} };
struct OK : NoImpact {};

// Removing foo method is impacting a child class => NOK class does not compile
struct ImpactChildClass { virtual void foo() final {} };
struct NOK : ImpactChildClass { void foo() {} };

int main() {}
2
Richard Dally

Voici pourquoi vous pouvez choisir de déclarer une fonction à la fois virtual et final dans une classe de base:

class A {
    void f();
};

class B : public A {
    void f(); // Compiles fine!
};

class C {
    virtual void f() final;
};

class D : public C {
    void f(); // Generates error.
};

Une fonction marquée final doit être aussi virtual. Le marquage d'une fonction final vous empêche de déclarer une fonction du même nom et de la même signature dans une classe dérivée.

1
Omnifarious

J'ai trouvé un autre cas où la fonction virtuelle est utile pour être déclarée comme finale. Ce cas fait partie de Liste des avertissements SonarQube . La description de l'avertissement dit:

L'appel d'une fonction membre pouvant être remplacée à partir d'un constructeur ou d'un destructeur peut entraîner un comportement inattendu lors de l'instanciation d'une sous-classe qui remplace la fonction membre.

Par exemple:
- Par contrat, le constructeur de la classe sous-classe commence par appeler le constructeur de la classe parent.
- Le constructeur de la classe parent appelle la fonction membre parent et non celle remplacée par la classe enfant, ce qui prête à confusion pour le développeur de la classe enfant.
- Il peut produire un comportement non défini si la fonction de membre est virtuelle dans la classe parente.

Exemple de code non conforme

class Parent {
  public:
    Parent() {
      method1();
      method2(); // Noncompliant; confusing because Parent::method2() will always been called even if the method is overridden
    }
    virtual ~Parent() {
      method3(); // Noncompliant; undefined behavior (ex: throws a "pure virtual method called" exception)
    }
  protected:
    void         method1() { /*...*/ }
    virtual void method2() { /*...*/ }
    virtual void method3() = 0; // pure virtual
};

class Child : public Parent {
  public:
    Child() { // leads to a call to Parent::method2(), not Child::method2()
    }
    virtual ~Child() {
      method3(); // Noncompliant; Child::method3() will always be called even if a child class overrides method3
    }
  protected:
    void method2() override { /*...*/ }
    void method3() override { /*...*/ }
};

Solution conforme

class Parent {
  public:
    Parent() {
      method1();
      Parent::method2(); // acceptable but poor design
    }
    virtual ~Parent() {
      // call to pure virtual function removed
    }
  protected:
    void         method1() { /*...*/ }
    virtual void method2() { /*...*/ }
    virtual void method3() = 0;
};

class Child : public Parent {
  public:
    Child() {
    }
    virtual ~Child() {
      method3(); // method3() is now final so this is okay
    }
  protected:
    void method2() override { /*...*/ }
    void method3() final    { /*...*/ } // this virtual function is "final"
};
0
dismine

Au lieu de cela:

public:
    virtual void f();

Je trouve utile d'écrire ceci:

public:
    virtual void f() final
        {
        do_f(); // breakpoint here
        }
protected:
    virtual void do_f();

La raison principale est que vous avez maintenant un seul emplacement pour le point d'arrêt avant de le distribuer dans l'une des nombreuses implémentations potentiellement surchargées. Malheureusement (IMHO), dire "final" nécessite également que vous disiez "virtuel".

0
Kevin Hopps

virtual + final sont utilisés dans une déclaration de fonction pour rendre l'exemple court.

En ce qui concerne les syntax de virtual et final, l'exemple de Wikipedia serait plus expressif en introduisant struct Base2 : Base1 avec Base1 contenant virtual void f(); et Base2 contenant void f() final; (voir ci-dessous).

La norme

En se référant à N3690 :

  • virtual comme function-specifier peut faire partie de decl-specifier-seq
  • final peut faire partie de virt-specifier-seq

Aucune règle n'utilise le mot clévirtual et les identificateurs ayant une signification particulièrefinal ensemble. Section 8.4, définitions de fonctions (respect opt ​​= optionnel):

définition de fonction:

attribut-specifier-seq (opt) Déclarateur-specifier-seq (opt) Déclarateur virt-specifier-seq (opt) Corps-de-fonction

Entraine toi

Avec C++ 11, vous pouvez omettre le mot clé virtual lorsque vous utilisez final. Cela compile sur gcc> 4.7.1, sur clang> 3.0 avec C++ 11, sur msvc, ... (voir compiler Explorer ).

struct A
{
    virtual void f() {}
};

struct B : A
{
    void f() final {}
};

int main()
{
    auto b = B();
    b.f();
}

PS: L’exemple sur cppreference n’utilise pas non plus virtuel avec final dans la même déclaration.

PPS: Il en va de même pour override.

0
Roi Danton