Article de reference

Fonction virtuelle

En programmation orientée objet, comme c'est souvent le cas en C++ et en Object Pascal , une fonction virtuelle ou une méthode virtuelle est une fonction ou une méthode héritabl...

En programmation orientée objet, comme c'est souvent le cas en C++ et en Object Pascal , une fonction virtuelle ou une méthode virtuelle est une fonction ou une méthode héritable et remplaçable qui est distribuée de manière dynamique . Les fonctions virtuelles sont une partie importante du polymorphisme (d'exécution) en programmation orientée objet (OOP). Elles permettent l'exécution de fonctions cibles qui n'ont pas été précisément identifiées au moment de la compilation.

La plupart des langages de programmation, tels que JavaScript , PHP et Python , traitent toutes les méthodes comme virtuelles par défaut et ne fournissent pas de modificateur pour modifier ce comportement. Cependant, certains langages fournissent des modificateurs pour empêcher que les méthodes soient remplacées par des classes dérivées (comme les mots-clés final et private en Java et PHP ).

But

Le concept de fonction virtuelle résout le problème suivant :

En 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é via un pointeur ou une référence du type de la classe de base au lieu du type de la classe dérivée. Si des méthodes de la classe de base sont remplacées par la classe dérivée, la méthode réellement appelée par une telle référence ou un tel pointeur peut être liée soit « tôt » (par le compilateur), selon le type déclaré du pointeur ou de la référence, soit « tard » (c'est-à-dire par le système d'exécution du langage), selon le type réel de l'objet auquel il est fait référence.

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 la plus dérivée est appelée en fonction du type réel de l'objet référencé, quel que soit le type déclaré du pointeur ou de la référence. Si elle n'est pas « virtuelle », la méthode est résolue « tôt » 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.

En C++, les méthodes virtuelles sont déclarées en ajoutant le virtualmot-clé au début de la déclaration de la fonction dans la classe de base. Ce modificateur est hérité par toutes les implémentations de cette méthode dans les classes dérivées, ce qui signifie qu'elles peuvent continuer à se remplacer mutuellement et à être liées tardivement. Et même si les méthodes appartenant à la classe de base appellent la méthode virtuelle, elles appelleront à la place la méthode dérivée. La surcharge se produit lorsque deux ou plusieurs méthodes d'une classe ont le même nom de méthode mais des paramètres différents. La substitution signifie avoir deux méthodes avec le même nom de méthode et les mêmes paramètres. La surcharge est également appelée correspondance de fonction et la substitution, mappage de fonction dynamique.

Exemple

C++

Diagramme de classe des animaux

Par exemple, une classe de base Animalpourrait avoir une fonction virtuelle Eat. La sous-classe Llamaserait implémentée Eatdifféremment de la sous-classe Wolf, mais on peut invoquer Eatn'importe quelle instance de classe appelée Animal et obtenir le Eatcomportement de la sous-classe spécifique.

