Duplicata possible:
Quelqu'un peut-il expliquer les méthodes virtuelles C++?
J'ai une question concernant les fonctions virtuelles C++.
Pourquoi et quand utilisons-nous des fonctions virtuelles? Quelqu'un peut-il me donner une implémentation ou une utilisation en temps réel des fonctions virtuelles?
Vous utilisez des fonctions virtuelles lorsque vous souhaitez remplacer un certain comportement (méthode de lecture) pour votre classe dérivée plutôt que celui implémenté pour la classe de base et que vous souhaitez le faire au moment de l'exécution via un pointeur vers la classe de base.
L'exemple classique est lorsque vous avez une classe de base appelée Shape
et des formes concrètes (classes) qui en dérivent. Chaque classe concrète redéfinit (implémente une méthode virtuelle) appelée Draw()
.
La hiérarchie des classes est la suivante:
L'extrait de code suivant montre l'utilisation de l'exemple; il crée un tableau de pointeurs de classe Shape
dans lequel chacun pointe vers un objet de classe dérivé distinct. Au moment de l'exécution, l'invocation de la méthode Draw()
entraîne l'appel de la méthode remplacée par cette classe dérivée et le Shape
particulier est dessiné (ou rendu).
Shape *basep[] = { &line_obj, &tri_obj,
&rect_obj, &cir_obj};
for (i = 0; i < NO_PICTURES; i++)
basep[i] -> Draw ();
Le programme ci-dessus utilise simplement le pointeur sur la classe de base pour stocker les adresses des objets de classe dérivés. Cela fournit un couplage lâche car le programme n'a pas à changer radicalement si une nouvelle classe dérivée concrète de shape
est ajoutée à tout moment. La raison en est qu'il existe un minimum de segments de code qui utilisent (dépendent) du type concret Shape
.
Ce qui précède est un bon exemple des Open Closed Principle des fameux SOLIDES principes de conception.
Vous utilisez des fonctions virtuelles lorsque vous devez gérer différents objets de la même manière. Cela s'appelle le polymorphisme. Imaginons que vous ayez une classe de base - quelque chose comme la forme classique:
class Shape
{
public:
virtual void draw() = 0;
virtual ~Shape() {}
};
class Rectange: public Shape
{
public:
void draw() { // draw rectangle here }
};
class Circle: public Shape
{
public:
void draw() { // draw circle here }
};
Vous pouvez maintenant avoir un vecteur de formes différentes:
vector<Shape*> shapes;
shapes.Push_back(new Rectangle());
shapes.Push_back(new Circle());
Et vous pouvez dessiner toutes les formes comme ceci:
for(vector<Shape*>::iterator i = shapes.begin(); i != shapes.end(); i++)
{
(*i)->draw();
}
De cette façon, vous dessinez différentes formes avec une seule méthode virtuelle - draw (). La version appropriée de la méthode est sélectionnée en fonction des informations d'exécution sur le type d'objet derrière le pointeur.
Remarque Lorsque vous utilisez des fonctions virtuelles, vous pouvez les déclarer comme pure virtual (comme dans la classe Shape, placez simplement "= 0" après la méthode proto). Dans ce cas, vous ne pourrez pas créer d'instance d'objet avec une fonction virtuelle pure et elle sera appelée classe abstraite.
Notez également "virtuel" avant le destructeur. Dans le cas où vous prévoyez de travailler avec des objets via des pointeurs vers leurs classes de base, vous devez déclarer le destructeur virtuel, donc lorsque vous appelez "supprimer" pour le pointeur de classe de base, toute la chaîne de destructeurs sera appelée et il n'y aura pas de fuites de mémoire.
Pensez à la classe des animaux et en dérivez le chat, le chien et la vache. La classe animale a un
virtual void SaySomething()
{
cout << "Something";
}
une fonction.
Animal *a;
a = new Dog();
a->SaySomething();
Au lieu d'imprimer "Quelque chose", le chien devrait dire "Bark", le chat devrait dire "Meow". Dans cet exemple, vous voyez que a est un chien, mais il y a des moments où vous avez un pointeur animal et vous ne savez pas de quel animal il s'agit. Vous ne voulez pas savoir de quel animal il s'agit, vous voulez juste que l'animal dise quelque chose. Donc, vous appelez simplement la fonction virtuelle et les chats diront "miaou" et les chiens diront "aboyer".
Bien sûr, la fonction SaySomething aurait dû être pure virtuelle pour éviter d'éventuelles erreurs.
Vous utiliseriez une fonction virtuelle pour implémenter le "polymorphisme", en particulier lorsque vous avez un objet, ne savez pas quel est le type sous-jacent réel, mais sachez quelle opération vous voulez effectuer sur lui, et l'implémentation de cela (comment il le fait) diffère selon le type que vous avez réellement.
Essentiellement ce qu'on appelle communément le "principe de substitution de Liskov" du nom de Barbara Liskov qui en a parlé vers 1983.
Lorsque vous devez utiliser des décisions d'exécution dynamiques où, au moment où le code appelant la fonction est appelé, vous ne savez pas quels types peuvent y passer, maintenant ou dans le futur, c'est un bon modèle à utiliser.
Ce n'est pas le seul moyen cependant. Il existe toutes sortes de "rappels" qui peuvent prendre un "blob" de données et vous pouvez avoir des tables de rappels dépendant d'un bloc d'en-tête dans les données qui arrivent, par ex. un processeur de messages. Pour cela, il n'est pas nécessaire d'utiliser une fonction virtuelle, en fait, vous utiliseriez probablement une sorte de mise en œuvre d'une table virtuelle avec une seule entrée (par exemple, une classe avec une seule fonction virtuelle).