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

Langage C++ Discussion :

Classe abstraite et méthodes virtuelles


Sujet :

Langage C++

  1. #1
    Membre du Club
    Profil pro
    Étudiant
    Inscrit en
    Mai 2007
    Messages
    123
    Détails du profil
    Informations personnelles :
    Âge : 38
    Localisation : France, Var (Provence Alpes Côte d'Azur)

    Informations professionnelles :
    Activité : Étudiant

    Informations forums :
    Inscription : Mai 2007
    Messages : 123
    Points : 53
    Points
    53
    Par défaut Classe abstraite et méthodes virtuelles
    Bonsoir,

    Je développe une application permettant l'affichage en OpenGL de surfaces 3D.
    J'ai codé une classe GenericSurface et des classes BezierSurface et BSplineSurface qui héritent de GenericSurface.

    Dans GenericSurface, j'ai mis :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    virtual void drawSurface() = 0;
    avec la définition spécifique à chaque classe fille. (pas de paramètres)
    Tout va bien pour l'instant, ça fonctionne.

    J'ai voulu faire pareil avec :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    virtual void computeSurface() = 0;
    virtual void computeSurfacePoint() = 0;
    en sachant que ces 2 méthodes prennent des paramètres différents selon BezierSurface ou BSplineSurface, d'où, je pense, vient mon problème.

    Etant donné que l'on retrouve ces 2 méthodes dans toutes mes classes filles de Surface, j'aurais voulu définir un prototype virtuel pour dire "cette méthode est à redéfinir dans la classe fille avec ses propres paramètres", est-ce possible ?

    J'espère avoir été clair... sinon n'hésitez pas à demander !
    Je vous remercie par avance !

  2. #2
    Membre éprouvé Avatar de Steph_ng8
    Homme Profil pro
    Doctorant en Informatique
    Inscrit en
    Septembre 2010
    Messages
    677
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 39
    Localisation : France

    Informations professionnelles :
    Activité : Doctorant en Informatique

    Informations forums :
    Inscription : Septembre 2010
    Messages : 677
    Points : 997
    Points
    997
    Par défaut
    Je n'ai jamais lu quoi que ce soit qui indique que ce soit possible, mais en même temps je ne me suis jamais posé la question…
    Ceci dit, je pense que ça dépend de la manière dont les paramètres diffèrent.

    S'il y a toujours le même nombre de paramètres, mais que leur type peut varier, tu peut utiliser les template.
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    template <typename T1, typename T2...>
    virtual
    void computeSurface(T1 arg1, T2 arg2...) = 0;
    Quoiqu'en écrivant ces lignes j'ai la vague impression d'avoir lu que « template » et « virtual » ne vont pas ensemble.
    À vérifier…

    Si tu as un nombre variable de paramètres, mais tous du même type (ou à défaut qui héritent tous de la même classe), tu peux utiliser un conteneur, std::vector ou std::map par exemple.
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    // Tous le même type
    virtual
    void computeSurface(const std::vector<UnType>& args) = 0;
     
    // Des types différents mais héritant de la même classe
    virtual
    void computeSurface(const std::vector<UnType *>& args) = 0;
    Si les paramètres varient aussi bien en nombre qu'en types, il faut aller voir du côté des fonctions avec un nombre variables d'arguments.

    J'espère t'avoir donné des pistes.

  3. #3
    Rédacteur
    Avatar de 3DArchi
    Profil pro
    Inscrit en
    Juin 2008
    Messages
    7 634
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Juin 2008
    Messages : 7 634
    Points : 13 017
    Points
    13 017
    Par défaut
    Salut,
    1/La covariance ne peut avoir lieu que sur le type retour. Donc une fonction virtuelle (pure ou non) doit avoir les mêmes paramètres pour ses arguments (et la même constance) dans la classe de base et les classes dérivées. Ce que tu souhaites faire n'est pas possible ainsi.
    2/virtuels et génériques : pas possible (cf ici)

    Une solution consiste à passer un paramètre lui aussi abstrait casté dans le type concret de la fonction dans la classe dérivée :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    struct param;
    struct base
    {
       virtual void do_it(param const&) const=0;
    };
     
     
    struct param
    {
        virtual ~param()=0;
    };
     
    struct param_derive : public param
    {
        virtual ~param_derive(){}
    };
     
    struct derive : public base
    {
       virtual void do_it(param const&p_) const
       {
           param_derive const &pd_ = dynamic_cast<param_derive const &>(p_);
           // do it
       }
    };
     
     
    void call_do_it(base const&b_, param const&p_)
    {
        b_.do_it(p_);
    }
    int main()
    {
        param_derive pd;
        derive d;
     
        call_do_it(d,pd);
     
        return 0;
    }
    En fait, tu dois pouvoir potasser le multidispatch qui semble correspondre à ton problème.

  4. #4
    Membre du Club
    Profil pro
    Étudiant
    Inscrit en
    Mai 2007
    Messages
    123
    Détails du profil
    Informations personnelles :
    Âge : 38
    Localisation : France, Var (Provence Alpes Côte d'Azur)

    Informations professionnelles :
    Activité : Étudiant

    Informations forums :
    Inscription : Mai 2007
    Messages : 123
    Points : 53
    Points
    53
    Par défaut
    Ce n'est pas réellement un problème, j'ai fait au plus simple, c'est à dire, définition classique de la méthode pour chaque classe fille, sans aucune trace dans la classe mère.
    C'est juste que comme chaque Surface fille a une méthode computeSurface(...) et computeSurfacePoint(...) mais avec des paramètres différents et un corps différent, j'aurais voulu définir un prototype dans la classe mère qui dit que je dois définir dans chaque classe fille la méthode avec ses propres paramètres et son propre corps en fonction de la classe fille.
    Ca aurait été plus "logique" pour moi d'un point de vue conceptuel.

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

    Informations professionnelles :
    Activité : aucun

    Informations forums :
    Inscription : Octobre 2004
    Messages : 11 627
    Points : 30 692
    Points
    30 692
    Par défaut
    Citation Envoyé par Daikyo Voir le message
    Ce n'est pas réellement un problème, j'ai fait au plus simple, c'est à dire, définition classique de la méthode pour chaque classe fille, sans aucune trace dans la classe mère.
    C'est juste que comme chaque Surface fille a une méthode computeSurface(...) et computeSurfacePoint(...) mais avec des paramètres différents et un corps différent, j'aurais voulu définir un prototype dans la classe mère qui dit que je dois définir dans chaque classe fille la méthode avec ses propres paramètres et son propre corps en fonction de la classe fille.
    Ca aurait été plus "logique" pour moi d'un point de vue conceptuel.
    Justement, cela n'aurait pas été logique du point de vue de la conception...

    Tu dois mettre dans la classe mère l'ensemble de l'interface qui te permet de travailler avec n'importe quelle objet des classes dérivées sans te poser de question.

    Or, si tu remarque qu'il y a une fonction qui prend des paramètres différents en fonction du type réellement manipulé, tu dois, fatalement, te poser la question du type réel de ton objet avant de pouvoir essayer d'invoquer la fonction

    Il y a, cependant, des alternatives si seul le type des arguments nécessaires est susceptible de changer, mais que leur nombre reste identique.

    Tu peux, en effet, envisager (pour autant que cela ait un sens de le faire) de créer une hiérarchie d'héritage regroupant les différents types d'argument, et de passer tes argument sous la forme de référence (ou de pointeurs sur) des objets passant pour être du type de base de cette hiérarchie:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    class ArgBase
    {
        public:
            /*interface commune minimale pour tous les arguments */
    };
    class ArgType1:public ArgBase
    {
         /* définitions des fonctions membres de ArgBase */
    };
    class ArgType2:public ArgBase
    {
         /* définitions des fonctions membres de ArgBase */
    };
    class Base
    {
        public:
            virtual void doSomething(ArgBase /* const */ & arg) = 0;
            virutal void doWithPointer(ArgBase* ptr) = 0;
    }
    class Derived1 : public Base
    {
        public:
            virtual void doSomething(ArgBase /* const */ & arg)
            {
                /* tu peux envisager de tester la conversion si tu es sur
                 * du type qui sera effectivement utilisé :D
                 */
                ArgType1 /* const */ & ref=dynamic_cast<ArgType /*const */ &>(arg);
                /* ... */
            }
            virtual void doWithPointer(ArgBase * ptr)
            {
                 /* ou décider d'utiliser directement l'interface de ArgBase si
                  * elle te suffit, et si le type réellement utilisé
                  * n'a pas trop d'importance
                  */
                  ptr->blabla();
            }
    };
    (NOTA: les commentaires s'appliquent dans les deux cas... je voulais juste montrer que c'est possible pour les références comme pour les pointeurs )

    Une autre possibilité consiste à passer tes argument (au niveau du prototype de la fonction membre) sous la forme d'un type qui puisse littéralement passer pour n'importe quel autre type, tel que boost::any ou boost::variant.

    Il t'appartiendra alors, au niveau des fonctions membres des classes dérivées, de veiller à convertir l'argument reçu dans... le type d'objet dont tu as réellement besoin

    Mais, là encore, il est utile de vérifier que l'argument transmis à ce que tu attends réellement

  6. #6
    En attente de confirmation mail

    Homme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Août 2004
    Messages
    1 391
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 34
    Localisation : France, Doubs (Franche Comté)

    Informations professionnelles :
    Activité : Ingénieur développement logiciels

    Informations forums :
    Inscription : Août 2004
    Messages : 1 391
    Points : 3 311
    Points
    3 311
    Par défaut
    Le genre de changement de paramètre qui aurait du sens et qui n'est pas possible en C++ ca sera les type de paramètre contravariant (pas certain que c'est le bon terme). C'est à dire l'inverse de ce qui est possible pour les types de retour, c'est à dire changer le type de paramètre en un tpe mère (pointeur ou référence comme avant). Pour le faire, la méthode qui propose koala fonction mais en faisant correspondre correctement les deux hiérarchie (pas quelconque).
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
     
    struct Type1 {};
    struct Type2 : Type1 {};
    struct A
    { virtual void foo(Type1& t){ Type2& p = dynamic_cast<Type2&>(t); } };
    struct B : A
    { virtual void foo(Type1&){} };
    Le dynamic_cast permet de manipuler dans la suite des objets du type2 et force en même temps à ce qu'on ne puisse pas passer d'objet de type1.

    Dans ce sens là, le changement de type ne pose pas de problème (si le contexte convient bien entendue), mais il en poserais dans l'autre sens.
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
     
    struct Type1 {};
    struct Type2 : Type1 {};
    struct A
    { virtual void foo(Type1&){} };
    struct B : A
    { virtual void foo(Type1& t){Type2& p = dynamic_cast<Type2&>(t);} };
    Ici le changement de type est dans le même sens que pour le type de retour covariant. Le problème est qu'un code du genre :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
     
    void goo(A& a)
    { Type1 t(); a.foo(t); }
    Ne devrait pas poser de problème, je regarde l'interface de A et je vois que je peus lui envoyer un message foo acompagné d'un paramètre de type Type1, c'est ce que je fais. Le problème est que le LSP(*) me dit aussi que je doit pouvoir utiliser un objet de type T2 partout où j'attend un T1 (car T2 est un sous-ype de T1). Or si je le fait ca va me produire une bel erreure, qui ne se produit pas si le changement des types des paramètre sont dans l'autre sens.

    (*) C'est directement lié au LSP, cf l'article de Emmanuel Deloget sur son blog, restreintre le type des paramètre à un sous-type revient à restreintre les pré-conditions de la fonction, ce qui est un viol direct, alors que l'étendre à un type mère affaiblie les pré-conditions, ce qui ne pose aucun problème. Le type de retour quand à lui rentre dans les post-condition qui doivent être plus fortes.

    Si ton objectif est plus de déterminer quel fonction appeller parmie une collection de fonctions (pour des types différents), alros regardes du coté du multi-dispatch. (cf Loki)

  7. #7
    Membre du Club
    Profil pro
    Étudiant
    Inscrit en
    Mai 2007
    Messages
    123
    Détails du profil
    Informations personnelles :
    Âge : 38
    Localisation : France, Var (Provence Alpes Côte d'Azur)

    Informations professionnelles :
    Activité : Étudiant

    Informations forums :
    Inscription : Mai 2007
    Messages : 123
    Points : 53
    Points
    53
    Par défaut
    Bonjour,

    Je reviens sur mon problème qui a plus ou moins évolué, voici la situation :

    J'ai une classe abstraite GenericSurface et deux classes qui héritent de celle-ci : BezierSurface et BSplineSurface.
    La classe GenericSurface est abstraite car j'ai défini un prototype en virtuel pur :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    virtual void drawSurface() = 0;
    Aucun problème jusque là.

    Si j'ai fait de l'héritage, c'est parce que j'ai besoin de gérer une liste de surfaces différentes (de BezierSurface et de BSplineSurface donc) dans une autre classe SurfaceManager. J'ai fait donc une liste de cette manière :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    std::vector<GenericSurface*> surfacesList;

    Je reviens maintenant à mes classes filles BezierSurface et BSplineSurface, elles possèdent toutes les deux la méthode suivante :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    void computeSurface(...);
    mais avec des paramètres différents et un corps de fonction différent.

    Or, je veux pouvoir l'utiliser à partir de la liste, donc de cette manière :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    surfacesList[i]->computeSurface(...);
    Et pour que cela puisse se faire, je dois définir le prototype de cette fonction en virtuelle pure dans la classe mère GenericSurface. Et c'est là où ça coince car je dois définir deux prototypes en virtuel pur dans la classe mère d'une fonction différente selon la classe fille.

    J'ai "contourné" le problème de la manière suivante :

    Dans GenericSurface :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    virtual void computeSurface(int _nBP) = 0; // Utile à BezierSurface
    virtual void computeSurface(int _nBP, int _degreU, int _degreV) = 0; // Utile à BSplineSurface

    Dans BezierSurface :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    public void computeSurface(int _nBP); // Utile à BezierSurface, corps de la méthode rempli avec ce qu'il faut
    private virtual void computeSurface(int _nBP, int _degreU, int _degreV); // Inutile pour BezierSurface, corps de la méthode VIDE

    Dans BSplineSurface :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    public void computeSurface(int _nBP = 50, int _degreU = 3, int _degreV = 3); // Utile à BSplineSurface, corps de la fonction rempli avec ce qu'il faut
    private virtual void computeSurface(int _nBP); // Inutile pour BSplineSurface, corps de la fonction VIDE

    Ce bricolage semble fonctionner mais ça ne me plaît pas trop... Si vous avez une solution plus "propre", je suis preneur...

    Je vous remercie par avance !

  8. #8
    Rédacteur
    Avatar de 3DArchi
    Profil pro
    Inscrit en
    Juin 2008
    Messages
    7 634
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Juin 2008
    Messages : 7 634
    Points : 13 017
    Points
    13 017
    Par défaut
    Salut,
    Le changement de visibilité n'a aucune influence sur l'appel à partir d'un pointeur de la classe de base.
    Question : dans l'appel surfacesList[i]->computeSurface(...);, comment sais-tu quel jeu de paramètres tu dois fournir ?

  9. #9
    Membre du Club
    Profil pro
    Étudiant
    Inscrit en
    Mai 2007
    Messages
    123
    Détails du profil
    Informations personnelles :
    Âge : 38
    Localisation : France, Var (Provence Alpes Côte d'Azur)

    Informations professionnelles :
    Activité : Étudiant

    Informations forums :
    Inscription : Mai 2007
    Messages : 123
    Points : 53
    Points
    53
    Par défaut
    Bonne question... Je n'y avais pas réfléchi...
    Je suis d'autant plus embêté maintenant...

  10. #10
    Rédacteur
    Avatar de 3DArchi
    Profil pro
    Inscrit en
    Juin 2008
    Messages
    7 634
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Juin 2008
    Messages : 7 634
    Points : 13 017
    Points
    13 017
    Par défaut
    Probablement que cette réponse t'apportera des informations pertinentes sur le design à mettre en oeuvre avec l'abstraction appropriée.

  11. #11
    Membre du Club
    Profil pro
    Étudiant
    Inscrit en
    Mai 2007
    Messages
    123
    Détails du profil
    Informations personnelles :
    Âge : 38
    Localisation : France, Var (Provence Alpes Côte d'Azur)

    Informations professionnelles :
    Activité : Étudiant

    Informations forums :
    Inscription : Mai 2007
    Messages : 123
    Points : 53
    Points
    53
    Par défaut
    Conception revue et corrigée => problem solved

    Après réflexion, je n'ai pas besoin de passer des paramètres donc prototype commun en virtuel pur donc redéfinition facile dans les classes filles.

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

Discussions similaires

  1. Interfaces/Classes abstraites et méthodes statiques
    Par Zakapatul dans le forum VB.NET
    Réponses: 7
    Dernier message: 06/01/2009, 14h38
  2. Réponses: 15
    Dernier message: 05/07/2007, 01h29
  3. Réponses: 23
    Dernier message: 16/03/2007, 20h21
  4. Réponses: 14
    Dernier message: 17/11/2006, 19h17
  5. Réponses: 6
    Dernier message: 27/07/2005, 09h06

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