De nombreux langages de programmation, comme Java , considèrent par défaut toutes les méthodes comme virtuelles. Certains de ces langages, comme Python , ne permettent pas de modifier ce comportement. Cependant, d'autres peuvent fournir des modificateurs qui empêchent la redéfinition des méthodes par les classes dérivées (comme les mots-clés `final` et `private` en Java et PHP ).
programmation orientée objet , lorsqu'une classe dérivée hérite d'une classe de base, un objet de la classe dérivée peut être référencé par un pointeur ou une référence du type de la classe de base plutôt que du type de la classe dérivée. Si des méthodes de la classe de base sont redéfinies par la classe dérivée, la méthode effectivement appelée par une telle référence ou un tel pointeur peut être liée soit « précédemment » (par le compilateur), selon le type déclaré du pointeur ou de la référence, soit « tardivement » (c'est-à-dire par le système d'exécution du langage), selon le type réel de l'objet référencé.Les fonctions virtuelles sont résolues « tardivement ». Si la fonction en question est « virtuelle » dans la classe de base, l’implémentation de la fonction dans la classe dérivée la plus proche est appelée en fonction du type réel de l’objet référencé, indépendamment du type déclaré du pointeur ou de la référence. Si elle n’est pas « virtuelle », la méthode est résolue « précédentement » et sélectionnée en fonction du type déclaré du pointeur ou de la référence.
Les fonctions virtuelles permettent à un programme d'appeler des méthodes qui n'existent pas nécessairement au moment de la compilation du code.
Par exemple, une classe de base Animalpeut avoir une fonction virtuelle eat. Une sous-classe Llamaimplémenterait cette fonction eatdifféremment d'une autre sous-classe Wolf, mais on peut l'appeler eatsur n'importe quelle instance de classe appelée Animal et obtenir le eatcomportement de la sous-classe spécifique.
Cela permet à un programmeur de traiter une liste d'objets de classe Animal, en disant à chacun à son tour de manger (en appelant eat), sans avoir besoin de savoir quel type d'animal peut se trouver dans la liste, comment chaque animal mange ou quel pourrait être l'ensemble complet des types d'animaux possibles.
C
En C, le mécanisme sous-jacent aux fonctions virtuelles pourrait être fourni de la manière suivante :
classes abstraites et fonctions virtuelles pures
Une fonction virtuelle pure , ou méthode virtuelle pure, est une fonction virtuelle qu'une classe dérivée doit implémenter si cette dernière n'est pas abstraite . Les classes contenant des méthodes virtuelles pures sont dites « abstraites » et ne peuvent pas être instanciées directement. Une sous-classe d'une classe abstraite ne peut être instanciée directement que si toutes les méthodes virtuelles pures héritées ont été implémentées par cette classe ou par une classe parente. Les méthodes virtuelles pures possèdent généralement une déclaration ( signature ) et aucune définition ( implémentation ).
Par exemple, une classe de base abstraite MathSymbolpeut fournir une fonction virtuelle pure doOperation(), et les classes dérivées Pluspeuvent Minusl'implémenter doOperation()pour en fournir des implémentations concrètes. Implémenter cette fonction doOperation()n'aurait pas de sens dans la MathSymbolclasse, car MathSymbolil s'agit d'un concept abstrait dont le comportement est défini uniquement pour chaque type (sous-classe) donné de MathSymbol. De même, une sous-classe donnée de MathSymbolne serait pas complète sans une implémentation de doOperation().
Bien que les méthodes virtuelles pures n'aient généralement pas d'implémentation dans la classe qui les déclare, dans certains langages (par exemple C++ et Python), il est permis qu'elles contiennent une implémentation dans leur classe déclarante, fournissant ainsi un comportement de repli ou par défaut auquel une classe dérivée peut déléguer, le cas échéant.
Les fonctions virtuelles pures peuvent également être utilisées lorsque les déclarations de méthodes servent à définir une interface , comme le spécifie explicitement le mot-clé `interface` en Java. Dans ce cas, les classes dérivées fournissent toutes les implémentations. Selon ce modèle de conception , la classe abstraite faisant office d'interface ne contient que des fonctions virtuelles pures, sans aucun membre de données ni méthode ordinaire. En C++, l'utilisation de telles classes abstraites comme interfaces est possible grâce à la prise en charge de l'héritage multiple . Cependant, de nombreux langages orientés objet ne prenant pas en charge l'héritage multiple, ils proposent souvent un mécanisme d'interface distinct. Le langage Java en est un exemple .
Comportement pendant la construction et la destruction
Le comportement des langages diffère lors de l'exécution du constructeur ou du destructeur d'un objet. C'est pourquoi il est généralement déconseillé d'appeler des fonctions virtuelles dans les constructeurs.
En C++, la fonction de base est appelée. Plus précisément, c'est la fonction la plus dérivée, qui n'est pas plus dérivée que la classe du constructeur ou du destructeur courant, qui est appelée. Si cette fonction est une fonction virtuelle pure, un comportement indéfini se produit. Ceci est vrai même si la classe contient une implémentation de cette fonction virtuelle pure, car un appel à une fonction virtuelle pure doit être explicitement qualifié. Une implémentation C++ conforme n'est pas tenue (et généralement incapable) de détecter les appels indirects à des fonctions virtuelles pures à la compilation ou à l'édition de liens . Certains systèmes d'exécution génèrent une erreur d'appel de fonction virtuelle pure lorsqu'ils rencontrent un tel appel à l' exécution .
En Java et en C#, l'implémentation dérivée est appelée, mais certains champs ne sont pas encore initialisés par le constructeur dérivé (bien qu'ils soient initialisés à leur valeur par défaut zéro). Certains patrons de conception , tels que le patron Abstract Factory , encouragent activement cette pratique dans les langages qui la prennent en charge.
destructeurs virtuels
Les langages orientés objet gèrent généralement l'allocation et la libération de mémoire automatiquement lors de la création et de la destruction des objets. Cependant, certains langages orientés objet permettent d'implémenter une méthode de destruction personnalisée , si nécessaire. Si le langage en question utilise la gestion automatique de la mémoire, le destructeur personnalisé (généralement appelé finaliseur dans ce contexte) appelé sera forcément celui approprié à l'objet concerné. Par exemple, si un objet de type Loup héritant d'Animal est créé, et que les deux possèdent des destructeurs personnalisés, celui appelé sera celui déclaré dans Loup.
Dans le cadre d'une gestion manuelle de la mémoire, la situation peut se complexifier, notamment en ce qui concerne la répartition statique . Si un objet de type Wolf est créé mais référencé par un pointeur de type Animal, et que c'est ce dernier pointeur qui est supprimé, le destructeur appelé risque d'être celui défini pour Animal et non celui de Wolf, sauf s'il est virtuel. C'est particulièrement le cas en C++, où ce comportement est une source fréquente d'erreurs de programmation si les destructeurs ne sont pas virtuels.