web-dev-qa-db-fra.com

La finale implique-t-elle un remplacement?

Si je comprends bien, le mot clé override indique qu'une déclaration donnée implémente une méthode de base virtual, et la compilation devrait échouer si aucune méthode de base correspondante n'est trouvée.

Ma compréhension du mot clé final est qu'il indique au compilateur qu'aucune classe ne doit remplacer cette fonction virtual.

override final Est-il donc redondant? Il semble bien compiler . Quelles informations override final Transmet-elles que final ne transmet pas? Quel est le cas d'utilisation d'une telle combinaison?

46
quant

final n'exige pas que la fonction écrase quoi que ce soit en premier lieu. Son effet est défini dans [class.virtual]/4 comme

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 ça. Maintenant override final signifierait simplement
„Cette fonction remplace une classe de base (override) et ne peut pas être remplacée elle-même (final).“
final à lui seul imposerait une exigence plus faible. override et final ont un comportement indépendant.


Notez que final ne peut être utilisé que pour les fonctions virtuelles - [class.mem]/8

Un virt-specifier-seq ne doit apparaître que dans la déclaration d'une fonction membre virtuelle (10.3).

D'où la déclaration

void foo() final;

Est effectivement le même que

virtual void foo() final override;

Puisque les deux nécessitent foo pour remplacer quelque chose - la deuxième déclaration en utilisant override, et la première en étant valide si et seulement si foo est implicitement virtuel, c'est-à-dire lorsque foo remplace une fonction virtuelle appelée foo dans une classe de base, ce qui rend foo dans la dérivée automatiquement virtuelle. Ainsi override serait superflu dans les déclarations où final, mais pas virtual, se produit.
Pourtant, cette dernière déclaration exprime l'intention beaucoup plus clairement et devrait certainement être préférée.

40
Columbo

final n'implique pas nécessairement que la fonction est surchargée. Il est parfaitement valide (si sa valeur est quelque peu douteuse) de déclarer une fonction virtuelle comme final sur sa déclaration first dans la hiérarchie d'héritage.

Une des raisons pour lesquelles je peux penser à créer une fonction virtuelle et immédiatement finale est si vous voulez empêcher une classe dérivée de donner au même nom et paramètres une signification différente.

16
Angew

(Passez à la fin pour voir la conclusion si vous êtes pressé.)

override et final ne peuvent apparaître que dans la déclaration d'une fonction virtuelle. Et les deux mots clés peuvent être utilisés dans la même déclaration de fonction, mais leur utilité dépend des situations.

Prenez le code suivant comme exemple:

#include <iostream>
using std::cout; using std::endl;

struct B {
  virtual void f1() { cout << "B::f1() "; }
  virtual void f2() { cout << "B::f2() "; }
  virtual void f3() { cout << "B::f3() "; }
  virtual void f6() final { cout << "B::f6() "; }
  void f7() { cout << "B::f7() "; }
  void f8() { cout << "B::f8() "; }
  void f9() { cout << "B::f9() "; }
};

struct D : B {
  void f1() override { cout << "D::f1() "; }
  void f2() final { cout << "D::f2() "; }
  void f3() override final { cout << "D::f3() "; }  // need not have override
  // should have override, otherwise add new virtual function
  virtual void f4() final { cout << "D::f4() "; }
  //virtual void f5() override final;  // Error, no virtual function in base class
  //void f6(); // Error, override a final virtual function
  void f7() { cout << "D::f7() "; }
  virtual void f8() { cout << "D::f8() "; }
  //void f9() override;  // Error, override a nonvirtual function 
};

int main() {
  B b; D d;
  B *bp = &b, *bd = &d; D *dp = &d;
  bp->f1(); bp->f2(); bp->f3(); bp->f6(); bp->f7(); bp->f8(); bp->f9(); cout << endl;
  bd->f1(); bd->f2(); bd->f3(); bd->f6(); bd->f7(); bd->f8(); bd->f9(); cout << endl;
  dp->f1(); dp->f2(); dp->f3(); dp->f6(); dp->f7(); dp->f8(); dp->f9(); cout << endl;
  return 0;
}

