Remplacer en toute sécurité les fonctions virtuelles C ++

100

J'ai une classe de base avec une fonction virtuelle et je souhaite remplacer cette fonction dans une classe dérivée. Existe-t-il un moyen de faire en sorte que le compilateur vérifie si la fonction que j'ai déclarée dans la classe dérivée remplace réellement une fonction dans la classe de base? Je voudrais ajouter une macro ou quelque chose qui garantit que je n'ai pas accidentellement déclaré une nouvelle fonction, au lieu de remplacer l'ancienne.

Prenons cet exemple:

class parent {
public:
  virtual void handle_event(int something) const {
    // boring default code
  }
};

class child : public parent {
public:
  virtual void handle_event(int something) {
    // new exciting code
  }
};

int main() {
  parent *p = new child();
  p->handle_event(1);
}

Ici parent::handle_event()est appelée à la place de child::handle_event(), car la méthode de l'enfant manque la constdéclaration et déclare donc une nouvelle méthode. Cela peut également être une faute de frappe dans le nom de la fonction ou une différence mineure dans les types de paramètres. Cela peut également se produire facilement si l'interface de la classe de base change et que quelque part une classe dérivée n'a pas été mise à jour pour refléter le changement.

Existe-t-il un moyen d'éviter ce problème, puis-je dire au compilateur ou à un autre outil de vérifier cela pour moi? Des indicateurs de compilation utiles (de préférence pour g ++)? Comment évitez-vous ces problèmes?

qc
la source
2
Excellente question, j'essaie de comprendre pourquoi ma fonction de classe enfant n'est pas appelée depuis une heure maintenant!
Akash Mankar

Réponses:

89

Depuis g ++ 4.7, il comprend le nouveau overridemot-clé C ++ 11 :

class child : public parent {
    public:
      // force handle_event to override a existing function in parent
      // error out if the function with the correct signature does not exist
      void handle_event(int something) override;
};
Gunther Piez
la source
@hirschhornsalz: J'ai trouvé que lorsque vous implémentez la fonction handle_event et que vous ajoutez un remplacement à la fin de l'implémentation de la fonction, g ++ donne une erreur; si vous fournissez une implémentation de fonction en ligne dans la déclaration de classe après le mot clé override, tout va bien. Pourquoi?
h9uest le
3
@ h9uest overridedoit être utilisé dans la définition. Une implémentation en ligne est à la fois définition et implémentation, donc ça va.
Gunther Piez
@hirschhornsalz ouais j'ai eu le même message d'erreur par g ++. Une remarque, cependant: vous et g ++ error msg avez utilisé le terme "définition de classe" - ne devrions-nous pas utiliser "déclaration" (paire {déclaration, définition})? Vous vous êtes clarifié dans ce contexte particulier en disant «définition et implémentation», mais je me demande simplement pourquoi la communauté c ++ décide soudainement de changer les termes sur les classes?
h9uest
20

Quelque chose comme le overridemot clé de C # ne fait pas partie de C ++.

Dans gcc, -Woverloaded-virtualmet en garde contre le masquage d'une fonction virtuelle de classe de base avec une fonction du même nom mais une signature suffisamment différente pour ne pas la remplacer. Cela ne vous protégera pas, cependant, de ne pas remplacer une fonction en raison d'une mauvaise orthographe du nom de la fonction elle-même.

CB Bailey
la source
2
C'est si vous utilisez Visual C ++
Steve Rowe
4
L'utilisation de Visual C ++ ne crée pas de overridemot clé en C ++; cela peut signifier que vous utilisez quelque chose qui peut compiler du code source C ++ invalide, cependant. ;)
CB Bailey
3
Le fait que le remplacement ne soit pas valide en C ++ signifie que le standard est incorrect, et non Visual C ++
Jon
2
@Jon: OK, maintenant je vois vers quoi tu roulais. Personnellement, je pourrais prendre ou quitter la overridefonctionnalité de style C # ; J'ai rarement eu des problèmes avec les remplacements échoués et ils ont été relativement faciles à diagnostiquer et à résoudre. Je ne suis pas d'accord avec le fait que les utilisateurs de VC ++ devraient l'utiliser. Je préférerais que C ++ ressemble à C ++ sur toutes les plates-formes, même si un projet particulier n'a pas besoin d'être portable. Il convient de noter que C ++ 0x aura [[base_check]], [[override]]et les [[hiding]]attributs de sorte que vous pouvez opter pour passer outre vérifier si on le souhaite.
CB Bailey
5
Curieusement, quelques années après que le commentaire utilisant VC ++ ne crée pas de overridemot-clé, il semble que ce soit le cas . Eh bien, pas un mot clé correct mais un identifiant spécial en C ++ 11. Microsoft a suffisamment insisté pour en faire un cas particulier et pour suivre le format général des attributs et overrideen faire la norme :)
David Rodríguez - dribeas
18

Pour autant que je sache, ne pouvez-vous pas simplement le rendre abstrait?