classe Animal { public : // Intentionnellement pas virtuel : void Move () { std :: cout << "Cet animal se déplace d'une manière ou d'une autre" << std :: endl ; } virtual void Eat () = 0 ; };
// La classe "Animal" peut posséder une définition pour Eat si vous le souhaitez. 
class Llama : public Animal { public : // La fonction non virtuelle Move est héritée mais pas remplacée. void Eat () override { std :: cout << "Les lamas mangent de l'herbe !" << std :: endl ; } };

Cela permet à un programmeur de traiter une liste d'objets de classe Animal, en indiquant à chacun à son tour de manger (en appelant Eat), sans avoir besoin de savoir quel type d'animal peut être dans la liste, comment chaque animal mange ou quel pourrait être l'ensemble complet des types d'animaux possibles.

En C, le mécanisme derrière les fonctions virtuelles pourrait être fourni de la manière suivante :

#include <stdio.h>
/* un objet pointe vers sa classe... */ 
struct Animal { const struct AnimalVTable * vtable ; };
/* qui contient la fonction virtuelle Animal.Eat */ 
struct AnimalVTable { void ( * Eat )( struct Animal * self ); // fonction 'virtuelle' };
/* 
 Étant donné que Animal.Move n'est pas une fonction virtuelle, 
 elle ne figure pas dans la structure ci-dessus. 
*/ 
void Move ( const struct Animal * self ) { printf ( "<Animal à %p> s'est déplacé d'une manière ou d'une autre " , ( void * )( self )); }
/* 
 contrairement à Move, qui exécute Animal.Move directement, 
 Eat ne peut pas savoir quelle fonction (le cas échéant) appeler au moment de la compilation. 
 Animal.Eat ne peut être résolu qu'au moment de l'exécution lorsque Eat est appelé. 
*/ 
void Eat ( struct Animal * self ) { const struct AnimalVTable * vtable = * ( const void ** )( self ); if ( vtable -> Eat != NULL ) { ( * vtable -> Eat )( self ); // exécute Animal.Eat } else { fprintf ( stderr , "La méthode virtuelle 'Eat' n'est pas implémentée " ); } }
/* 
 implémentation de Llama.Eat ceci est la fonction cible 
 qui sera appelée par 'void Eat(struct Animal *self).' 
*/ 
static void _Llama_eat ( struct Animal * self ) { printf ( "<Llama at %p> Les lamas mangent de l'herbe ! " , ( void * )( self )); }
/* initialiser la classe */ 
const struct AnimalVTable Animal = { NULL }; // la classe de base n'implémente pas Animal.Eat const struct AnimalVTable Llama = { _Llama_eat }; // mais la classe dérivée le fait
int main ( void ) { /* init objets comme instance de sa classe */ struct Animal animal = { & Animal }; struct Animal llama = { & Llama }; Move ( & animal ); // Animal.Move Move ( & llama ); // Llama.Move Eat ( & animal ); // impossible de résoudre Animal.Eat donc afficher "Non implémenté" sur stderr Eat ( & llama ); // résout Llama.Eat et exécute }

Classes abstraites et fonctions virtuelles pures

Une fonction virtuelle pure ou une méthode virtuelle pure est une fonction virtuelle qui doit être implémentée par une classe dérivée si la classe dérivée 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 une classe parente. Les méthodes virtuelles pures ont généralement une déclaration ( signature ) et aucune définition ( implémentation ).

À titre d'exemple, une classe de base abstraite MathSymbolpeut fournir une fonction virtuelle pure doOperation()et des classes dérivées Pluset Minusimplement doOperation()pour fournir des implémentations concrètes. L'implémentation doOperation()n'aurait pas de sens dans la MathSymbolclasse, car MathSymbolc'est 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 aucune implémentation dans la classe qui les déclare, les méthodes virtuelles pures dans certains langages (par exemple C++ et Python) sont autorisées à contenir une implémentation dans leur classe déclarante, fournissant un comportement de secours ou par défaut qu'une classe dérivée peut déléguer, si approprié.

Les fonctions virtuelles pures peuvent également être utilisées lorsque les déclarations de méthode servent à définir une interface , comme le spécifie explicitement le mot-clé interface en Java. Dans une telle utilisation, les classes dérivées fourniront toutes les implémentations. Dans un tel modèle de conception , la classe abstraite qui sert d'interface ne contiendra que des fonctions virtuelles pures, mais aucun membre de données ni aucune méthode ordinaire. En C++, l'utilisation de classes purement abstraites comme interfaces fonctionne car C++ prend en charge l'héritage multiple . Cependant, comme de nombreux langages OOP ne prennent pas en charge l'héritage multiple, ils fournissent souvent un mécanisme d'interface distinct. Le langage de programmation Java en est un exemple .

Comportement pendant la construction et la destruction

Les langages diffèrent dans leur comportement pendant l'exécution du constructeur ou du destructeur d'un objet. Pour cette raison, l'appel de fonctions virtuelles dans les constructeurs est généralement déconseillé.

En C++, la fonction « base » est appelée. Plus précisément, la fonction la plus dérivée qui n'est pas plus dérivée que la classe du constructeur ou du destructeur actuel est appelée. Si cette fonction est une fonction virtuelle pure, alors un comportement indéfini se produit. Ceci est vrai même si la classe contient une implémentation pour cette fonction virtuelle pure, car un appel à une fonction virtuelle pure doit être explicitement qualifié. Une implémentation C++ conforme n'est pas requise (et généralement pas capable) de détecter les appels indirects à des fonctions virtuelles pures au moment de la compilation ou de la liaison . Certains systèmes d'exécution émettront une erreur d'appel de fonction virtuelle pure lorsqu'ils rencontrent un appel à une fonction virtuelle pure au moment de l'exécution .

En Java et 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 à leurs valeurs zéro par défaut). Certains modèles de conception , tels que le modèle Abstract Factory , encouragent activement cette utilisation dans les langages prenant en charge cette capacité.

Destructeurs virtuels

Les langages orientés objet gèrent généralement l'allocation et la désallocation de mémoire automatiquement lorsque des objets sont créés et détruits. Cependant, certains langages orientés objet permettent d'implémenter une méthode de destructeur 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) qui est appelé est certainement celui qui convient à l'objet en question. Par exemple, si un objet de type Wolf qui hérite d'Animal est créé et que les deux ont des destructeurs personnalisés, celui appelé sera celui déclaré dans Wolf.

Dans les contextes de gestion manuelle de la mémoire, la situation peut être plus complexe, notamment en ce qui concerne la répartition statique . Si un objet de type Wolf est créé mais pointé par un pointeur Animal, et que c'est ce type de pointeur Animal qui est supprimé, le destructeur appelé peut en fait être celui défini pour Animal et non celui pour Wolf, à moins que le destructeur ne soit virtuel. C'est particulièrement le cas avec C++, où le comportement est une source courante d'erreurs de programmation si les destructeurs ne sont pas virtuels.

Plus d articles de Worldlex Wiki

Revenez a l index pour explorer davantage de pages sur l histoire, la science, la culture, la geographie et la societe en francais.

Explorer l index