IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)
Navigation

Inscrivez-vous gratuitement
pour pouvoir participer, suivre les réponses en temps réel, voter pour les messages, poser vos propres questions et recevoir la newsletter

C++ Discussion :

Héritage et Polymorphisme - Peut-on éviter un dynamic cast ?


Sujet :

C++

  1. #1
    Membre à l'essai
    Étudiant
    Inscrit en
    Juin 2008
    Messages
    32
    Détails du profil
    Informations professionnelles :
    Activité : Étudiant

    Informations forums :
    Inscription : Juin 2008
    Messages : 32
    Points : 21
    Points
    21
    Par défaut Héritage et Polymorphisme - Peut-on éviter un dynamic cast ?
    Bonjour

    J'ai un petit problème avec une structure Vector que je suis en train de faire. J'ai donc une structure Vector :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
     
    struct Vector
    {
        private :
           ...
        public :
            ...
     
            double getNorm() const; 
            Vector * getUnitVector() const;
    };
    J'ai également une structure Vector2D qui hérite de ma structure Vector :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
     
    struct Vector2D : public Vector
    {
        private :
           ....
        public :
           ....
     
    };
    Dans la structure Vector se trouve une méthode getUnitVector() qui retourne un vecteur unitaire correspondant au vecteur interne.

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
     
    Vector * Vector::getUnitVector()
    {
        // Create the unit length vector
        Vector * unitVector =  (1.0 / this->getNorm()) * this;
     
        // Return the unit length vector
        return unitVector;
    }
    Mon problème est que j'aimerais avoir une expression du genre :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
     
    Vector2D * vect = new Vector2D();
    Vector2D * vect2 = vect->getUnitVector();
    Le problème est que la méthode getUnitVector() se trouve dans la classe Vector et retourne donc un pointeur Vector * et donc la deuxième instruction pose problème car un Vector * ne peut pas être assigné à un Vector2D *. Il faudrait ici un "dynamic cast". Mais je ne trouve pas vraiment beau d'utiliser dynamic cast et j'imagine qu'il doit exister quelque chose de beaucoup plus propre pour ce genre de problème qui me parait assez classique.

    Si quelqu'un saurait comment si prendre dans ce genre de situation.

  2. #2
    Membre averti Avatar de zabibof
    Inscrit en
    Février 2007
    Messages
    188
    Détails du profil
    Informations forums :
    Inscription : Février 2007
    Messages : 188
    Points : 344
    Points
    344
    Par défaut
    C'est un avis personnel mais je ne pense pas que l'héritage soit une bonne idée pour les classes de vecteurs. Si tu veux un Vecteur2D, tu fais un Vecteur2D à part, si tu veux un Vecteur3D, tu le fais à part.

  3. #3
    Membre éprouvé
    Avatar de Spout
    Profil pro
    Ingénieur systèmes et réseaux
    Inscrit en
    Février 2007
    Messages
    904
    Détails du profil
    Informations personnelles :
    Âge : 39
    Localisation : France, Val d'Oise (Île de France)

    Informations professionnelles :
    Activité : Ingénieur systèmes et réseaux

    Informations forums :
    Inscription : Février 2007
    Messages : 904
    Points : 1 067
    Points
    1 067
    Par défaut
    Le seul moyen d'éviter le cast dynamique en gardant l'héritage est de sucharger la méthode getUnitVector() dans la classe enfant en renvoyant un VectorD*. Il ne restera plus qu'a mettre ensuite un virtual devant la méthode de la classe mère, pour indiquer que si c'est la classe enfant qui est instanciée, c'est la méthode de celle-ci qui sera appelée et non celle de la classe mère.
    Euh.. c'est clair ce que je dis ?
    "L'ordinateur obéit à vos ordres, pas à vos intentions." [Anonyme]

  4. #4
    Expert éminent sénior
    Avatar de koala01
    Homme Profil pro
    aucun
    Inscrit en
    Octobre 2004
    Messages
    11 614
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 52
    Localisation : Belgique

    Informations professionnelles :
    Activité : aucun

    Informations forums :
    Inscription : Octobre 2004
    Messages : 11 614
    Points : 30 626
    Points
    30 626
    Par défaut
    Salut,

    Déjà, je vais commencer par attirer ton attention sur les risques liés à l'utilisation de pointeurs...

    Quand tu écris ta méthode
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
     
    Vector * Vector::getUnitVector() 
    {
        // Create the unit length vector
        Vector * unitVector =  (1.0 / this->getNorm()) * this;
     
        // Return the unit length vector
        return unitVector;
    }
    tu lui demande de... renvoyer un pointeur.

    Et mieux, tu déclare unitVector comme tel.

    Mais, je serais surpris que ta fonction fasse ce que tu espère réellement:

    En effet, this est ... un pointeur sur la structure en cours d'utilisation, et un pointeur est - faut il le rappeler - ...l'adresse à laquelle on trouve... l'élément réel.
    Aussi quand tu calcule (1.0/ this->getNorm()), il n'y a rien d'incorrect (du moins, du seul point de vue de la présentation, même si tu aurais très bien pu t'éviter l'écriture de this->).

    Par contre, comme this est un pointeur, et qu'un pointeur est une adresse (donc, en définitive, une valeur numérique); quand tu multiplie (1.0/getNorm()) par... la valeur de this, je serais déjà surpris que le compilateur accepte sans rechigner :

    D'abord, parce que la multiplication d'un dougle par un entier donne... un double

    Ensuite, étant donné que tu assigne le résultat à un pointeur, cela implique que le résultat devrait être... une adresse mémoire, et une adresse mémoire est généralement du genre "entier non signé" (int ou long, voir long long, cela n'a qu'un intérêt limité ici ) mais, en aucun cas du genre "réel".

    Enfin, si même ton compilateur a accepté ce mic-mac de passages d'entiers à réels puis de réels à entiers sans rechigner (ce que je ne te souhaite nullement), il faut bien coprendre qu'à la première tentative d'appel d'une des méthodes du résultat, il peut strictement tout arriver:

    En effet, il n'y a aucun moyen de savoir ce qui se trouve à ce qui est devenue l'adresse mémoire calculée...: c'est peut être une adresse mémoire inutilisée, mais, c'est peut être l'adresse appelée avec un format c: (ou équivalent) ou... une instruction qui sera considérée par le processeur comme le fait d'ouvrir un port particulier à une adresse IP qui correspond - justement, c'est vraiment pas de bol - hyper secrète du pentagone, et à envoyer une série de codes qui auront pour résultat - on n'a vraiment pas de chance - de lancer une attaque nucléaire sur moscou...

    Car il faut bien savoir que la structure interne d'un processeur même fait qu'il lui est impossible de savoir si une valeur est à considérer comme valeur ou comme instruction.

    Sa méthode de travail est en effet de se dire "je lis une information, et je la considère comme instruction, puis, si c'est une intruction qui nécessite une information suppélementaire, je lis cette information afin de pouvoir effectuer l'instruction" (en très gros, très schématique quand même), ce qui rend possible cette hypothèse oh combien pessimiste...

    Normalement, un compilateur bien réglé aurait au minimum du t'indiquer que ton calcul effectuait des conversions implicites de pointeurs en entiers (en réels).

    L'idée est donc, si tu veux renvoyer un pointeur, d'effectuer l'allocation dynamqiue de la mémoire en fournissant en paramètres du constructeur la valeur qui va bien, et qui peut être calculée sous une forme plus proche de
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    Vector * Vector::getUnitVector()
    {
        // Create the unit length vector
        Vector * unitVector = new Vector ((1.0 / getNorm()) * le_bon_membre);
     
        // Return the unit length vector
        return unitVector;
    }
    ou le_bon_membre correspond à la valeur à utiliser pour la multiplication.

    Evidemment, s'agissant d'une allocation dynamique de la mémoire, il ne faudra pas oublier d'en prévoir la libération au moment où tu n'a plus besoin de la variable

    Maintenant, il est temps de parler un peu du problème qui est le tien: le polymorphisme (ca va faire une intervention vraiment longue )

    L'idée est relativement simple: tu signale dés la déclaration de la méthode au compilateur que "attention, cette méthode aura un comportement qui dépend du type réel de l'objet au départ duquel elle est invoquée".

    Cela se fait en créant ce que l'on appelle une fonction virtuelle.

    Plus concrètement, il "suffit" de rajouter le mot clé virtual avant le prototype de la fonction.

    Cela pourrait se traduire dans ta classe Vector sous une forme proche de
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    struct Vector
    {
        private :
           ...
        public :
            ...
     
            double getNorm() const; 
            virtual Vector * getUnitVector() const;
    };
    avec l'implémentation "normale"
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    Vector * Vector::getUnitVector()
    {
        // Create the unit length vector
        Vector * unitVector = new Vector ((1.0 / getNorm()) * le_bon_membre);
     
        // Return the unit length vector
        return unitVector;
    }
    et, pour indiquer que tu veux redéfinir le comportement de getUnitVetor pour ta classe Vector2D, il "suffit" de redéclarer cette méthode au sein de la classe en question, sous la forme de
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    class Vector2D : public Vector
    {
        /*... tout ce qui est intéressant ;) */
            virtual Vector2D* getUnitVector() const;
    };
    sans oublier, cela va de soi, l'implémentation qui va avec

    avec l'implémentation "modifiée"
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    /* on en profite pour avoir un "retour co variant" */
    Vector2D * Vector::getUnitVector()
    {
        // Create the unit length vector
        Vector2D * unitVector = new Vector2D (/*paramètres de création */);
     
        // Return the unit length vector
        return unitVector;
    }
    Au final, quand tu écrira le code
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    int main()
    {
        Vector* v = new Vector2D(/*parametres*/);
        Vector2D unit = v->getVectorUnit();
        /*...*/
        delete unit;
        delete v;
        return 0;
    }
    ce sera bel et bien Vector2D::getVectorUnit qui sera appelée ;à
    A méditer: La solution la plus simple est toujours la moins compliquée
    Ce qui se conçoit bien s'énonce clairement, et les mots pour le dire vous viennent aisément. Nicolas Boileau
    Compiler Gcc sous windows avec MinGW
    Coder efficacement en C++ : dans les bacs le 17 février 2014
    mon tout nouveau blog

  5. #5
    Membre à l'essai
    Étudiant
    Inscrit en
    Juin 2008
    Messages
    32
    Détails du profil
    Informations professionnelles :
    Activité : Étudiant

    Informations forums :
    Inscription : Juin 2008
    Messages : 32
    Points : 21
    Points
    21
    Par défaut
    Merci beaucoup pour vos réponses.

    Pour l'instruction :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
     
    // Create the unit length vector
    Vector * unitVector =  (1.0 / this->getNorm()) * this;
    J'aurais peut-être du préciser que j'ai redéfinit l'opérateur * pour la multiplication entre un nombre scalaire et un vecteur. L'operateur redéfinit * prend donc comme argument un double (1.0 / getNorm()) et un pointeur vers un Vector afin d'avoir des opérations du genre nb * pVecteur qui permet de retourner un nouveau vecteur dont les composantes ont été multipliées par le nombre nb. C'est clair que sinon multiplier un pointeur avec un double et l'assigner à un autre pointeur paraît plus que dangereux.

    Pour mon problème de polymorphisme et d'héritage, merci beaucoup à vous deux. Effectivement je n'avais pas pensé à cette solution.

  6. #6
    Expert éminent sénior
    Avatar de koala01
    Homme Profil pro
    aucun
    Inscrit en
    Octobre 2004
    Messages
    11 614
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 52
    Localisation : Belgique

    Informations professionnelles :
    Activité : aucun

    Informations forums :
    Inscription : Octobre 2004
    Messages : 11 614
    Points : 30 626
    Points
    30 626
    Par défaut
    Citation Envoyé par Dani3L Voir le message
    Merci beaucoup pour vos réponses.

    Pour l'instruction :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
     
    // Create the unit length vector
    Vector * unitVector =  (1.0 / this->getNorm()) * this;
    J'aurais peut-être du préciser que j'ai redéfinit l'opérateur * pour la multiplication entre un nombre scalaire et un vecteur. L'operateur redéfinit * prend donc comme argument un double (1.0 / getNorm()) et un pointeur vers un Vector afin d'avoir des opérations du genre nb * pVecteur qui permet de retourner un nouveau vecteur dont les composantes ont été multipliées par le nombre nb. C'est clair que sinon multiplier un pointeur avec un double et l'assigner à un autre pointeur paraît plus que dangereux.
    Oui, mais, quand tu nous aura tout dis... on saura tout

    Quoi qu'il en soit, le problème reste entier: Vector* unitVector déclare un pointeur, la fonciton renvoi un pointeur de type Vector* (ou Vector2D*), et, dés lors, il faut:
    • soit prendre l'adresse d'un membre existant (ce qui n'est quand même pas conseillé)
    • soit allouer dynamiquement de la mémoire pour un nouvel objet (car prendre l'adresse d'une variable temporaire, c'est du suicide)... en n'oubliant pas qu'il faut libérer cette mémoire avant de perdre toute référence dessus

    Pour mon problème de polymorphisme et d'héritage, merci beaucoup à vous deux. Effectivement je n'avais pas pensé à cette solution.
    Et pourtant: c'est la solution la plus simple que l'on puisse envisager (cf ma signature )
    A méditer: La solution la plus simple est toujours la moins compliquée
    Ce qui se conçoit bien s'énonce clairement, et les mots pour le dire vous viennent aisément. Nicolas Boileau
    Compiler Gcc sous windows avec MinGW
    Coder efficacement en C++ : dans les bacs le 17 février 2014
    mon tout nouveau blog

  7. #7
    Membre à l'essai
    Étudiant
    Inscrit en
    Juin 2008
    Messages
    32
    Détails du profil
    Informations professionnelles :
    Activité : Étudiant

    Informations forums :
    Inscription : Juin 2008
    Messages : 32
    Points : 21
    Points
    21
    Par défaut
    Citation Envoyé par koala01 Voir le message
    Quoi qu'il en soit, le problème reste entier: Vector* unitVector déclare un pointeur, la fonction renvoi un pointeur de type Vector* (ou Vector2D*), et, dés lors, il faut:
    • soit prendre l'adresse d'un membre existant (ce qui n'est quand même pas conseillé)
    • soit allouer dynamiquement de la mémoire pour un nouvel objet (car prendre l'adresse d'une variable temporaire, c'est du suicide)... en n'oubliant pas qu'il faut libérer cette mémoire avant de perdre toute référence dessus
    Effectivement c'est la deuxième solution que j'utilise. Car la fonction de surcharge de l'opérateur * crée dynamiquement un nouveau Vector (qui correspond au vecteur multiplié par la valeur scalaire) et retourne son pointeur.

  8. #8
    Expert éminent sénior
    Avatar de koala01
    Homme Profil pro
    aucun
    Inscrit en
    Octobre 2004
    Messages
    11 614
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 52
    Localisation : Belgique

    Informations professionnelles :
    Activité : aucun

    Informations forums :
    Inscription : Octobre 2004
    Messages : 11 614
    Points : 30 626
    Points
    30 626
    Par défaut
    Et si, la prochaine fois, tu essayais de nous fournir toutes les informations d'un seul coup

    Comme beaucoup d'intervenants sur le forum, j'ai d'énormes problèmes avec ma boule de crystal pour l'instant, et, de ce fait, il ne nous est possible que de nous baser sur le code que tu voudra bien nous donner... ou sur les informations supplémentaires qui nous permettront de "valider" la logique du code.

    Concrètement, si je l'avais su, j'aurais gagné un bon quart d'heure en évitant d'écrire la tirade sur les pointeurs
    A méditer: La solution la plus simple est toujours la moins compliquée
    Ce qui se conçoit bien s'énonce clairement, et les mots pour le dire vous viennent aisément. Nicolas Boileau
    Compiler Gcc sous windows avec MinGW
    Coder efficacement en C++ : dans les bacs le 17 février 2014
    mon tout nouveau blog

  9. #9
    Membre à l'essai
    Étudiant
    Inscrit en
    Juin 2008
    Messages
    32
    Détails du profil
    Informations professionnelles :
    Activité : Étudiant

    Informations forums :
    Inscription : Juin 2008
    Messages : 32
    Points : 21
    Points
    21
    Par défaut
    Encore désolé. Je ne pensais pas que cette surcharge d'opérateur était très importante concernant mon problème d'héritage et de polymorphisme.

    J'ai retenu le conseil.

    Merci beaucoup en tous cas de m'avoir répondu.

  10. #10
    Expert éminent sénior
    Avatar de koala01
    Homme Profil pro
    aucun
    Inscrit en
    Octobre 2004
    Messages
    11 614
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 52
    Localisation : Belgique

    Informations professionnelles :
    Activité : aucun

    Informations forums :
    Inscription : Octobre 2004
    Messages : 11 614
    Points : 30 626
    Points
    30 626
    Par défaut
    A vrai dire, la surcharge de l'opérateur n'est pas importante pour le problème de polymorphisme...

    Le problème, c'est que, si tu ne nous parle pas de cette surcharge, et que tu ne nous dis pas qu'en plus, elle effectue une allocation dynamique de la mémoire, on ne peut se baser que sur la partie de code que tu donne... avec la réflexion "logique" du "houlllaaa... il multiplie un réel par un pointeur et assigne le résultat à un pointeur ca compile, ca "

    En gros, quand tu fourni un code, essaye de t'arranger pour que ce soit un CMC (Code minimum Compilable), de manière à nous permettre:
    • d'avoir toutes les cartes en main pour "valider" le code
    • de reproduire le résultat et le comparer (éventuellement) à ce que tu t'attendais à obtenir

    [EDIT] En outre, je viens de réaliser que cette surcharge de l'opérateur * est bien mal venue...

    En effet, il n'y a aucun moyen de savoir si un pointeur passant pour un "pointeur sur Vector" est bel et bien un "pointeur sur Vector" et non un "pointeur sur Vector2D" étant donné que ce deuxième type dérive (de manière directe ou indirecte) du premier...

    Si même tu implémentais une nouvelle surcharge de l'opérateur en vue de permettre l'utilisation d'un pointeur sur Vector2D, le compilateur se plaindrait de l'ambiguité lorsque tu invoquerais la méthode Vector2D* Vector2D::getUnitVector()
    A méditer: La solution la plus simple est toujours la moins compliquée
    Ce qui se conçoit bien s'énonce clairement, et les mots pour le dire vous viennent aisément. Nicolas Boileau
    Compiler Gcc sous windows avec MinGW
    Coder efficacement en C++ : dans les bacs le 17 février 2014
    mon tout nouveau blog

  11. #11
    Membre chevronné
    Avatar de poukill
    Profil pro
    Inscrit en
    Février 2006
    Messages
    2 155
    Détails du profil
    Informations personnelles :
    Âge : 40
    Localisation : France

    Informations forums :
    Inscription : Février 2006
    Messages : 2 155
    Points : 2 107
    Points
    2 107
    Par défaut
    En général le downcasting peut avantageusement être remplacer par un visiteur, ou du double dispatch etc... J'avais déjà posé la question dessus il y a quelques mois, tu pourras y trouver une partie de ton bonheur !

+ Répondre à la discussion
Cette discussion est résolue.

Discussions similaires

  1. héritage et polymorphisme
    Par davdou dans le forum JSF
    Réponses: 2
    Dernier message: 23/11/2007, 09h51
  2. [C#] Information sur héritage et polymorphisme
    Par LE NEINDRE dans le forum C#
    Réponses: 21
    Dernier message: 14/06/2007, 11h00
  3. Réponses: 19
    Dernier message: 05/06/2007, 08h13
  4. Réponses: 20
    Dernier message: 22/05/2007, 17h52
  5. Réponses: 5
    Dernier message: 20/10/2006, 13h16

Partager

Partager
  • Envoyer la discussion sur Viadeo
  • Envoyer la discussion sur Twitter
  • Envoyer la discussion sur Google
  • Envoyer la discussion sur Facebook
  • Envoyer la discussion sur Digg
  • Envoyer la discussion sur Delicious
  • Envoyer la discussion sur MySpace
  • Envoyer la discussion sur Yahoo