class parent {
public:
  virtual void handle_event(int something) const = 0 {
    // boring default code
  }
};

Je pensais avoir lu sur www.parashift.com que vous pouvez réellement implémenter une méthode abstraite. Ce qui a du sens pour moi personnellement, la seule chose qu'il fait est de forcer les sous-classes à l'implémenter, personne n'a dit quoi que ce soit à propos de ne pas être autorisé à avoir une implémentation elle-même.

Ray Hidayat
la source
Ce n'est que maintenant que j'ai réalisé que cela fonctionne sur plus que de simples destructeurs! Super trouvaille.
strager
1
Il y a quelques inconvénients potentiels à cela: 1) l'autre chose que le fait de marquer une ou plusieurs méthodes comme abstraite est de rendre la classe de base non instanciable, ce qui peut être un problème si cela ne fait pas partie de l'utilisation prévue de la classe. 2) la classe de base n'est peut-être pas la vôtre à modifier en premier lieu.
Michael Burr
3
Je suis d'accord avec Michael Burr. Rendre le résumé de la classe de base ne fait pas partie de la question. Il est parfaitement raisonnable d'avoir une classe de base avec des fonctionnalités dans une méthode virtuelle, que vous voulez qu'une classe dérivée remplace. Et il est tout aussi raisonnable de vouloir se protéger contre un autre programmeur renommant la fonction dans la classe de base, et faisant en sorte que votre classe dérivée ne la remplace plus. L'extension Microsoft "override" est inestimable dans ce cas. J'adorerais le voir ajouté à la norme, car malheureusement, il n'y a pas de bon moyen de le faire sans lui.
Brian
Cela empêche également la méthode de base (par exemple BaseClass::method()) d'être appelée dans l'implémentation dérivée (par exemple DerivedClass::method()), par exemple pour une valeur par défaut.
Narcolessico
11

Dans MSVC, vous pouvez utiliser le overridemot clé CLR même si vous ne compilez pas pour CLR.

Dans g ++, il n'y a pas de moyen direct d'appliquer cela dans tous les cas; d'autres personnes ont donné de bonnes réponses sur la façon de détecter les différences de signature en utilisant -Woverloaded-virtual. Dans une version future, quelqu'un pourrait ajouter une syntaxe similaire __attribute__ ((override))ou l'équivalent en utilisant la syntaxe C ++ 0x.

Doug
la source
9

Dans MSVC ++, vous pouvez utiliser le mot cléoverride

class child : public parent {
public:
  virtual void handle_event(int something) <b>override</b> {
    // new exciting code
  }
};

override fonctionne à la fois pour le code natif et CLR dans MSVC ++.

bobobobo
la source
5

Rendez la fonction abstraite, de sorte que les classes dérivées n'aient pas d'autre choix que de la remplacer.

@Ray Votre code n'est pas valide.

class parent {
public:
  virtual void handle_event(int something) const = 0 {
    // boring default code
  }
};

Les fonctions abstraites ne peuvent pas avoir de corps définis en ligne. Il doit être modifié pour devenir

class parent {
public:
  virtual void handle_event(int something) const = 0;
};

void parent::handle_event( int something ) { /* do w/e you want here. */ }
Tanveer Badar
la source
3

Je suggérerais un léger changement dans votre logique. Cela peut fonctionner ou non, selon ce que vous devez accomplir.

handle_event () peut toujours faire le "code par défaut ennuyeux" mais au lieu d'être virtuel, au point où vous voulez qu'il fasse le "nouveau code passionnant", demandez à la classe de base d'appeler une méthode abstraite (c'est-à-dire qu'il faut remplacer la méthode) qui sera fourni par votre classe descendante.

EDIT: Et si vous décidez plus tard que certaines de vos classes descendantes n'ont pas besoin de fournir un "nouveau code passionnant" alors vous pouvez changer l'abstrait en virtuel et fournir une implémentation de classe de base vide de cette fonctionnalité "insérée".

JMD
la source
2

Votre compilateur peut avoir un avertissement qu'il peut générer si une fonction de classe de base est masquée. Si c'est le cas, activez-le. Cela attrapera les conflits const et les différences dans les listes de paramètres. Malheureusement, cela ne révélera pas une erreur d'orthographe.

Par exemple, il s'agit d'avertissement C4263 dans Microsoft Visual C ++.

Mark Ransom
la source
1

overrideMot clé C ++ 11 lorsqu'il est utilisé avec la déclaration de fonction à l'intérieur de la classe dérivée, il force le compilateur à vérifier que la fonction déclarée remplace en fait une fonction de classe de base. Sinon, le compilateur lèvera une erreur.

Par conséquent, vous pouvez utiliser un overridespécificateur pour assurer un polymorphisme dynamique (remplacement de fonction).

class derived: public base{
public:
  virtual void func_name(int var_name) override {
    // statement
  }
};
Adarsh ​​Kumar
la source