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 :

Pointeur de fonction et classes imbriquées


Sujet :

Langage C++

Vue hybride

Message précédent Message précédent   Message suivant Message suivant
  1. #1
    Membre confirmé
    Profil pro
    Inscrit en
    Août 2007
    Messages
    66
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Août 2007
    Messages : 66
    Par défaut Pointeur de fonction et classes imbriquées
    Bonjour à tous,

    J'ai un problème de définition de pointeur de fonction qui est celui ci :

    J'ai deux classes A et B.
    La classe B fait appel dans son constructeur au constructeur de la classe A.
    Le constructeur de la classe A est paramétré.
    Le type de paramètre du constructeur de la classe A est un pointeur de fonction.
    Le constructeur de la classe B lorsqu’il appelle le constructeur de la classe A passe en paramètre une de ses méthodes "membre" qui a la bonne signature.

    Le premier problème auquel je me suis confronté était de compiler le code.

    GCC n'aimait pas le type que je lui passais.
    En jetant un coup d’œil à la FAC C++ http://cpp.developpez.com/faq/cpp/?p...onction_membre , j'ai compris qu'il y avait deux moyens de faire :

    -soit changer la signature de mon pointeur de fonction
    -soit mettre ma méthode de classe en "static"

    Je ne pouvais pas mettre ma méthode en "static" parce qu'elle manipule des données d'instance. J'ai donc opté pour la modification de la signature de mon pointeur.

    Seulement, j'ai un problème de référence.

    Si je définis mon pointeur en premier, le compilateur ne reconnait pas le type B.
    Si je définis le type B en premier, le compilateur ne reconnait pas le pointeur de fonction.
    Si je définis le type A en premier, le compilateur ne reconnais pas le pointeur de fonction

    Voici le code illustrant mon propos :

    Pointeur :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
     
     
    typedef void (B::*ParameterlessFunctionPointer)(void);
    Classe A :

    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
     
     
    class A
    {
    	private : 
     
    	A(const A&);
     
    	A& operator=(const A &);
     
        public :
     
    	A(ParameterlessFunctionPointer func)
    	{
               printf("naissance de A");
    	   printf("\n");
    	}
     
    	~A()
    	{
    	  printf("mort de A");
    	  printf("\n");
    	} 
    };
    Classe B:

    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
     
     
    class B
    {
        private:
     
    	A * _instanceDeA;
     
    	B(const B&);
     
    	B& operator=(const B &);
     
        public :
     
        void FonctionSansParamDeB() 
    	{
    	  printf("Appel  de Fonction Sans Param de B");
    	  printf("\n");
    	}
     
    	B()
    	{	  
    	  _instanceDeA = new A(FonctionSansParamDeB);
               printf("naissance de B");
    	  printf("\n");
    	}
     
    	~B()
    	{
    	  delete _instanceDeA;
    	 _instanceDeA = NULL;
     
    	  printf("mort de B");
    	  printf("\n");
    	} 
    };
    Main de test :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
     
     
    int main(int argc, char* argv[])
    {	
           B * instanceDeB =  new B();
     
    	return 0;
    }
    Merci de votre aide.

  2. #2
    Rédacteur/Modérateur
    Avatar de JolyLoic
    Homme Profil pro
    Développeur informatique
    Inscrit en
    Août 2004
    Messages
    5 463
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 51
    Localisation : France, Yvelines (Île de France)

    Informations professionnelles :
    Activité : Développeur informatique
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Août 2004
    Messages : 5 463
    Par défaut
    Fais dans l'ordre :
    - une déclaration de A (class A
    - une définition de B, sans le corps des fonctions membre (du moins, pas celles qui manipulent un A)
    - une définition du pointeur de fonction
    - une définition de A
    - Le corps des fonctions de B et A.
    Ma session aux Microsoft TechDays 2013 : Développer en natif avec C++11.
    Celle des Microsoft TechDays 2014 : Bonnes pratiques pour apprivoiser le C++11 avec Visual C++
    Et celle des Microsoft TechDays 2015 : Visual C++ 2015 : voyage à la découverte d'un nouveau monde
    Je donne des formations au C++ en entreprise, n'hésitez pas à me contacter.

  3. #3
    Membre confirmé
    Profil pro
    Inscrit en
    Août 2007
    Messages
    66
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Août 2007
    Messages : 66
    Par défaut
    Je vous suis pas à pas.

    - une déclaration de A

    - une définition de B, sans le corps des fonctions membre (du moins, pas celles qui manipulent un A)

    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
     
     
    class B
    {
        private:
     
    	A * _instanceDeA;
     
    	B(const B&);
     
    	B& operator=(const B &);
     
        public :
     
        void FonctionSansParamDeB();
     
    	B();
     
    	~B();
    };
    - une définition du pointeur de fonction

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
     
     
    typedef void (B::*ParameterlessFunctionPointer)(void);
    Par contre pour le point "une définition de A" : je ne sais pas quelle syntaxe adopter ...

  4. #4
    Rédacteur/Modérateur
    Avatar de JolyLoic
    Homme Profil pro
    Développeur informatique
    Inscrit en
    Août 2004
    Messages
    5 463
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 51
    Localisation : France, Yvelines (Île de France)

    Informations professionnelles :
    Activité : Développeur informatique
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Août 2004
    Messages : 5 463
    Par défaut
    Citation Envoyé par XAMLdev Voir le message
    Par contre pour le point "une définition de A" : je ne sais pas quelle syntaxe adopter ...
    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
    class A
    {
    	private : 
     
    	A(const A&);
     
    	A& operator=(const A &);
     
        public :
     
    	A(ParameterlessFunctionPointer func)
    	{
               printf("naissance de A");
    	   printf("\n");
    	}
     
    	~A()
    	{
    	  printf("mort de A");
    	  printf("\n");
    	} 
    };
    PS : Mon post concerne juste l'aspect syntaxique, pas l'aspect bien fondé du code.
    Ma session aux Microsoft TechDays 2013 : Développer en natif avec C++11.
    Celle des Microsoft TechDays 2014 : Bonnes pratiques pour apprivoiser le C++11 avec Visual C++
    Et celle des Microsoft TechDays 2015 : Visual C++ 2015 : voyage à la découverte d'un nouveau monde
    Je donne des formations au C++ en entreprise, n'hésitez pas à me contacter.

  5. #5
    Expert éminent
    Avatar de Médinoc
    Homme Profil pro
    Développeur informatique
    Inscrit en
    Septembre 2005
    Messages
    27 397
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 41
    Localisation : France

    Informations professionnelles :
    Activité : Développeur informatique
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Septembre 2005
    Messages : 27 397
    Par défaut
    XAMLdev, note qu'il y a des différences cruciales entre C# et C++ natif, au niveau des constructeurs et à celui des délégués et pointeurs de fonction:
    • En C#, on peut faire appel aux méthodes virtuelles ou abstraites dans un constructeur, pour obtenir le comportement de la classe dérivée; en C++ c'est impossible, on aura seulement le comportement de la classe de base (ou une erreur "pure virtual function called" pour les méthodes abstraites.)
    • Un délégué "classique" ("fermé") comporte un pointeur de fonction ET un pointeur d'objet.
    • En C++, un "pointeur de fonction membre" (correspondant à un délégué "ouvert") initialisé avec une méthode virtuelle appelle la méthode virtuellement, j'avais eu l'occasion de le constater. En C# je ne sais pas, je n'ai jamais utilisé de délégué "ouvert" sur une méthode virtuelle.

    Tout ceci peut compliquer drastiquement le portage d'un tel code depuis C# vers C++ natif.
    SVP, pas de questions techniques par MP. Surtout si je ne vous ai jamais parlé avant.

    "Aw, come on, who would be so stupid as to insert a cast to make an error go away without actually fixing the error?"
    Apparently everyone.
    -- Raymond Chen.
    Traduction obligatoire: "Oh, voyons, qui serait assez stupide pour mettre un cast pour faire disparaitre un message d'erreur sans vraiment corriger l'erreur?" - Apparemment, tout le monde. -- Raymond Chen.

  6. #6
    Membre confirmé
    Profil pro
    Inscrit en
    Août 2007
    Messages
    66
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Août 2007
    Messages : 66
    Par défaut

    Je t’arrête tout de suite: tu ne dois en aucun cas essayer de "simplement" paraphraser ce que tu as fait en C# en C++.
    ET l’esprit d’aventure alors ? EN fait mon code C# a déjà été pensé en terme de portage du coup il est optimisé pour être transcrit. Je ne dis pas que ce sera du 1 pour 1 mais ça ne devrait pas en être loin.


    La question que j'ai envie de te poser, c'est: pourquoi utiliser un pointeur de fonction ?
    C’est simple. Dans mon archi, un message est un type asexué. C'est-à-dire que je ne transmets dans le constructeur d’un message que des paramètres de type simples ou généraux. Les capacités de tirs ne spécialisent pas les messages. De plus comme les capacités de tors génèrent et gèrent leur message, elles ne peuvent pas transmettre leur type (sous forme d’instance par exemple). Ceci conduirait à une belle référence circulaire. De plus mes capacités de tirs sont libres d’être ce qu’elles veulent : une classe seule, une classe dérivée, une classe composée. Peu importe son type ça ne doit pas influer sur la structure du message qui ne passe que des infos primaires. Du coup pour faire un genre de callbak, je passe le pointeur de fonction qui comme je l’ai dit en C# est un type définit qui ne change pas suivant sa localisation. Là encore ça n’oblige pas le message à être spécialement typé.


    Attends... Tu essaye de me dire qu'une même capacité de tir peut vouloir obtenir différents type de projectiles ?
    Oui par exemple des projectiles composés comme des explosions par fragmentation. Et bien d’autres choses …


    Plutôt que de transmettre un pointeur de fonction, transmet simplement un pointeur vers le type de base de ta capacité de tir à ton projectile, en ayant pris soin de déclarer une fonction que ton projectile pourra appeler au départ du pointeur afin d'indiquer à la capacité de tir qu'il est temps de le détruire ?

    Non, je ne transmets pas de type dans mes messages de tirs.


    En effet, la capacité de tir n'a, a priori, qu'une chose à faire: envoyer un projectile dans une direction donnée quand on lui demande de le faire, et pour autant qu'elle ait un projectile "en réserve", car, si le chargeur est vide... elle aura du mal à l'envoyer
    La capacité de tir est un médiateur entre les projectiles qu’elle génère et le tireur qui tire.
    Il y en a certains cas ou la façon de tirer fait que le projectile impose un comportement à la capacité de tir qui maintient un état particulier du tireur : ce n’est pas fréquent mais ça arrive.

    Le code de la capacité de tir que tu as donné est pratiquement le mien sauf que les opérations que je fais en call back ne sont pas de destruction du projectile ( ta méthode detruisMoi(Projectile * ) = 0).

    C’est plus complexe que cela. Et effectivement mes projectiles peuvent rebondir sur un obstacle disparaitre ou être à tête chercheuse.


    Il n'y a, sans doute, aucun intérêt à faire en sorte que tes projectiles soient gérés par une classe qui se contenterait de les maintenir en mémoire aussi longtemps qu'ils existent, mais il y a très certainement intérêt à faire en sorte que les projectiles soient maintenus en mémoire à coté de "tout ce qui peut provoquer leur destruction", simplement parce que c'est la collision avec un de ces objets susceptibles de provoquer leur destruction qui... provoquera leur destruction effective

    J’ai opté pour une méthode de gestion qui ressemble à de l’inversion de contrôle : il n’y a pas de grand scrutateur qui manage mais chaque projectile se gère et notifie les étapes importantes de sa vie à qui de droit. C’est ce que j’appelle le comble de l’inspecteur ….


    C# sera pourtant toujours beaucoup plus proche de java que de C++, de par la conception même du langage
    Oui mais non, pour les pointeurs de fonction en JEE il n’ y a ni le paradigme de C++ ni celui de C# et le système de gestion des évènements est plus fouillis.

    Je suis en train de tester une adaptation de mon code, je reviens au prochain post pour en parler.

    Merci de vos remarques très instructives.

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

    Informations professionnelles :
    Activité : aucun

    Informations forums :
    Inscription : Octobre 2004
    Messages : 11 644
    Par défaut
    Salut,

    Attends un peu... j'aimerais être sur de bien comprendre ce que tu cherches à faire.

    si j'ai bien compris, tu voudrais créer, dans le constructeur de ton objet de type B, un objet du type A (qui est membre de B) qui... utiliserait une fonction de B pour s'initialiser, alors que la seule relation qui existe entre A et B est au mieux une composition

    Est-ce que cela ne te paraît pas "un tout petit peu" trop complexe

    En plus, je présumes que, si tu en viens à envisager cette solution, c'est sans doute parce que tu prévois que A puisse être dérivé en plusieurs classes qui en héritent et que tu voudrais donc que le type réel de l'objet pointé par le membre instanceDeA de ta classe B puisse être une instance des classes dérivées. Me trompes je

    A moins, bien sur, que tu ne viennes de java et que l'utilisation massive de new (et des pointeurs qui en résultent) ne soit que le fruit de l'habitude de créer d'office tous tes objets en invoquant cet opérateur

    Dans ce dernier cas, sache que C++ permet parfaitement de créer un objet sans recourir aux pointeurs ni à l'allocation dynamique de la mémoire, et que le fait d'éviter les pointeurs ne pourra que te faciliter énormément la tâche: Si l'objet de type A doit être construit et détruit en même temps que l'objet de type B qui le contient, et que tu n'envisages pas de manipuler le membre de type A sous une forme polymorphe, tu auras déjà beaucoup plus facile à éviter purement et simplement le recours aux pointeurs, sous une forme proche de
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    class B{
        public:
            B():_instanceDeA(/* paramètres éventuels */ ){}
     
        private:
            A instanceDeA;
    };
    , tout comme tu pourrais utiliser simplement un objet de type B "normal", plutôt qu'un pointeur vers un objet pour lequel l'espace mémoire a été alloué de manière dynamique dans ta fonction main, sous la forme de
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    int main(int argc, char* argv[])
    {	
           B  instanceDeB;
     
    	return 0;
    }
    (Au passage, le code de la fonction main telle que tu l'as écrite occasionne une fuite mémoire, vu que instanceDeB n'est jamais détruit)

    Mais revenons un peu sur ma deuxième question (celle qui parle du fait de faire dériver certaines classes de A et de B)...

    Je déduis, du fait que tu as déclaré le constructeur par copie et l'opérateur d'affectation privé, que tu t'attends, effectivement, à avoir plusieurs classes dérivées de A et plusieurs classes dérivées de B, autrement, tu n'aurais sans doute pas pris la peine de donner une sémantique d'entité à ces deux classes.

    De là à imaginer que tu t'attends à ce qu'un objet de type B1, héritant de B finisse par se retrouver avec un objet de type A1 héritant de A comme membre connu sous le nom de _instanceDeA, il n'y a qu'un pas, que je franchis allègrement.

    Et je serais très surpris que tu viennes me dire que tel n'est pas ton objectif final, me trompes-je

    Si tel est ton objectif final, oublies le tout de suite, car tu fonces vers un mur!!!

    Il faut bien comprendre que la relation d'héritage est strictement "à sens unique": Une classe dérivée connait, fatalement, l'ensemble de ses classes de base, qu'elle en hérite de manière directe ou indirecte (elle connait la classe dont hérite la classe dont hérite la classe dont elle hérite), mais il n'y a strictement aucune raison pour qu'une classe de base connaisse en quoi que ce soit les classe qui en héritent, simplement, simplement parce que "n'importe qui" peut, à tout moment, décider de faire hériter une nouvelle classe dérivée de la classe de base.

    Cela se remarque d'ailleurs dans l'ordre de création des objets lorsque tu crées un objet de type dérivé, qui se fait, dans l'ordre d'héritage, de la classe de base vers la classe dérivée.

    Ainsi, si tu as une classe E qui hérite de la classe D qui hérite de la classe C qui hérite de la classe B, lorsque tu vas créer un objet de type E, le constructeur de B sera appelé en premier, puis ce sera le tour du constructeur de C, puis de celui de B et enfin celui de E.

    A l'inverse, lors de la destruction de l'objet de type E, cela se fera dans l'ordre inverse, à savoir d'abord le destructeur de E, puis celui de D, suivi de celui de C et enfin celui de B.

    En observant cet ordre, tu te rendras compte que tu ne pourras jamais transmettre un pointeur sur une fonction issue du type E lorsque tu seras au niveau du constructeur de B, et que, si le pointeur de fonction pointe vers un comportement polymorphe, tu ne pourras, de toutes façons, observer que le comportement propre à B au niveau de son constructeur, simplement parce que... les éléments de l'objet de type dérivé n'existent pas encore.

    Comme je suis quasiment sur de mes déductions (mais bon, n'hésites pas à me contredire si je me trompes vraiment, hein ), ce qu'il faut, c'est que tu aies la certitude qu'un objet de type B1 (dérivé de B) utilise bel et bien un objet de type A1 (dérivé de A) comme membre pour _instanceDeA.

    Mais, pour arriver à ce résultat, ce n'est pas à B de décider du type réel de l'objet dérivé de A qu'il faut construire, ni (et surtout pas) au constructeur de A à faire la transition.

    N'oublies pas que, même si le constructeur est une fonction, c'est avant tout une fonction bien particulière qui ne renvoie aucune donnée!!! Ce n'est donc pas au niveau du constructeur de A que tu pourras déterminer d'aucune manière quel sera le type réel de A. Si tu appeles new A, tu obtiendras l'espace mémoire nécessaire à la représentation d'un A et tu n'as strictement aucune chance de te retrouver avec un espace mémoire permettant de représenter un objet de type A1.

    Pour te permettre de résoudre ton problème, je vais commencer par te rappeler ce que David wheller dit sur le sujet:
    Citation Envoyé par David wheller
    all problems in computer science can be solved by another level of indirection
    (Tout problème en informatique peut être résolu par un autre niveau d'indirection)
    Le niveau d'indirection supplémentaire dont il est question ici a de fortes chances d'être très proche du patron de conception connu sous le terme de fabrique (factory en anglais).

    L'idée est en fait de créer une classe (ou une fonction) intermédiaire qui n'a strictement rien à voir avec le type A ni avec le type B (et encore moins avec un quelconque type dérivé de A ou de B) et qui sera en mesure de créer un objet de n'importe quel type dérivé de A sur base des paramètres qu'on lui transmettra.

    Le constructeur de B devra juste s'assurer de transmettre le paramètre en question (qu'il aura obtenu par l'intermédiaire des constructeurs de type dérivés qui l'auront appelé) à la fabrique.

    Et c'est donc le constructeur du type dérivé de B qui devra s'assurer de transmettre le "bon paramètre" au constructeur de B afin de s'assurer que le type de l'objet dérivé de A utilisé pour _instanceDeA corresponde bel et bien à celui qu'il s'attend à y trouver.

    Gardes cependant en tête que si tu disposes d'un pointeurs vers un objet de type A, tu ne le connaitra que... comme étant un objet de type A, et que tu seras donc limité au niveau de l'interface disponible à celle qui correspond à A, même si le type réel de l'objet est une des classes dérivées de A.

    Je te laisse libre de choisir le meilleur moyen de transmettre les informations nécessaires au bon fonctionnement de la fabrique, mais ce n'est pas les options qui manquent

    *Peut-être* serait il intéressant de faire en sorte que chaque type dérivé de B connaisse le type réel de l'objet dérivé de A qu'il connaisse, afin, entre autres, de pouvoir profiter de l'ensemble de l'interface complète de celui-ci

    Je n'en connais pas suffisamment sur ton projet pour pouvoir me faire une idée précise de la réponse à cette question.

    Mais, si tel le cas, ce n'est pas la classe de base B qui devrait disposer du pointeur sur _instanceDeA mais bien chaque type dérivé de B qui devrait disposer de son propre pointeur vers le type qui l'intéresse en particulier, la classe B déclarant alors sans doute toutes les fonctions virtuelles comme virtuelles pure, étant donné qu'elle ne dispose, purement et simplement, pas des données dont on a besoin pour fournir un comportement correct (et devenant, de facto, une classe abstraite, c'est à dire pour laquelle on ne peut pas demander d'instanciation autrement que par l'intermédiaire des classes dérivées)
    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
    Membre confirmé
    Profil pro
    Inscrit en
    Août 2007
    Messages
    66
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Août 2007
    Messages : 66
    Par défaut
    Ça me fait plaisir d'avoir un tel niveau de conversation technique.

    Effectivement, je ne voulais pas rendre mon exemple trop complexe mais tu as compris l'essentiel de ce que je voulais faire. Ce que je vais présenter est une version simplifiée de mon mécanisme.

    D’abord je ne viens pas de Java mais C# ce qui est très différent pour le cas qui nous préoccupe.

    La classe B est une "capacité de tir complexe" qui utilise des "capacités de tir simples" (les classes A), dans les classes A il y a un message initialisé.

    Le but est de modéliser un système de tir d'un personnage dans un jeu vidéo.

    J'ai déjà terminé le code en C# et tout fonctionne à merveille. En ce moment je travaille sur un portage en C++.

    En C#, les pointeurs de fonction sont des "délégués" dont la syntaxe ressemble furieusement à un typedef de pointeur de fonction.

    Mon idée est que les capacités de tir génèrent des messages qui enclenchent des tirs de projectiles.

    Les projectiles ne connaissent pas les capacités de tir, ils vivent leur vie et à des moments particuliers, ils signalent leur mort en appelant le pointeur de fonction qui pointe vers les capacités de tirs.

    Du coup en C#, je transmets mon pointeur de fonction de constructeurs en constructeurs pour que le message qui est envoyé au moment du tir donne le bon pointeur (si il y a besoin). Je tiens à mon encapsulation pour ne pas que mes données soient manipulées de l’extérieur (par erreur par exemple).

    Il y a une factory qui génère les projectiles à partir des messages et chaque message est une instance unique qui se met à jour suivant le désir de la capacité de tir.

    D cette manière, la gestion est plus souple et moins couteuse en temps.
    De plus suivant les aléas de la synchronisation VBL du framerate et d'autres choses, seul le projectile sait s'il est mort ou non.

    En c#, on n'a pas besoin de changer de type. Où que soit définit et nommé un pointeur de fonction il est d'un seul type définit s'il en respecte la signature.

    En C++, même si la signature est la même, suivant sa localisation, le type change. Comme je ne manipule que des données d'instance je ne puis utiliser l'astuce de mettre en static les fonctions passées en paramètre ...

    La solution de hacher la définition ne marchera pas parce que ça implique trop de choses ...

    Du coup je vais être obligé de créer un genre de singleton qui va archiver pour moi ces infos et consultable par mes projectile.

    Mon idée c'est à chaque création d' "capacité de tir complexe" d'alimenter une cellule d’une table avec un identifiant et une instance de celle-ci dans un singleton : un annuaire.

    Cet annuaire sera consultable par les projectiles en espérant ne pas avoir de problèmes de références circulaires.

    Bref c pas simple et ce n’est pas gagné et je n’ai qu’une semaine de C++ à mon actif ....

  9. #9
    Expert éminent
    Avatar de koala01
    Homme Profil pro
    aucun
    Inscrit en
    Octobre 2004
    Messages
    11 644
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 53
    Localisation : Belgique

    Informations professionnelles :
    Activité : aucun

    Informations forums :
    Inscription : Octobre 2004
    Messages : 11 644
    Par défaut
    Citation Envoyé par XAMLdev Voir le message
    Ça me fait plaisir d'avoir un tel niveau de conversation technique.
    Je me balade rarement sur les sections dédiées aux autres langages, mais, C++ étant particulièrement complexe, les discussion de haut niveau technique me semblent assez courantes ici
    Effectivement, je ne voulais pas rendre mon exemple trop complexe mais tu as compris l'essentiel de ce que je voulais faire.
    Disons qu'il y avait quelques détails qui ne trompent pas
    D’abord je ne viens pas de Java mais C# ce qui est très différent pour le cas qui nous préoccupe.
    C# sera pourtant toujours beaucoup plus proche de java que de C++, de par la conception meme du langage

    La classe B est une "capacité de tir complexe" qui utilise des "capacités de tir simples" (les classes A), dans les classes A il y a un message initialisé.

    Le but est de modéliser un système de tir d'un personnage dans un jeu vidéo.
    J'ai déjà terminé le code en C# et tout fonctionne à merveille. En ce moment je travaille sur un portage en C++.

    En C#, les pointeurs de fonction sont des "délégués" dont la syntaxe ressemble furieusement à un typedef de pointeur de fonction.
    Je t'arrêtes tout de suite: tu ne dois en aucun cas essayer de "simplement" paraphraser ce que tu as fait en C# en C++.

    Si cela peut suffire dans les cas les plus simples, ce sera très certainement insuffisant et inadapté pour les cas les plus complexes

    Mon idée est que les capacités de tir génèrent des messages qui enclenchent des tirs de projectiles.

    Les projectiles ne connaissent pas les capacités de tir, ils vivent leur vie et à des moments particuliers
    Jusque là, cela semble parfaitement correct, logique et cohérent ,
    ils signalent leur mort en appelant le pointeur de fonction qui pointe vers les capacités de tirs.
    La question que j'ai envie de te poser, c'est: pourquoi utiliser un pointeur de fonction

    Pourquoi ne pas "simplement" faire en sorte que chaque projectile dispose, en accessibilité privée cela va de soi, d'un pointeur vers la capacité de tir dont il dépend et faire en sorte d'appeler une fonction virtuelle (si tant est qu'elle doive l'être, mais j'y reviendrai plus tard) faisant partie de l'interface de B (donc de l'interface commune à toutes les capacités de tire)

    Du coup en C#, je transmets mon pointeur de fonction de constructeurs en constructeurs pour que le message qui est envoyé au moment du tir donne le bon pointeur (si il y a besoin). Je tiens à mon encapsulation pour ne pas que mes données soient manipulées de l’extérieur (par erreur par exemple).
    respecter l'encapsulation est toujours une bonne chose, mais ce qui me choque vraiment est l'utilisation d'un pointeur de fonction.
    Il y a une factory qui génère les projectiles à partir des messages et chaque message est une instance unique qui se met à jour suivant le désir de la capacité de tir.
    Que la factory utilise le message émis par la capacité de tir ne me choque pas forcément, c'est l'une des possibilités (à laquelle je n'avais d'ailleurs pas pensé au moment d'écrire ma prose précédente) dont je voulais parler lorsque j'ai parlé des paramètres à transmettre à la factory
    D cette manière, la gestion est plus souple et moins couteuse en temps.
    De plus suivant les aléas de la synchronisation VBL du framerate et d'autres choses, seul le projectile sait s'il est mort ou non.
    Attends... Tu essaye de me dire qu'une même capacité de tir peut vouloir obtenir différents type de projectiles

    Note que cela ne pose pas énormément de problème, car je présumes que chaque capacité de tir répondra, par ailleurs, à différents messages permettant de choisir le type de projectile ou la cadence de tir, et que le message correspondant au tir du projectile lui-même sera de toutes façons le résultats de ces réglages
    En c#, on n'a pas besoin de changer de type. Où que soit définit et nommé un pointeur de fonction il est d'un seul type définit s'il en respecte la signature.
    Ce qui est loin d'être le cas en C++
    En C++, même si la signature est la même, suivant sa localisation, le type change. Comme je ne manipule que des données d'instance je ne puis utiliser l'astuce de mettre en static les fonctions passées en paramètre ...
    En effet, mais tout le problème vient de ce que tu veux utiliser un pointeur de fonction!

    Plutôt que de transmettre un pointeur de fonction, transmet simplement un pointeur vers le type de base de ta capacité de tir à ton projectile, en ayant pris soin de déclarer une fonction que ton projectile pourra appeler au départ du pointeur afin d'indiquer à la capacité de tir qu'il est temps de le détruire
    Du coup je vais être obligé de créer un genre de singleton qui va archiver pour moi ces infos et consultable par mes projectile.

    Mon idée c'est à chaque création d' "capacité de tir complexe" d'alimenter une cellule d’une table avec un identifiant et une instance de celle-ci dans un singleton : un annuaire.

    Cet annuaire sera consultable par les projectiles en espérant ne pas avoir de problèmes de références circulaires.

    Bref c pas simple et ce n’est pas gagné et je n’ai qu’une semaine de C++ à mon actif ....
    Tu vas chercher beaucoup trop loin avec toutes ces idées.

    Plutôt que d'avoir une relation "unidirectionnelle" ou la capacité de tir connait le projectile mais où le projectile ne connait pas la capacité de tir qui l'a envoyé, je te propose deux solutions:

    La première à laquelle je ne vois pas forcément ce que tu reproches, est de faire en sorte que la capacité de tir connaisse le projectile et que chaque projectile sache de quelle capacité de tir il est issu.
    Cela pourrait très bien se concrétiser par un code proche de
    Projectile.hpp
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    /* déclaration anticipé de la classe de base des capacités de tir
    class Capacite;
    /* C'est la classe de base, tous les projectiles en héritent */
    class Projectile{
        public:
            Projectile(Capacité * cap);
            virtual ~Projectile(){}
            void jeMeure();
        private:
            Capacite * cap_;
    };
    Projectile.cpp
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    #include <Projectile.hpp>
    #include <Capacite.hpp>
    Projectile::Projectile(Capacité * cap):cap_(cap){}
     
    void Projectile::jeMeure(){
        cap_->detruisMoi(this);
    }
    (je n'ai mis que les détails qui étaient intéressants )
    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
    class Projectile;
    class Capacite{
        public:
            virtual ~Capacite(){}
            /* je transmets explicitement le projectile en paramètre car
             * on peut estimer que certaines capacités de tir peuvent 
             * gérer plusieurs projectiles en meme temps... Elles doivent donc
             * être en mesure de savoir quel projectile elles doivent détruire
             */
            virtual void detruisMoi(Projectile * ) = 0;
            /* une seule fonction (à implémenter dans les différentes classes
             * dérivée) est susceptible de provoquer l'émission d'un projectile
             */
            virtual void tires() = 0;
        private:
           /* je ne met pas le projectile dans la capacité de tir, simplement
            * parce que l'on peut estimer que certaines capacités de tir 
            * risquent d'être capable de gérer plusieurs projectiles en même
            * temps
    };
    (je passe volontairement certains aspects qui n'ont pas énormément d'incidence sur le problème en question (gardes anti inclusions multiple, ...) )

    Les classes dérivées de Capacite (ou, si tu préfères: les capacités de tir concrètes) devront toutes fournir une implémentation cohérente (par rapport à ce qu'elles sont sensées permettre au joueur) pour les fonctions tires et detruisMoi.

    La première provoqueras l'émission d'un (ou de plusieurs) projectile(s) du (des) types requis et leur enregistrement auprès de la capacité de tir concrète.

    La deuxième permettra à la capacité de tir concrète de détruire correctement les projectiles qui lui auront signifié être en fin de vie (quel que soit le sens que l'on peut donner à ce terme).

    Ceci dit, tu devrais très sérieusement envisager d'adopter le patron "MVC"...

    Car, soyons réalistes, il y a beaucoup de choses qui peuvent faire qu'un projectile (quel qu'il soit) nécessite d'être détruit, et il ne me semble pas vraiment qu'il soit de la responsabilité de la capacité de tir de décider de le faire.

    En effet, la capacité de tir n'a, a priori, qu'une chose à faire: envoyer un projectile dans une direction donnée quand on lui demande de le faire, et pour autant qu'elle ait un projectile "en réserve", car, si le chargeur est vide... elle aura du mal à l'envoyer

    De leur coté, les différents types de projectiles ont leur propriétés propres. S'il fallait les citer de manière non exhaustives, je penserais en priorité à:
    • leur poids
    • leur capacité à transpercer des blindage
    • les dégats qu'ils sont susceptibles de produire
    • leur vitesse initiale
    • leur portée (qu'elle soit due à la vitesse initiale et au frottement ou à une quantité de carburant dont il dispose n'importe pas vraiment )
    • leur pouvoir explosif (qui sait )
    • le diamètre dans lequel ils occasionnent des dégâts
    • ...
    Maintenant, un projectile peut aussi bien toucher un ennemi qu'un arbre ou un rocher ou... ne rien rencontrer avant de tomber simplement à terre à cause de la gravité et des lois de la balistique

    Et, pour les projectiles les plus gros ("missiles" ), ils peuvent peut être même (qui sait ) être interceptés ou leurrés (s'ils sont "à tête chercheuse" ) par un contre dispositif quelconque!!!

    Bref, dans tous les cas que je viens de citer, je ne vois que le cas où le projectile fini par tomber en ayant atteint sa portée maximale qui pourrait justifier que le projectile décide de se détruire lui-même.

    Et encore: le projectile passera plutôt d'un état actif/dangereux/létal (tout ce que tu veux qui implique qu'il est dangereux ) à un état inactif/sans danger / non létal (tout ce que tu veux qui implique que c'est une "balle perdue pour tout le monde" ), mais la responsabilité de détruire un projectile qui a atteint cet état de "non dangerosité", tout comme celle qui consistera à détruire n'importe quel projectile ayant atteint une cible (quelle qu'elle soit) ou ayant été détruit par un contre dispositif quelconque reviendra à "autre chose".

    cet "autre chose" aura en fait comme responsabilité de repérer toutes les collisions qui pourraient survenir entre n'importe quel projectile et, finalement, n'importe quoi d'autre car c'est ce qui, au final, déciderait de la destruction du projectile

    Tout cela pour dire que ton projectile et ta capacité de tir sont des données "métier" (comprends: qui font partie du modèle de ton application), mais que ce qui devra décider de détruire un projectile au moment où c'est nécessaire est plutôt un contrôleur qui devra, en outre, avoir une parfaite connaissance de tout ce avec quoi n'importe quel projectile est susceptible d'entrer en collision.

    Il n'y a, sans doute, aucun intérêt à faire en sorte que tes projectiles soient gérés par une classe qui se contenterait de les maintenir en mémoire aussi longtemps qu'ils existent, mais il y a très certainement intérêt à faire en sorte que les projectiles soient maintenus en mémoire à coté de "tout ce qui peut provoquer leur destruction", simplement parce que c'est la collision avec un de ces objets susceptibles de provoquer leur destruction qui... provoquera leur destruction effective
    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

Discussions similaires

  1. mélange de pointeurs sur fonctions et classes
    Par membreComplexe12 dans le forum C++
    Réponses: 4
    Dernier message: 15/10/2012, 14h06
  2. Pointeur sur fonction de classe anonyme
    Par dewsz dans le forum C++
    Réponses: 5
    Dernier message: 15/07/2008, 11h00
  3. [Win32] Pointeur sur fonctions et méthodes de classe
    Par Invité dans le forum Langage
    Réponses: 4
    Dernier message: 13/09/2007, 19h07
  4. Réponses: 12
    Dernier message: 30/06/2006, 16h46
  5. Glut / Class / Pointeur sur fonction
    Par areS. dans le forum GLUT
    Réponses: 5
    Dernier message: 02/12/2005, 20h50

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