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++

  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
    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

  5. #5
    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 ....

  6. #6
    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

  7. #7
    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.

  8. #8
    Expert éminent
    Avatar de Médinoc
    Homme Profil pro
    Développeur informatique
    Inscrit en
    Septembre 2005
    Messages
    27 398
    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 398
    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.

  9. #9
    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.

  10. #10
    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
    J'ai un problème avec un membre static ...

    Voici deux classes :

    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
     
     
    	class A 
        {
    		private :
     
    		int _value;
     
    	    public :
     
    		A(int value)
    		{
    			_value=value;
    			printf("naissance de A");
    			printf("\n");
    		}	
     
    	};

    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
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
     
     
     
    	class B 
        {
     	    private :	
     
    		typedef struct {int InstanceId; B * Instance;} OutElement;
     
    		static OutElement** _elements;
    		A * _instanceA;
    		int _id;
     
    		static void AddOutElement(OutElement * newElement)
    		{   
    			_elements = new OutElement*[1];
    		}
     
    		static void RemoveOutElement(int instanceId)
    		{}
     
    		public :
     
    		static void CallOut(int instanceId)
            {}
     
    		int GetId()
    		{
    		   return _id;
    		}
     
            B()
            {
    			_id = 7;
     
                _instanceA = new A(_id);
     
    			OutElement * newElement = new OutElement();
                newElement->Instance = this;
                newElement->InstanceId = _id;
     
                AddOutElement(newElement);
     
    			printf("naissance de B");
    		    printf("\n");
    	    }
     
            ~B()
            {
               printf("mort de B");
    		   printf("\n");
     
    		   RemoveOutElement(_id);
            }	    
        };
    la compilation marche mais l'édition de lien plante, ld me renvoie une erreur suivante :

    "undefinded reference to B::_elements"

    Pourtant cette variable est définie ...

  11. #11
    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
    Alors, déjà que un pointeur c'est pas le top, mais, un pointeur de pointeur (sur un pointeur de fonction qui plus est j'avais mal lu, désolé, mais ça reste vrai quand même)... tu prends des risques là

    Ceci dit, ta variable est déclarée, mais non définie

    Il faut définir explicitement ta variable dans le fichier d'implémentation sous une forme qui pourrait ressembler à
    B.cpp
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    #include <B.h>
    /* je présume que si tu pars sur un pointeur de pointeur, c'est parce que 
     * tu ne sais pas trop quelle fonction tu veux utiliser
     */
    static B::OutElement** B::_elements=NULL;
    /* OU OU OU , en C++11 */
    static B::OutElement** B::_elements=nullptr;
    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

  12. #12
    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
    ET l’esprit d’aventure alors ?
    Oh, je ne t'empêche pas du tout d'essayer de porter du C# en C++, bien au contraire
    EN fait mon code C# a déjà été pensé en terme de portage du coup il est optimisé pour être transcrit.
    Oui, mais visiblement tu essayes de le transcrire pour ainsi dire mot à mot, et c'est là qu'est le problème
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
     Je ne dis pas que ce sera du 1 pour 1 mais ça ne devrait pas en être loin.
    Tu pourras, très certainement, garder l'organisation et les hiérarchies de classes, cela, je n'en doute pas une seule seconde. Mais
    Il n'est pas impossible que tu doive rajouter une ou deux classe pour servir de "pont".
    Certaines techniques qui peuvent te sembler naturelles en C# (comme le fait d'utiliser des pointeurs de fonctions ) ne le sont vraiment pas, ou du moins, ne sont vraiment pas recommandées en C++

    C’est simple. Dans mon archi, un message est un type asexué.
    En C#, cela se peut, mais pas en C++
    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.
    Je n'ai pas dit que les capacités de tir spécialisent les messages, j'ai dit
    • Quel que soit le message effectivement envoyé, il peut être considéré comme un message d'ordre général (il existe une hiérarchie de classe dont la classe de base est un message "sans précision")
    • Que chaque capacité de tir est susceptible d'émettre un message spécifique dont le type réel dépend, tout bien considéré, de la capacité de tir elle-même
    • que nous pourrions donc profiter intelligemment du polymorphisme lorsqu'il s'agit de faire la transition entre le message et le projectile

    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.
    Quand bien même, les référence circulaires au sein d'un module sont courantes en C++

    Par contre, dans le cas présent, la référence circulaire est surtout le symptome de ce que j'expliquais plus haut: La capacité de tir peut etre à l'origine de ce qui provoque la création d'un projectile, mais n'a strictement rien à voir avec la destruction de celui-ci.

    Et encore: à bien y réfléchir, ce n'est pas la capacité de tir elle même qui provoque la création du projectile, mais un événement particulier (clique sur la souris, appuis sur la barre d'espace, ...) qui devrait utiliser la capacité de tir afin de déterminer quel type de projectile sera créé.


    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.
    Mais elles auront sans doute (ou devraient avoir) toutes un point commun: le fait de dériver d'une classe de base identique, non

    Il y a sans doute moyen de faire sans (en utilisant le paradigme générique), mais ca risque de faire vraiment compliqué
    Peu importe son type ça ne doit pas influer sur la structure du message qui ne passe que des infos primaires.
    Ca, j'en suis beaucoup moins sur...

    Oui, le message ne transmet très certainement que des informations primaires, mais non je ne crois pas que tu puisses estimer que le type de ta capacité de tir n'influe pas sur le type du message en lui-même.

    Le fait est que les informations transmises par le message peuvent être d'ordre tout à fait différent en fonction du projectile à envoyer.

    Je ne parle pas ici de "simplement" envoyer des valeurs différentes pour des données qui seront transmises de manière systématique, je parle de transmettre, pour certains types de projectiles, des données supplémentaires spécifique, bien sur
    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é.
    Sauf que, pour pouvoir utiliser une fonction membre comme callback, tu as besoin... de l'objet au départ duquel elle est appelée, à moins d'en faire une fonction statique.

    Et même dans ce cas, une fonction membre statique devra alors connaitre... l'instance particulière de ton instance de tir qui doit prendre en charge la destruction de ton projectile.

    J'attires, en outre, ton attention sur un fait assez particulier en ce qui concerne les pointeurs sur fonctions membres:

    La signature d'une fonction membre est pleinement qualifiée, c'est à dire que le nom de la fonction membre contient le type d'objet auquel elle appartient.

    Lorsque tu écris un code proche de
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    typedef void (B::*ParameterlessFunctionPointer)(void);
    tu défini ParameterlessFunctionPointer comme un pointeur sur une fonction issue du type B ne prenant aucun argument et ne renvoyant rien. MAIS

    si C hérite de B et que la fonction que tu veux utiliser n'existe que dans C, la fonction n'entre pas dans cette définition parce que ce n'est plus une fonction de B, mais bien une fonction (spécifique!!) de C.
    Le typedef pour le pointeur de fonction d'une fonction membre qui n'apparait pas dans B est proche de
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    typedef void (C::*ParameterlessFunctionPointer)(void);
    et est incompatible avec le typedef précédent.

    Cependant, il existe une alternative aux pointeurs de fonction: les foncteurs (ou, si tu préfères, les "objets fonctions").

    Ce sont des classes ou des structures qui exposent essentiellement un opérateur ( l'opérateur () pour ce qui nous intéresse ici) et qui peut parfaitement être dérivé en différentes "saveurs"
    Oui par exemple des projectiles composés comme des explosions par fragmentation. Et bien d’autres choses …
    Oui, je ne sais pas pourquoi cela me surprenais, mais bon..

    Non, je ne transmets pas de type dans mes messages de tirs.
    En y réfléchissant bien, tu as raison de le faire ainsi: le projectile est totalement indépendant tant du tireur que de la capacité de tir qui l'a généré: il vit sa vie et n'a plus de compte à rendre ni à l'un ni à l'autre.
    La capacité de tir est un médiateur entre les projectiles qu’elle génère et le tireur qui tire.
    Je l'avais bien compris sous cette forme.

    Mais la responsabilité du tireur et de la compétence de tir vis à vis du projectile tiré s'arrête... au moment où le projectile est tiré.

    Une fois qu'il est tiré, le projectile n'a plus rien à voir ni avec l'un ni avec l'autre
    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.
    Oui, mais, dans ce cas, le changement d'état de la capacité de tir ne devrait pas être directement induit par le projectile: tu devrais avoir un contrôleur qui modifie l'état de la capacité de tir en fonction de l'état du projectile tiré
    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'était un exemple simple pour te faire comprendre

    Ce qui était important à comprendre, c'est que, si tu veux que le projectile puisse communiquer directement d'une manière ou d'une autre avec la capacité de tir, ce qu'il te faut c'est:
    • soit une hiérarchie de classes spécifique (prenant la forme d'un foncteur) qui permet d'invoquer des fonctions spécifiques pour chaque capacité de tir
    • soit un comportement polymorphe au niveau de la classe de base des capacités de tir qui sera appelé de manière systématique par le projectile, quitte à ce que ce comportement polymorphe ne fasse strictement rien

    Dans les deux cas, il faut bien que le projectile ou que le foncteurs dispose... de l'instance particulière de la capacité de tir vers laquelle se tourner pour invoquer les fonctions en questions

    Et comme nous sommes finalement dans une situation dans laquelle différentes données "métier" sont susceptibles d'entrer en jeu d'une part et dans laquelle il ne rentre pas dans la responsabilité du projectile d'avoir la moindre relation vis à vis de la capacité de tir qui a provoqué sa création d'autre part, nous sommes bel et bien dans une situation dans laquelle toute cette gestion devrait être "déléguée" à... un contrôleur.
    C’est plus complexe que cela. Et effectivement mes projectiles peuvent rebondir sur un obstacle disparaitre ou être à tête chercheuse.
    C'est bien pour cela que tu ne peux pas limiter ta réflexion au seul passage d'informations entre ton projectile et la capacité de tir qui a provoqué sa création.

    La "transmission d'informations" vers et depuis le projectile doit être gérée dans son ensemble, par "quelque chose" qui puisse avoir connaissance de l'ensemble de l'environnement dans lequel le projectile évolue (la capacité de tir faisant partie de cet environnement ), quitte à ce qu'il ne voie le projectile que comme "un projectile, sans précision" et quitte à ce que ce "quelque chose" puisse mettre chaque projectile en relation avec la capacité de tir qui en a provoquer la création.

    Le "quelque chose" dont je parle n'est ni le projectile, ni la capacité de tir (bien qu'il ait connaissance des deux ) et se classe dans la catégorie des contrôleurs: une classe qui peut vérifier les états de certains objets dont elle a connaissance et modifier l'état d'autres objet en conséquence si le conditions pour ce faire sont vérifiées
    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 ….
    Sauf que tu donnes là une responsabilité qu'il ne devrait pas avoir à ton projectile.

    Les grandes étapes d'un projectile (pris de manière tout à fait générale ) sont: je pars, je touche, je meure "de ma belle mort" (mais sans rien avoir touché ).

    Mais ce n'est pas au projectile de prévenir qui que ce soit, c'est à cette "autre chose" dont j'ai parlé plus haut, d'autant plus que la capacité de tir n'est qu'une des choses qui peut devoir être tenue au courent de certaines étapes spécifiques
    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 ne crois pas qu'il soit pertinent de lancer ce genre de débat dans cette discussion en particulier.

    Mais je peux t'assurer que, sans entrer dans les détails spécifiques, le "mode de pensée" adopté en C# est, très certainement, beaucoup plus proche de celui que l'on adopte en java que de celui qu'on adopte en C++.

    Avec des différences notoires, je n'en disconvient absolument pas, mais le fait général reste malgré tout
    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

  13. #13
    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

    Tu prends des risques là
    E fait je ne sais pas, j'ai besoin d'un tableau de types non primaire. J'ai trouvé un exemple analogue ici : http://www.cplusplus.com/forum/unices/77610/ (en bas de la page). Si ce n’est pas comme cela ou qu'il faut faire autrement ... je suis prêt à modifier sans soucis.


    Mais Il n'est pas impossible que tu doives rajouter une ou deux classe pour servir de "pont".
    Certaines techniques qui peuvent te sembler naturelles en C# (comme le fait d'utiliser des pointeurs de fonctions) ne le sont vraiment pas, ou du moins, ne sont vraiment pas recommandées en C++.
    Je suis tout à fait d'accord avec toi, je le sens bien en ce moment.

    Mais elles auront sans doute (ou devraient avoir) toutes un point commun: le fait de dériver d'une classe de base identique, non ?
    Non, je n'utilise l'héritage que quand la modélisation m'y oblige, quand deux notions sont réellement proches (en implémentation). C'est un mécanisme couteux qui peut entrainer des difficultés de tests et de débogage genre poupées russes. J'évite aussi de tomber dans le travers de modéliser tout à fond : je ne fais que ce qui est nécessaire.

    Le fait est que les informations transmises par le message peuvent être d'ordre tout à fait différent en fonction du projectile à envoyer.
    J’utilise des structures standards qui sont passées en paramètre dans les messages ça couvre les spécificités de chacune.


    Mais la responsabilité du tireur et de la compétence de tir vis à vis du projectile tiré s'arrête... au moment où le projectile est tiré.
    Non, parce que la notion de projectile englobe les lazers, beam et arcs électriques (genre sos fantômes) et là il y a encore un lien après le tir. Tu vas me dire que ce n'est plus vraiment des projectiles et je te dirais que j'ai décidé de ne pas aller plus loin conceptuellement parce qu'il faut se fixer des limites ...il n'est pas possible de modéliser le monde.

    PS: quand je parle de beam et arcs je ne parle pas de particules ....

  14. #14
    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
    Voilà finalement la solution que j'ai mis en œuvre. Elle est un compromis donc ce n'est la meilleure mais de loin la moins pire. J'ai testé ça marche

    Le fameux message asexué :

    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
     
     
    class Message
    {
        private :
     
        int _value;
     
        public :
     
    	Message(int value)
    	{
    	  _value = value;
     
    	  printf("naissance de Message");
    	  printf("\n");
    	}
     
    	int GetValue()
    	{
    	  return _value;
    	}
     
    	~Message()
    	{
    		printf("mort de Message");
    		printf("\n");
    	}	
    };
    Le pointeur de fonction d'émission qui lui est attaché à une méthode statique.

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
     
     
    typedef void (*PointeurFonctionEmission)(Message * message);
    Une classe mimant une capacité de tir primaire.

    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
     
     
    class B
    {	
    	private :
     
    	Message * message;
     
    	public :
     
    	PointeurFonctionEmission Emet;
     
    	B(int instanceId)
    	{
    	 message = new Message(instanceId);
    	 printf("naissance de B");
    	 printf("\n");
    	}
     
    	void Emission()
    	{
    	  Emet(message);
    	  printf("Emission de message depuis B");
    	  printf("\n");
    	}
     
    	~B()
    	{
    		printf("mort de B");
    		printf("\n");	
    	}
    };
    La capacité de tir complexe.

    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
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
     
     
    class A 
    {
    	private :	
     
    	typedef struct {int InstanceId; A * Instance;} OutElement;
     
    	static OutElement** _elements;
    	B * _instanceB;
    	int _id;
     
    	static void AddOutElement(OutElement * newElement)
    	{
    		printf("ajout entrée dans annuaire de A");
    		printf("\n");
    	}
     
    	static void RemoveOutElement(int instanceId)
    	{		
    		printf("suppression entrée dans annuaire de A");
    		printf("\n");
    	}
     
    	public :
     
    	static void Appel(int instanceId)
    	{
    		printf("appel effectif de A");
    		printf("\n");
    	}
     
    	void SetHandler(PointeurFonctionEmission value)
    	{
    	  _instanceB->Emet = value;
    	}
     
    	int GetId()
    	{
    		return _id;
    	}
     
    	void Emission()
    	{
    	   _instanceB->Emission();
    	}
     
    	A()
    	{
    		_id = 7;
     
    		_instanceB = new B(_id);
     
    		OutElement * newElement = new OutElement();
    		newElement->Instance = this;
    		newElement->InstanceId = _id;
     
    		AddOutElement(newElement);
     
    		printf("naissance de A");
    		printf("\n");
    	}
     
    	~A()
    	{
    		printf("mort de A");
    		printf("\n");
     
    		RemoveOutElement(_id);
    	}	    
    };
    	A::OutElement** A::_elements=NULL;
    Le projectile.

    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 C 
    {
    	private :
     
    	int _instanceId;
     
    	public :
     
    	C(int instanceId)
    	{
    		_instanceId = instanceId;
    		printf("naissance de C");
    		printf("\n");
    	}	
     
    	void Appel()
    	{
    	   A::Appel(_instanceId);
    	   printf("appel de A depuis C");
    	   printf("\n");
    	}
     
    	~C()
    	{
    	   printf("mort de C");
    	   printf("\n");	
    	}		
    };


    L'usine.

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
     
     
    void Usine(Message * m)
    {
    	C * instanceC =new C(m->GetValue());
    }

    Le main de test.

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
     
     
    int main(int argc, char* argv[])
    {    	
     
    	A * instanceA = new A();
     
    	instanceA->SetHandler(Usine);
     
    	instanceA->Emission();
     
    	return 0;
    }

  15. #15
    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
    Le dernier problème :

    dans les méthodes

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
     
     
    static void AddOutElement(OutElement * newElement)
    	{
    		printf("ajout entrée dans annuaire de A");
    		printf("\n");
    	}
     
    	static void RemoveOutElement(int instanceId)
    	{		
    		printf("suppression entrée dans annuaire de A");
    		printf("\n");
    	}
    j'ai mis le code suivant que je pense pas être terrible en terme de performance mais surtout de gestion mémoire :


    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
     
     
    static void AddBeamOutElement(BeamOutElement * newElement)
    		{   
                BeamOutElement** newItems = new BeamOutElement*[_currentBeamOutCallNumber + 1];
     
    			int indexItems;
                for (indexItems = 0; indexItems < _currentBeamOutCallNumber; ++indexItems)
                {
                    newItems[indexItems] = _elements[indexItems];
                }
     
                newItems[_currentBeamOutCallNumber] = newElement;
     
                delete[] _elements;
     
                _elements = newItems;
     
    		   ++_currentBeamOutCallNumber;
     
    		   	printf("ADD BeamOutElement !");
    			printf("\n");
    		}

    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
     
     
    static void RemoveBeamOutElement(int instanceId)
    		{	
    			int index = 0, indexObjects;
    			BeamOutElement** newItems = new BeamOutElement*[_currentBeamOutCallNumber - 1];
     
    			for (indexObjects = _currentBeamOutCallNumber; --indexObjects >= 0; )
    			{
    				if (_elements[indexObjects]->InstanceId != instanceId)
    				{
    					newItems[index] = _elements[indexObjects];
    					++index;
    				}
    				else
    				{
    					_elements[indexObjects] = NULL;
    				}
    			}
     
    			delete[] _elements;
     
    			_elements = newItems;
     
    			--_currentBeamOutCallNumber;
     
    		   	printf("REMOVE BeamOutElement !");
    			printf("\n");            
    		}
    Pourriez vous m'aider à les corriger au mieux de ce que demande C++ ?

    Merci.

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