Quel est le but d'utiliser le mot virtuel réservé devant les fonctions? Si je veux qu'une classe enfant écrase une fonction parent, je déclare simplement la même fonction telle que void draw(){}
.
class Parent {
public:
void say() {
std::cout << "1";
}
};
class Child : public Parent {
public:
void say()
{
std::cout << "2";
}
};
int main()
{
Child* a = new Child();
a->say();
return 0;
}
La sortie est 2.
Encore une fois, pourquoi le mot réservé virtual
serait-il nécessaire dans l'en-tête de say()
?
Merci beaucoup.
C'est la question classique du fonctionnement du polymorphisme, je pense. L'idée principale est que vous voulez faire abstraction du type spécifique pour chaque objet. En d'autres termes: vous voulez pouvoir appeler les instances Child sans savoir que c'est un enfant!
Voici un exemple: en supposant que vous avez la classe "Child" et la classe "Child2" et "Child3", vous voulez pouvoir vous y référer via leur classe de base (Parent).
Parent* parents[3];
parents[0] = new Child();
parents[1] = new Child2();
parents[2] = new Child3();
for (int i=0; i<3; ++i)
parents[i]->say();
Comme vous pouvez l'imaginer, c'est très puissant. Il vous permet d'étendre le parent autant de fois que vous le souhaitez et les fonctions qui prennent un pointeur parent fonctionnent toujours. Pour que cela fonctionne comme d'autres le mentionnent, vous devez déclarer la méthode virtuelle.
Si la fonction était virtuelle, vous pouvez le faire et obtenir toujours la sortie "2":
Parent* a = new Child();
a->say();
Cela fonctionne car une fonction virtual
utilise le type réel alors qu'une fonction non virtuelle utilise le type déclaré . Lisez la suite polymorphisme pour une meilleure discussion sur les raisons pour lesquelles vous souhaitez faire cela.
Essayez-le avec:
Parent *a = new Child();
Parent *b = new Parent();
a->say();
b->say();
Sans virtual
, les deux avec impression '1'. Ajoutez virtual et l'enfant agira comme un enfant, même s'il est référencé via un pointeur vers un Parent
.
Si vous n'utilisez pas le mot clé virtual
, vous ne remplacez pas, mais définissez plutôt une méthode non liée dans la classe dérivée qui masquera la méthode de la classe de base. Autrement dit, sans les virtual
, Base::say
et Derived::say
ne sont pas liés - à côté de la coïncidence du nom.
Lorsque vous utilisez le mot-clé virtuel (obligatoire dans la base, facultatif dans la classe dérivée), vous indiquez au compilateur que les classes dérivées de cette base pourront remplacer la méthode. Dans ce cas, Base::say
et Derived::say
sont considérés comme des remplacements de la même méthode.
Lorsque vous utilisez une référence ou un pointeur vers une classe de base pour appeler une méthode virtuelle, le compilateur ajoutera le code approprié afin que le remplacement final soit appelé (le remplacement dans la classe la plus dérivée qui définit la méthode dans la hiérarchie de l'instance concrète utilisée). Notez que si vous n'utilisez pas de références/pointeur mais des variables locales, le compilateur peut résoudre l'appel et il n'a pas besoin d'utiliser le mécanisme de répartition virtuelle.
Eh bien, je l'ai testé par moi-même, car il y a beaucoup de choses auxquelles nous pouvons penser:
#include <iostream>
using namespace std;
class A
{
public:
virtual void v() { cout << "A virtual" << endl; }
void f() { cout << "A plain" << endl; }
};
class B : public A
{
public:
virtual void v() { cout << "B virtual" << endl; }
void f() { cout << "B plain" << endl; }
};
class C : public B
{
public:
virtual void v() { cout << "C virtual" << endl; }
void f() { cout << "C plain" << endl; }
};
int main()
{
A * a = new C;
a->f();
a->v();
((B*)a)->f();
((B*)a)->v();
}
production:
A plain
C virtual
B plain
C virtual
Je pense qu'une bonne réponse, simple et courte, pourrait ressembler à ceci (parce que je pense que les gens qui peuvent mieux comprendre peuvent moins mémoriser et ont donc besoin d'explications courtes et simples):
Les méthodes virtuelles vérifient les DONNÉES de l'instance vers laquelle pointe le pointeur, tandis que les méthodes classiques n'appellent pas ainsi la méthode correspondant au type spécifié.
Le point de cette fonctionnalité est le suivant: supposons que vous ayez un tableau d'A. Le tableau peut contenir des B, C, (ou même des types dérivés). si vous voulez appeler séquentiellement la même méthode de toutes ces instances, vous appelleriez chacune que vous avez surchargée.
Je trouve cela assez difficile à comprendre, et évidemment tout cours C++ devrait expliquer comment cela est réalisé, car la plupart du temps on vous enseigne simplement les fonctions virtuelles, vous les utilisez, mais jusqu'à ce que vous compreniez comment le compilateur les comprend et comment l'exécutable va gérer les appels, vous êtes dans le noir.
La chose à propos de VFtables est que je n'ai jamais été expliqué quel type de code il ajoute, et c'est évidemment ici que C++ nécessite beaucoup plus d'expérience que C, et cela pourrait être la principale raison pour laquelle C++ a été étiqueté comme "lent" à ses débuts: en fait, il est puissant, mais comme tout, il est puissant si vous savez vous en servir, sinon vous "vous coupez la jambe".
Lorsque vous utilisez le mot clé virtual, une table de fonction virtuelle est créée pour localiser les méthodes correctes dans une instance. Ensuite, même si l'instance dérivée est pointée par un pointeur de classe de base, elle trouvera toujours l'implémentation correcte de la méthode.