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 :

[C++] Void* et fonctions virtuelles


Sujet :

C++

  1. #1
    Expert confirmé
    Avatar de Aspic
    Homme Profil pro
    Étudiant
    Inscrit en
    Août 2005
    Messages
    3 905
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Hauts de Seine (Île de France)

    Informations professionnelles :
    Activité : Étudiant
    Secteur : High Tech - Électronique et micro-électronique

    Informations forums :
    Inscription : Août 2005
    Messages : 3 905
    Points : 4 388
    Points
    4 388
    Par défaut [C++] Void* et fonctions virtuelles
    Bonjour à tous,

    Je reviens vers vous pour un conseil.
    Soit les classes suivantes (j'ai supprimé le constructeur et destructeur pour une meilleure visibilité) :
    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
     
    class t_item
    {
    public:
    virtual int gerer(???)=0;
    };
     
    class t_item1 : public t_item
    {
    public:
    int gerer(???);
    };
     
    // dans le .cpp
    t_item1::gerer(???)
    {
    /* code à executer propre à l'item1 */
    }
     
    class t_item2 : public t_item
    {
    public:
    int gerer(???);
    };
     
    // dans le .cpp
    t_item2::gerer(???)
    {
    /* code à executer propre à l'item2 */
    }
    Le problème c'est qu'en fonction de mes items, je dois passer des paramètres différents (par exemple pour l'item1, je dois avoir un int et un char alors que pour l'item2 j'ai besoin de 3 int et d'un tableau de float)

    Etant donné que je peux pas surcharger ma fonction virtuelle pure en X fonctions, j'ai eu l'idée de faire passé un void* et comme ca, je peux encapsuler ce que je veux dedans dont une structure paramètres qui contient les paramètres que je veux.

    Mais est ce une bonne idée ? N'y a t-il pas une autre solution car cette solution me semble bancale

    Merci !
    Qui ne tente rien n'a rien !
    Ce qui ne nous tue pas nous rends plus fort !!
    Mon projet ZELDA en C++/Allegro
    http://www.tutoworld.com - Le Forum -
    Mes ressources Dotnet (cours, sources, tutos)
    --------------------------------------------
    + + =

    Ne pas oublier le Tag !

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

    Informations professionnelles :
    Activité : aucun

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

    De manière générale, ce n'est jamais une bonne idée de passer un void *.

    Tu devrais déjà te demander s'il est cohérent de créer une fonction virutelle et / ou de recourir à l'héritage dans ton cas.

    Le fait que deux classes exposent une fonction portant le même nom n'est en effet pas forcément suffisant pour décider de les introduire dans une hiérarchie commune, surtout si la fonction en question demande des paramètres différents

    La première question que tu devrais donc te poser est "est-ce que t_item1 et t_item2 héritent réellement de t_item"

    Tu pourras envisager de répondre oui à cette question si tu souhaite avoir la possibilité de faire cohabiter des t_item1 et des t_item2 dans une seule et même collection de pointeurs sur t_item. Autrement, il y a de fortes chances pour que tu sois simplement face à deux classes qui exposent une fonction portant le même nom

    S'il est, effectivement, cohérent d'envisager de faire cohabiter des t_item1 et des t_item2 dans une même collection, tu devrais envisager de créer également une hiérarchie pour les données que tu souhaites utiliser, et sans doute recourir au DP visiteur, au pimpl idome ou au double dispatch pour obtenir le résutat souhaité .
    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

  3. #3
    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 : 33
    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
    On pourrait forcer un espèce de polymorphisme avec changement des paramètres de la fonction en jouant avec boost::any et les template (variadique).

    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
     
    struct A
    {
      template<class... ArgTypes>
      void foo(ArgTypes... arg)
      { accept(boost::any(std::make_tuple(arg...))); }
      virtual void accept(boost::any&&) =0;
    };
     
    struct B : A
    {
      void goo(int i) { std::cout << i << std::endl; }
      void accept(boost::any&& a)
      {
        std::tuple<int>* p = boost::any_cast<std::tuple<int> >(&a);
        if (p) goo(std::get<0>(*p));
      }
    };
    (C'est un premier jet, imparfait et ca peut très vite devenir assez lourd comme système ...)

    Mais il faut déjà que tu te demandes pourquoi tu veux faire ca, et la réponse à cette question te conduira surment à utiliser un pattern déjà connue, genre visiteur. L'idée d'avoir une fonction virtuelle avec des paramètres qui changent (beaucoup) violerait le LSP.

  4. #4
    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,
    Si ta fonction gérer est virtuelle, c'est que tu souhaites l'appeler de façon polymorphe avec un type statique sur le type de base. Donc, au moment de l'appel, tu ne sais pas quel est le type dynamique de l'objet effectivement référencé (ou pointé). Comment vas-tu choisir alors le jeu de paramètre à fournir ? Comment sauras-tu qu'il s'agit d'un int et d'un char ou bien de 3 int ou bien de tout autre chose ? La réponse à cette question devrait soit te donner la signature si le jeu de paramètre le permet, soit te démontrer qu'il ne s'agit pas d'un appel virtuel car tu as besoin de connaître le type dérivé (et donc qu'il faut probablement revoir certains éléments d'architecture).

  5. #5
    Expert confirmé
    Avatar de Aspic
    Homme Profil pro
    Étudiant
    Inscrit en
    Août 2005
    Messages
    3 905
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Hauts de Seine (Île de France)

    Informations professionnelles :
    Activité : Étudiant
    Secteur : High Tech - Électronique et micro-électronique

    Informations forums :
    Inscription : Août 2005
    Messages : 3 905
    Points : 4 388
    Points
    4 388
    Par défaut
    Merci pour toutes vos réponses.

    Effectivement au vu de vos remarques cela ne semble pas la bonne solution de passer par une fonction virtuelle mais comme Kaola me le demandait, l'héritage dans mon cas est préconisé (ou alors j'ai rien compris au principe de l'héritage ). Je m'explique :

    Soit la classe mère abstraite t_item.
    Soit quatre classes filles t_item_epee, t_item_boomerang, t_item_grappin et t_item_pelle qui héritent de t_item.
    Soit deux fonctions de gestion des items :"gerer(???)" et afficher(BITMAP* zoneDeJeu)".

    L'épée doit connaitre : le masque de collision (type BITMAP*), les ennemies (type : vector<t_ennemie*>), la position de certains objets qui se détruisent à l'épée (type : vector<t_herbe*> ...)
    Le boomerang doit connaitre : le masque de collision (type BITMAP*), les ennemies (type : vector<t_ennemie*>), les interrupteurs (non codés)
    La grappin doit connaitre : le masque de collision (type BITMAP*), les ennemies (type vector<t_ennemie*>), les objets auxquels il peut s'accrocher (non codés encore...)
    La pelle doit connaitre : le masque de collision et c'est tout !

    Donc on voit bien que les paramètres sont différents dans chaque cas sauf le premier qui est commun à tout le monde.

    Le principe est qu'on a un inventaire avec les quatre items ci dessus par exemple mais on ne peut en assigner que deux à la fois (un dans le bouton A et un autre dans le bouton B)

    Dans mon moteur de jeu, voilà l'appel :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
     
    itemSelectionneBoutonA->gerer(???);
    itemSelectionneBoutonB->gerer(???);
    Et dans le moteur d'affichage :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
     
    itemSelectionneBoutonA->afficher(zoneJeu);
    itemSelectionneBoutonB->afficher(zoneJeu);
    PS : l'affichage est séparé entièrement de la gestion mémoire des entités du jeu

    Voilà, je ne sais pas si je fais complètement fausse route ou pas. J'ai regardé un peu le DP visiteur mais je vous avoue que je ne comprends pas son fonctionnement et je n'ai pas trouvé d'implémentation en code simple pour comprendre le fonctionnement car les diagrammes UML...

    Mais même, je ne vois pas comment ce DP pourrait m'aider dans ma situation

    Merci à vous, j'espère avoir été clair cette fois
    Qui ne tente rien n'a rien !
    Ce qui ne nous tue pas nous rends plus fort !!
    Mon projet ZELDA en C++/Allegro
    http://www.tutoworld.com - Le Forum -
    Mes ressources Dotnet (cours, sources, tutos)
    --------------------------------------------
    + + =

    Ne pas oublier le Tag !

  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 : 33
    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
    Bonjour,

    Comme la souligné 3DArchi, si tu peux faire ca c'est que tu sais quels paramètres donnés, autrement dit que tu as une idée du type réel de l'objet, ce qui n'est pas le cas général quand tu utilises le polymorphisme.

    En général tout ce que tu as c'est un objet dont tu ne connais pas le type dynamique, et c'est justement le fait d'avoir une fonction virtuelle qui permet d'avoir un appel qui dépend du type réel. (Exemple typique : un conteneur de Mere* à travers lequel tu itères en appellant la fonction virtuelle foo.

    Les visiteurs servent à visiter une hiérarchie, chaques classes de la hiérarchie acceptent de recevoir un visiteur, et ceux-ci execute une action différente selon le type réel de l'objet (on peut passer par un dynamic_cast pour le récupérer dans un visiteur acyclique, sinon je te laisse chercher pour le visiteur "classique")
    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
     
    struct BaseVisitor { virtual ~BaseVisitor() =0; };
    BaseVisitor::~BaseVisitor(){}
     
    template<class T>
    struct Visitor
    {
        virtual ~Visitor(){}
        virtual void visit(T&) =0; 
    };
     
    struct Item
    {
        Item() {}
        virtual ~Item(){}
        virtual void accept(BaseVisitor*) =0;
    };
     
    struct Item1 : Item 
    {
        Item1() {}
        void accept(BaseVisitor* v)
        {
    	Visitor<Item1>* v1 = dynamic_cast<Visitor<Item1>*>(v);
    	if (v1) v1->visit(*this);
        }
    };
     
    //On hérite de Visitor<Item1> car ce visiteur peut visiter Item1, si il doit aussi pouvoir visiter Item2, il faut aussi hériter de Visitor<Item2>
    struct OnlyMask : BaseVisitor, Visitor<Item1>
    {
        void visit(Item1& f)
        { /*code*/ }
    };
    Emmanuel Deloget a écrit un article sur les différences entre le visiteur acyclique et le "classique", je te conseille de le lire.

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

    Informations professionnelles :
    Activité : aucun

    Informations forums :
    Inscription : Octobre 2004
    Messages : 11 612
    Points : 30 612
    Points
    30 612
    Par défaut
    Citation Envoyé par Aspic Voir le message
    Merci pour toutes vos réponses.

    Effectivement au vu de vos remarques cela ne semble pas la bonne solution de passer par une fonction virtuelle mais comme Kaola me le demandait, l'héritage dans mon cas est préconisé (ou alors j'ai rien compris au principe de l'héritage ). Je m'explique :
    Mouaip... l'héritage peut se justifier, mais le nom de la classe mère est particulièrement mal choisi car il n'indique pas clairement ce que l'on attend des classes qui en dérivent

    Si tu venais à le renommer sous une forme proche de InventoryItem, je n'y verrais aucune objection


    Soit la classe mère abstraite t_item.
    Soit quatre classes filles t_item_epee, t_item_boomerang, t_item_grappin et t_item_pelle qui héritent de t_item.
    Soit deux fonctions de gestion des items :"gerer(???)" et afficher(BITMAP* zoneDeJeu)".

    L'épée doit connaitre : le masque de collision (type BITMAP*), les ennemies (type : vector<t_ennemie*>), la position de certains objets qui se détruisent à l'épée (type : vector<t_herbe*> ...)
    Le boomerang doit connaitre : le masque de collision (type BITMAP*), les ennemies (type : vector<t_ennemie*>), les interrupteurs (non codés)
    La grappin doit connaitre : le masque de collision (type BITMAP*), les ennemies (type vector<t_ennemie*>), les objets auxquels il peut s'accrocher (non codés encore...)
    La pelle doit connaitre : le masque de collision et c'est tout !
    Tes classes dérivées n'ont absolument pas besoin de savoir tout cela...

    Au pire, elles doivent juste être en mesure de dire si le joueur est équipé d'un objet donné (comprend: qu'il l'a en main ) ou si l'objet se trouve "simplement" dans l'inventaire (mais que le joueur ne l'a pas en main)... et encore ...

    On peut en effet estimer que le joueur ne peut avoir que un ou deux objet en main, et que tout objet se trouvant dans l'inventaire mais pas dans une des mains du joueur n'est, fatalement, pas équipé .
    Donc on voit bien que les paramètres sont différents dans chaque cas sauf le premier qui est commun à tout le monde.

    La seule responsabilité de l'objet devient donc... d'être susceptible d'être utilisé en exposant une fonction use qui prend un pointeur ou une référence vers l'élément sur l'objet doit être utilisé.
    1. C'est alors à l'élément sur lequel l'objet doit être utilisé de déterminer:
    2. si l'objet en cours d'utilisation a un effet éventuel sur lui (l'élément), et lequel
    3. un masque de collision pour l'objet utilisé
    4. Le résultat final de l'utilisation de l'objet utilisé

    Les éléments sur lesquels tu pourra utiliser les différents objets seront, quant à eu, géré par un "gestionnaire" (le terme est peut etre mal choisi, mais c'est le meilleur qui me vienne à l'esprit ) d'éléments de décors, dont la responsabilité sera de déterminer l'élément de décors sur lequel le joueur essaye d'avoir une action en fonction de sa position (et éventuellement de son orientation).
    Le principe est qu'on a un inventaire avec les quatre items ci dessus par exemple mais on ne peut en assigner que deux à la fois (un dans le bouton A et un autre dans le bouton B)
    Oui, oui, nous sommes bien d'accord là dessus

    Dans mon moteur de jeu, voilà l'appel :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
     
    itemSelectionneBoutonA->gerer(???);
    itemSelectionneBoutonB->gerer(???);
    Je ferais plutôt quelque chose comme
    DecorElement * ptr = gestionnaire.find(/* ce qui permet de choisir l'élément de décors*/);
    itemSelectionneBoutonA->useOn(ptr);
    itemSelectionneBoutonB->useOn(ptr);
    Et dans le moteur d'affichage :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
     
    itemSelectionneBoutonA->afficher(zoneJeu);
    itemSelectionneBoutonB->afficher(zoneJeu);
    PS : l'affichage est séparé entièrement de la gestion mémoire des entités du jeu
    Et c'est comme cela qu'il doit en aller

    Voilà, je ne sais pas si je fais complètement fausse route ou pas. J'ai regardé un peu le DP visiteur mais je vous avoue que je ne comprends pas son fonctionnement et je n'ai pas trouvé d'implémentation en code simple pour comprendre le fonctionnement car les diagrammes UML...
    Dans le cas présent, le visiteur serait sans doute l'élément de décors (comprenons nous: je parle de la partie métier de l'élément de décors, et non de sa partie visuelle ).

    Tu aurais donc les hiérarchies suivantes:
    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 Decors;
    class InventoryItem // tous les objets pouvant se trouver dans l'inventaire
    {
        public:
            virtual ~InventoryItem(){}
            virtual void useOn(Decors *) = 0;
    };
    class Sword: InventoryItem
    {
        public:
            virtual void useOn(Decors * to)
            {
               to->isUsed(*this);
            }
    };
    class boomerang: InventoryItem
    {
        public:
            virtual void useOn(Decors * to)
            {
               to->isUsed(*this);
            }
    };
    class Grapple: InventoryItem
    {
        public:
            virtual void useOn(Decors * to)
            {
               to->isUsed(*this);
            }
    };
    class Shovel: InventoryItem
    {
        public:
            virtual void useOn(Decors * to)
            {
               to->isUsed(*this);
            }
    };
    et l'autre sous la forme de
    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
    class Decors
    {
        public:
            virtual void isUsed(Sword &){/* does nothing by default */}
            virtual void isUsed(Boomerang&)){/* does nothing by default */}
            virtual void isUsed(Grappel&)){/* does nothing by default */}
            virtual void isUsed(Shovel&)){/* does nothing by default */}
    };
    class Grass : public Decors
    {
        public:
            virtual void isUsed(Sword &){/* when a sword hit grass */}
    };
    class Enemy : public Decors
    {
        public:
            virtual void isUsed(Boomerang&){/* when a Boomerang hit enemy*/}
    };
    class Wall: public Decors
    {
        public:
    ll        virtual void isUsed(Grappel&){/* when a Grappel hit wall*/}
    };
    class Ground: public Decors
    {
        public:
            virtual void isUsed(Shovel &){/* when a shovel hit ground*/}
    };
    Mais même, je ne vois pas comment ce DP pourrait m'aider dans ma situation
    Simplement en faisant en sorte que chaque élément de décors réagisse de manière adéquate en fonction de l'objet qui "l'attaque"

    NOTA : Il y a multitude d'autres solutions, mais celle ci est la première qui me vient à l'esprit
    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

  8. #8
    Expert confirmé
    Avatar de Aspic
    Homme Profil pro
    Étudiant
    Inscrit en
    Août 2005
    Messages
    3 905
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Hauts de Seine (Île de France)

    Informations professionnelles :
    Activité : Étudiant
    Secteur : High Tech - Électronique et micro-électronique

    Informations forums :
    Inscription : Août 2005
    Messages : 3 905
    Points : 4 388
    Points
    4 388
    Par défaut
    Merci beaucoup pour toute ton explication, je vais voir comment adapter cela si je peux (avec le design assez pourri que j'ai dans mon ca risque de pas être très facile )

    Juste une question par curiosité :
    Pourquoi passer par référence avec to->isUsed(*this); ?
    Et pas tout simplement to->isUsed(this); en récupérant un pointeur ?
    Qui ne tente rien n'a rien !
    Ce qui ne nous tue pas nous rends plus fort !!
    Mon projet ZELDA en C++/Allegro
    http://www.tutoworld.com - Le Forum -
    Mes ressources Dotnet (cours, sources, tutos)
    --------------------------------------------
    + + =

    Ne pas oublier le Tag !

Discussions similaires

  1. Réponses: 2
    Dernier message: 05/03/2006, 19h29
  2. Compilation avec des fonctions virtuel pure
    Par vanitom dans le forum C++
    Réponses: 4
    Dernier message: 16/12/2005, 14h37
  3. masquage de fonction virtuelle
    Par julien.sagnard dans le forum C++
    Réponses: 3
    Dernier message: 27/01/2005, 14h00
  4. Réponses: 2
    Dernier message: 07/10/2004, 17h00
  5. fonctions virtuelles
    Par Mau dans le forum Autres éditeurs
    Réponses: 4
    Dernier message: 21/11/2003, 09h53

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