Méthodes virtuelles dans une classe

Résolu
theGrimReaper Messages postés 25 Date d'inscription dimanche 31 octobre 2010 Statut Membre Dernière intervention 18 janvier 2014 - 26 nov. 2013 à 20:24
theGrimReaper Messages postés 25 Date d'inscription dimanche 31 octobre 2010 Statut Membre Dernière intervention 18 janvier 2014 - 28 nov. 2013 à 17:37
Bonjour,

J'ai juste une question de compréhension sur l'usage des "virtual" dans l'héritage de classes.

Est-ce que le fait de définir une méthode virtual dans une classe mère, permet de faire en sorte que cette classe ne puisse pas être surchargé (redéfini) par une classe fille ??

Parce que faire une méthode virtuelle permet de faire en sorte qu'elle puisse être surchargé dans une classe fille. Sauf qu'il me semble que par défaut, si une classe fille hérite d'une classe mère par la permission public ou protected, alors toutes ses méthodes (sauf private) sont héritées (utilisable aussi pour le coup) et passées par défaut en virtual (puisse qu'on peut les surcharger).
Et donc la question est : sont-elles passées par défaut en virtual à chaque fois qu'une classe hérite ??
Car si c'est le cas, cela voudrait dire que mettre le mot clé virtual devant les prototypes des méthodes dans la définition de la classe sert uniquement pour la compréhension et le respect des règles de la POO..?

Je pense qu'il y à encore quelques notions obscures et subtiles sur l'héritage que j'ai pas encore capté en C++...

Donc si quelqu'un se sent chaud d'éclairer ma lanterne, je suis tout ouï... ^^
Merci d'avance.

7 réponses

cptpingu Messages postés 3837 Date d'inscription dimanche 12 décembre 2004 Statut Modérateur Dernière intervention 28 mars 2023 123
Modifié par cptpingu le 26/11/2013 à 20:59
Bonjour.

Non, le mot clé "virtual" n'est pas là pour décorer :)
Si tu marques une méthode comme virtual, alors celle-ci a le droit d'être remplacé "en dessous" par la classe fille.
Ce n'est vrai que si tu fais rentrer la classe fille dans la classe mère.
Si tu utilises la classe elle même, il n'y aura jamais ambiguïté.


Je te fais un exemple avec ambiguïté. Soit les classes suivantes:
class Base
{
    virtual void myFunc(){print "Base";}
}
class Deriv : public Base
{
    virtual void myFunc(){print "Deriv";}
}


Deriv* deriv = new Deriv;
Base* base = new Deriv;
base->myFunc();

affichera
"Deriv"


class Base
{
    virtual void myFunc(){print "Base";}
}
class Deriv : public Base
{
    void myFunc(){print "Deriv";}
}


Deriv* deriv = new Deriv;
Base* base = new Deriv;
base->myFunc();

affichera
"Deriv"


class Base
{
    void myFunc(){print "Base";}
}
class Deriv : public Base
{
    void myFunc(){print "Deriv";}
}


Deriv* deriv = new Deriv;
Base* base = new Deriv;
base->myFunc();

affichera
"Base"


On remarque au passage que le mot clé virtual fonctionne du "haut" vers le "bas". Ne pas mettre de virtual sur la méthode de "Deriv", ne change rien (sauf si tu as une troisième classe qui hérite de "Deriv"). On le met uniquement par bonne pratique (car on ne veut pas "briser" la chaîne, quelqu'un pourrait vouloir hériter de "Deriv", et on ne doit pas le bloquer).


Maintenant, sans ambiguïtés.
Je te fais un exemple. Soit les classes suivantes:
class Base
{
    /* avec ou sans virtual */ void myFunc(){print "Base";}
}
class Deriv : public Base
{
    /* avec ou sans virtual */ void myFunc(){print "Deriv";}
}


Deriv* deriv = new Deriv;
Base* base = new Base;
deriv->myFunc(); // Deriv
base->myFunc(); // Base

Deriv deriv;
Base base;
deriv.myFunc(); // Deriv
base.myFunc(); // Base


N'hésite pas, si ce n'est pas clair. Je ne détaille pas tous les usages de "virtual", mais on peut en faire autre chose (tu verras sans doute cela plus tard).
0
theGrimReaper Messages postés 25 Date d'inscription dimanche 31 octobre 2010 Statut Membre Dernière intervention 18 janvier 2014
Modifié par theGrimReaper le 26/11/2013 à 21:37
Ok merci,
je comprends bien qu'avec l'utilisation de pointeur l'ambiguïté disparait instantanément, seulement dans ce cas, en quoi faut-il préféré l'utilisation d'allocation d'objets avec new et avoir un pointeur, plutôt que de simplement faire hériter une classe par une autre en laissant tranquillement les classes appeler leurs constructeurs parents avant le leur ??

Parce que je trouve ça vachement logique et quand même bien pensé pourtant...


"Si tu marques une méthode comme virtual, alors celle-ci a le droit d'être remplacé "en dessous" par la classe fille.
Ce n'est vrai que si tu fais rentrer la classe fille dans la classe mère.
Si tu utilises la classe elle même, il n'y aura jamais ambiguïté."



Je pense que c'est la que se situe l'endroit qui m'est obscure. :)
Explique moi ce que tu entends par "rentrer la classe fille dans la mère" ? (une utilisation au sein de la mère ?)


Edit :
"au sein de la mère" xD
Pas fait exprès dsl.
0
cptpingu Messages postés 3837 Date d'inscription dimanche 12 décembre 2004 Statut Modérateur Dernière intervention 28 mars 2023 123
26 nov. 2013 à 21:48
L'héritage, sans virtual, est très bien. Je n'ai jamais dit qu'il fallait l'éviter. Bien au contraire, je préfère éviter l'utilisation de virtual quand je le peux (pour des raisons que je ne vais pas décrire ici, en tout cas pas sur ce sujet, au risque d'être justement hors-sujet).