La sortie est

B::f1() B::f2() B::f3() B::f6() B::f7() B::f8() B::f9()
D::f1() D::f2() D::f3() B::f6() B::f7() B::f8() B::f9()
D::f1() D::f2() D::f3() B::f6() D::f7() D::f8() B::f9()
  1. Comparez f1() et f6(). Nous savons que override et final est indépendant de façon sémantique.

    • override signifie que la fonction remplace une fonction virtuelle dans sa classe de base. Voir f1() et f3().
    • final signifie que la fonction ne peut pas être remplacée par sa classe dérivée. (Mais la fonction elle-même n'a pas besoin de remplacer une fonction virtuelle de classe de base.) Voir f6() et f4().
  2. Comparez f2() et f3(). Nous savons que si une fonction membre est déclarée sans virtual et avec final, cela signifie qu'elle remplace déjà une fonction virtuelle dans la classe de base. Dans ce cas, la clé Word override est redondante.

  3. Comparez f4() et f5(). Nous savons que si une fonction membre est déclarée avec virtualet si ce n'est pas la première fonction virtuelle dans la hiérarchie d'héritage, alors nous devons utiliser override pour spécifier la relation de remplacement. Sinon, nous pouvons accidentellement ajouter une nouvelle fonction virtuelle dans une classe dérivée.

  4. Comparez f1() et f7(). Nous savons que toute fonction membre, pas seulement virtuelle, peut être remplacée dans une classe dérivée. Ce que virtual spécifie est le polymorphisme , ce qui signifie que la décision quant à la fonction à exécuter est retardée jusqu'à l'exécution au lieu de la compilation. (Cela devrait être évité dans la pratique.)

  5. Comparez f7() et f8(). Nous savons que nous pouvons même remplacer une fonction de classe de base et en faire une nouvelle virtuelle. (Ce qui signifie que toute fonction membre f8() de la classe dérivée de D sera virtuelle.) (Cela devrait également être évité dans la pratique.)

  6. Comparez f7() et f9(). Nous savons que override peut nous aider à trouver l'erreur lorsque nous voulons remplacer une fonction virtuelle dans la classe dérivée tout en oubliant d'ajouter la clé Word virtual dans la classe de base.

En conclusion, la meilleure pratique à mon avis est:

  • uniquement utilisez virtual dans la déclaration du premier virtuel fonction dans la classe de base;
  • utilisez toujours override pour spécifier la fonction virtuelle de substitution dans la classe dérivée, sauf si final est également spécifié.
5
Jaege

Non final n'implique pas nécessairement override. En fait, vous pouvez déclarer une fonction virtual que vous déclarez immédiatement finalvoir ici . Le mot clé final indique simplement qu'aucun class dérivé ne peut créer un remplacement de cette fonction.

Le mot clé override est important dans la mesure où il impose que vous remplaciez effectivement une fonction virtuelle (au lieu d'en déclarer une nouvelle sans rapport). Voir ce post concernant override

Donc, pour faire court, chacun d'eux sert son propre but particulier, et il est souvent correct d'utiliser les deux.

1
CoryKramer

Le code suivant (avec le spécificateur final) se compile. Mais la compilation échoue lorsque final est remplacé par override final. Donc override final transmet plus d'informations (et empêche la compilation) que simplement final.

class Base
{
public:
    virtual ~Base() {}
};

class Derived : public Base
{
public:
    virtual void foo() final
    {
        std::cout << "in Derived foo\n";
    }
};

Essentiellement, override final indique que cette méthode ne peut être remplacée dans aucune classe dérivée et cette méthode remplace une méthode virtuelle dans une classe de base. final seul ne spécifie pas la partie prioritaire de la classe de base.

1
tcb