L'héritage avec et sans virtual remplissent chacun leur rôle. Ce sont deux notions différentes, et complémentaires. Ce que j'essayais d'expliquer, c'est que tu n'as besoin de "virtual" uniquement dans un seul et unique cas: pour différencier le type statique et le type dynamique (que j'ai "élégamment" appelé "faire renter la classe fille dans la classe mère").

Exemple explicatif:
Base base;

Le type statique est: Base
Le type dynamique est: Base

Deriv deriv;

Le type statique est: Deriv
Le type dynamique est: Deriv

Base* base = new Base;

Le type statique est: Base
Le type dynamique est: Base

Base* base = new Deriv;

Le type statique est: Base
Le type dynamique est: Deriv

Deriv* deriv = new Deriv;

Le type statique est: Deriv
Le type dynamique est: Deriv

Il faut bien comprendre que lorsque l'on fait Base* base = new Deriv, on instancie bien un "Deriv", mais du fait que l'on utilise le pointeur de type "Base" et non "Deriv", on "masque" le type. On ne verra de l'extérieur, que ce que "Base" et "Deriv" ont en commun. Pourtant, à l'intérieur, c'est bien un type "Deriv" que l'on a. C'est juste qu'on ne le voit pas. Le type "visible" est appelé type statique, et le type "réel" est appelé "type dynamique".

Or, vu qu'on "voit" le type comme étant un type "Base" (même si ce n'est qu'une illusion), on a besoin d'un système pour appeler la méthode "myFunc" de "Deriv" et non de "Base". C'est là que le virtual entre en jeu, et uniquement dans ce cas précis. Si ton type n'était pas masqué derrière "Base" le virtual ne te servirait pas.
0
theGrimReaper Messages postés 25 Date d'inscription dimanche 31 octobre 2010 Statut Membre Dernière intervention 18 janvier 2014
27 nov. 2013 à 13:42
oki ^^

merci beaucoup, tout est plus clair maintenant.
Donc pour un héritage simple de type statique, le "virtual" est totalement optionnel... disons qu'il ne sert pas à grand chose, à part si on fait un héritage dynamique...

P'tite question quand même, est ce que si on le met quand même et qu'au finale on ne s'en sert pas, c'est faux du point de vue de la propreté du code ?

Parce que faire du C++ c'est pas compliqué, mais je préfère prendre les bonnes habitudes de codage dès le début, pour avoir quelque chose de propre et qui respecte les règles du C++.
0

Vous n’avez pas trouvé la réponse que vous recherchez ?

Posez votre question
cptpingu Messages postés 3837 Date d'inscription dimanche 12 décembre 2004 Statut Modérateur Dernière intervention 28 mars 2023 123
27 nov. 2013 à 14:17
Je le répète parce c'est important: le mot clé "virtual" n'est pas là pour décorer et à une vraie incidence sur le code généré !
On ne le met ou omet pas par propreté mais parce c'est une vraie fonctionnalité.

Si tu n'as pas besoin de virtual, ne le met pas. Dès le moment ou tu as une méthode "virtual", une "vtable" est créée en interne. Cette vtable n'est pas gratuite en terme de performance et de consommation mémoire (il ne faut pas non plus être trop strict, on parle de dégradation extrêmement faible qui n'a d'incidence que dans des problématiques de temps réels ou de sections critiques).

La règle que j'applique est celle-ci:
- Si on a besoin de récupérer des fonctionnalités (pour factoriser), et il y a une vraie relation de parenté entre deux classes (ie des attributs en commun), et la classe mère servira de base pour rassembler plusieurs objets différents mais de même parenté => Héritage avec méthodes virtuelles.
- Si on a besoin de récupérer des fonctionnalités (pour factoriser), et il y a une vraie relation de parenté entre deux classes (ie des attributs en commun) => Héritage sans "virtual".
- Si on a besoin de récupérer des fonctionnalités (pour factoriser) => Décoration par CRTP (curiously recurring template pattern), en gros un héritage "static" (ou "faux" héritage).
0
cptpingu Messages postés 3837 Date d'inscription dimanche 12 décembre 2004 Statut Modérateur Dernière intervention 28 mars 2023 123
Modifié par cptpingu le 27/11/2013 à 14:37
Je viens de retrouver le sujet où je parlais de vtable. Il est possible de la recoder soi-même, pour bien en comprendre le fonctionnement.
(On peut alors faire de l'héritage avec du virtual, même en C... C'est juste moche, mais ça fonctionne !).

http://codes-sources.commentcamarche.net/forum/oldest/1384710-fonction-virtuelle-en-c

Je t'invite à y jeter un oeil, ça devrait t'en dire plus, sur ce qu'est la vtable (virtual table) et à quoi sert exactement le mot clé virtual.
0
theGrimReaper Messages postés 25 Date d'inscription dimanche 31 octobre 2010 Statut Membre Dernière intervention 18 janvier 2014
28 nov. 2013 à 17:37
Parfait.
Merci l'ami. ;)
0
Rejoignez-nous